El Server Va Por Vos
SSRF
// OBJETIVO
Aprovechar que el servidor trae la URL que indicás para alcanzar un recurso interno y leer el endpoint de metadata de la nube.
// ZONA DE ATAQUE
Corre en un iframe de origen opaco: no puede leer este sitio ni tu progreso. Cuando logres el exploit, la flag aparece dentro de la app — copiala y verificala.
// CONSULTÁ
Pistas
Código vulnerable
El servidor busca la URL del usuario sin restringir el destino:
app.post('/api/preview', async (req, res) => {
const { target } = req.body;
const r = await fetch(target); // ← destino controlado por el usuario
res.json({ ok: true, body: await r.text() });
});
Como no hay allowlist ni bloqueo de rangos internos, el atacante hace que el
servidor pida direcciones que él no alcanza desde afuera: servicios en
localhost, redes privadas (10.0.0.0/8, 192.168.0.0/16) o el endpoint
link-local de metadata 169.254.169.254, que en la nube suele exponer
credenciales de la instancia. Es SSRF (Server-Side Request Forgery).
Remediación
No dejes que el usuario elija un destino arbitrario: validá el esquema, resolvé el host y rechazá todo lo que apunte a rangos internos.
const { target } = req.body;
+ const url = new URL(target);
+ if (!['http:', 'https:'].includes(url.protocol)) throw new Error('esquema no permitido');
+ const { address } = await dns.promises.lookup(url.hostname);
+ if (isPrivate(address) || isLinkLocal(address)) throw new Error('destino interno bloqueado');
- const r = await fetch(target);
+ const r = await fetch(url, { redirect: 'error' }); // sin seguir redirects a internos
res.json({ ok: true, body: await r.text() });
Por qué funciona: validar el esquema corta file:// y gopher://; resolver
el host y rechazar direcciones privadas o link-local impide alcanzar metadata y
servicios internos; y redirect: 'error' evita que un 302 a 169.254.169.254
esquive el chequeo. Idealmente, además, usá una allowlist de destinos.