Llegamos a la última categoría del Top Ten y a la segunda de las novedades de 2021 (junto con A04 Diseño Inseguro). A10:2021 – Server-Side Request Forgery (SSRF) entró en la lista impulsada por la encuesta a la comunidad y por su creciente relevancia en entornos cloud. La idea es tan simple como peligrosa: si una funcionalidad hace que el servidor realice una petición HTTP (u otra) a una URL que el usuario controla, un atacante puede lograr que el servidor pida cosas que él no podría pedir directamente: servicios internos, paneles de administración, y —lo más codiciado en la nube— el servicio de metadatos que expone credenciales de la instancia.

Ya rozamos el SSRF en la lección de XXE (03-07), donde una entidad externa hacía que el parser XML fuese a buscar una URL. Aquí lo tratamos de forma general y con su ejemplo canónico en BazarNube: una función que descarga una imagen desde una URL proporcionada por el usuario (por ejemplo, para importar la foto de perfil o la imagen de un producto desde un enlace). Bien hecha es cómoda; mal hecha, es una puerta a la red interna.

Aviso legal y ético: los ejemplos de acceso a servicios internos y metadatos son ilustrativos y con datos ficticios. Practica solo sobre sistemas propios o con autorización explícita. Alcanzar la red interna o los metadatos de un sistema ajeno sin permiso es delito.

Contenido

  1. Qué es el SSRF y por qué es tan peligroso en la nube
  2. El caso de la descarga de imágenes de BazarNube
  3. Cómo se explota (ilustrativo)
  4. Prevención 1: allowlist de destinos
  5. Prevención 2: bloquear redirecciones y direcciones internas
  6. Prevención 3: segmentación de red y defensa en profundidad
  7. Errores comunes, ejercicios y soluciones

  1. Qué es el SSRF y por qué es tan peligroso en la nube

En un SSRF, el atacante no ataca directamente al servicio interno; usa el servidor como intermediario. El servidor suele estar en una posición de red privilegiada: puede alcanzar bases de datos, colas, paneles de administración y el endpoint de metadatos del proveedor cloud, todos ellos normalmente inaccesibles desde internet. Al controlar la URL de destino, el atacante "pide prestada" esa posición.

El objetivo estrella en la nube es el servicio de metadatos (una IP-enlace local, del tipo 169.254.169.254), que puede devolver credenciales temporales de la instancia. Con ellas, un SSRF puede escalar a un compromiso completo de la cuenta cloud. Por eso A10 saltó al Top Ten.

  1. El caso de la descarga de imágenes de BazarNube

Marc implementó una función para importar la imagen de un producto desde una URL que introduce el vendedor:

// media.js — VULNERABLE a SSRF
router.post('/api/products/:id/image-from-url', requireLogin, async (req, res) => {
  const { imageUrl } = req.body;                 // URL controlada por el usuario
  const response = await fetch(imageUrl);         // el SERVIDOR hace la peticion
  const buffer = Buffer.from(await response.arrayBuffer());
  await saveProductImage(req.params.id, buffer);
  res.json({ ok: true });
});

El servidor toma imageUrl tal cual y hace fetch hacia donde diga el usuario. No se valida el destino, ni el esquema, ni si la URL apunta a la red interna. Es SSRF de manual.

  1. Cómo se explota (ilustrativo)

En lugar de una imagen, el atacante envía URLs internas:

POST /api/products/42/image-from-url
{ "imageUrl": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" }

El servidor, con su acceso privilegiado, consulta el servicio de metadatos y (según la configuración) obtiene credenciales de la instancia, que pueden acabar guardadas o reflejadas. Otras variantes:

http://10.0.0.5:5432/           -> tantear la base de datos interna
http://localhost:9200/          -> un servicio interno (p. ej. un buscador) sin auth
file:///etc/passwd              -> leer ficheros locales (si el cliente sigue file://)
http://interno/admin            -> panel de administracion no expuesto a internet
flowchart LR
  A[Atacante] -->|imageUrl interna| B[API BazarNube]
  B -->|el servidor la solicita| C[Metadatos cloud / servicio interno]
  C -->|respuesta con datos sensibles| B
  B -->|reflejada o almacenada| A

  1. Prevención 1: allowlist de destinos

La defensa más robusta es una lista blanca: define exactamente a qué destinos puede ir la función y rechaza todo lo demás. Una lista negra ("bloquea 169.254.169.254") es insuficiente, porque hay incontables formas de ofuscar direcciones (decimal, hexadecimal, DNS que resuelve a IP interna, IPv6...).

// media.js — SEGURO (parte 1): allowlist de esquema y de host
const ALLOWED_HOSTS = new Set(['cdn.proveedor-imagenes.com', 'images.bazarnube.com']);

function assertAllowedUrl(raw) {
  const url = new URL(raw);                          // lanza si no es URL valida
  if (!['https:'].includes(url.protocol))            // solo HTTPS
    throw new Error('Esquema no permitido');
  if (!ALLOWED_HOSTS.has(url.hostname))              // solo hosts de la allowlist
    throw new Error('Host no permitido');
  return url;
}

Si el caso de uso lo permite (a menudo sí), lo mejor es no aceptar URLs arbitrarias en absoluto: que el usuario suba el fichero directamente, o que elija de un catálogo de proveedores permitidos. Eliminar la funcionalidad de "descarga desde URL libre" elimina la clase de vulnerabilidad.

  1. Prevención 2: bloquear redirecciones y direcciones internas

Una allowlist de host se puede burlar de dos formas que hay que cerrar:

  • DNS rebinding / host que resuelve a IP interna: el atacante controla un dominio (cdn.malo.example) que resuelve a 169.254.169.254. Por eso hay que validar también la IP resuelta, no solo el nombre.
  • Redirecciones: un host permitido responde con un 302 hacia una URL interna. Por eso hay que deshabilitar el seguimiento automático de redirecciones (o revalidar cada salto).
// media.js — SEGURO (parte 2): resolver IP, rechazar rangos internos, sin redirecciones
const dns = require('dns').promises;
const ipaddr = require('ipaddr.js');

async function assertPublicIp(hostname) {
  const { address } = await dns.lookup(hostname);   // IP real a la que resuelve
  const addr = ipaddr.parse(address);
  const range = addr.range();                        // 'private', 'loopback', 'linkLocal'...
  if (['private', 'loopback', 'linkLocal', 'uniqueLocal', 'reserved'].includes(range))
    throw new Error('Destino interno no permitido');  // bloquea 10.x, 127.x, 169.254.x...
}

router.post('/api/products/:id/image-from-url', requireLogin, async (req, res) => {
  try {
    const url = assertAllowedUrl(req.body.imageUrl);
    await assertPublicIp(url.hostname);              // valida la IP resuelta
    const response = await fetch(url, { redirect: 'error', signal: timeout(5000) }); // sin redirecciones, con timeout
    if (!response.ok) return res.status(400).json({ error: 'Descarga fallida' });
    const type = response.headers.get('content-type') || '';
    if (!type.startsWith('image/')) return res.status(400).json({ error: 'No es una imagen' });
    const buffer = Buffer.from(await response.arrayBuffer());
    await saveProductImage(req.params.id, buffer);
    res.json({ ok: true });
  } catch (e) {
    logger.warn({ event: 'ssrf_blocked', reqId: req.id, reason: e.message }); // enlaza con A09
    res.status(400).json({ error: 'URL no permitida' });
  }
});

Detalles clave: redirect: 'error' impide que un host permitido redirija a uno interno; assertPublicIp rechaza cualquier IP de rango interno (incluida la link-local de metadatos 169.254.x); el timeout limita el abuso; validar el content-type como imagen añade una barrera; y el evento bloqueado se registra (A09).

Nota sobre la carrera TOCTOU: entre resolver la IP y hacer el fetch, el DNS podría cambiar. En escenarios de alto riesgo, resuelve la IP y conéctate a esa IP validada (fijando el host), o usa un proxy de salida que aplique la política de forma centralizada.

  1. Prevención 3: segmentación de red y defensa en profundidad

Aunque el código sea perfecto, conviene que la infraestructura limite el daño:

Capa Control
Aplicación Allowlist de host/esquema, validar IP resuelta, sin redirecciones, timeouts
Red Segmentar: el servicio no debería poder alcanzar la red interna ni los metadatos
Cloud Usar la versión endurecida del servicio de metadatos (que exige token) y roles de mínimo privilegio
Salida Un proxy de egreso que centralice y aplique la política de destinos permitidos
Observabilidad Registrar y alertar sobre peticiones salientes anómalas (A09)

La regla de fondo, de nuevo, es menor privilegio: si la función solo necesita alcanzar un CDN público, la red debería impedirle físicamente llegar a 169.254.169.254 o a 10.0.0.0/8. La defensa en profundidad hace que un fallo en una capa no sea catastrófico.

Errores Comunes y Consejos

  • Aceptar URLs arbitrarias del usuario. Si puedes evitarlo (subida directa, catálogo), elimina la clase de vulnerabilidad.
  • Usar lista negra de IPs. Es evadible (codificaciones, DNS a IP interna). Usa lista blanca de hosts y valida la IP resuelta.
  • Seguir redirecciones automáticamente. Un host permitido puede redirigir a uno interno; usa redirect: 'error'.
  • Validar solo el nombre de host, no la IP. DNS rebinding lo burla; resuelve y valida el rango.
  • Olvidar los metadatos cloud. Son el objetivo prioritario; bloquéalos por red y usa la variante que exige token.
  • No poner timeouts. Permiten abuso y escaneo de puertos por temporización.
  • Consejo: combina control en la aplicación (allowlist + validación de IP), en la red (segmentación) y en la nube (metadatos endurecidos + mínimo privilegio). Ninguna capa por sí sola basta.

Ejercicios

Ejercicio 1. ¿Por qué una lista negra que bloquea 169.254.169.254 es insuficiente para prevenir SSRF? Da dos formas de evadirla.

Ejercicio 2. El equipo añade una allowlist de hosts, pero sigue siguiendo redirecciones. Describe cómo un atacante podría burlar la allowlist y cómo se corrige.

Ejercicio 3. Propón un rediseño de la función "imagen desde URL" que elimine la clase de vulnerabilidad SSRF en lugar de solo mitigarla.

Soluciones

Solución 1. Porque hay muchas maneras de referirse a la misma dirección o de alcanzar destinos internos sin usar esa cadena exacta: (a) codificaciones alternativas de la IP (decimal 2852039166, hexadecimal, IPv6, 0177.0.0.1...); (b) un dominio controlado por el atacante que resuelve por DNS a una IP interna (rebinding). La lista negra no cubre estas variantes; por eso se usa allowlist más validación de la IP realmente resuelta.

Solución 2. El atacante usa un host permitido (o uno que controla y que la allowlist acepta) que responde con un 302 Location: http://169.254.169.254/...; si el cliente sigue la redirección, acaba en el destino interno. Se corrige con redirect: 'error' (o manual revalidando cada salto con la misma política de allowlist + IP).

Solución 3. Sustituir la "descarga desde URL libre" por subida directa del fichero por parte del usuario (multipart) o por selección desde un catálogo cerrado de proveedores/CDN permitidos. Si el servidor nunca hace una petición a una URL controlada por el usuario, no hay SSRF que mitigar: la clase de vulnerabilidad desaparece por diseño (enlaza con A04).

Conclusión

El SSRF convierte al servidor en un cómplice involuntario que pide recursos por el atacante, y en la nube puede escalar hasta credenciales de la instancia. Se previene con allowlist de destinos, validación de la IP resuelta, bloqueo de redirecciones, timeouts, y —sobre todo— segmentación de red y metadatos endurecidos. La mejor mitigación, cuando es viable, es no aceptar URLs arbitrarias y rediseñar la funcionalidad.

Entrada de backlog — A10: función de imagen-desde-URL asegurada con allowlist de host/esquema, validación de IP resuelta, redirect: 'error' y timeout; segmentación de red para impedir acceso a metadatos e IPs internas; metadatos cloud en modo endurecido; eventos SSRF bloqueados registrados en el SIEM.


Cierre del módulo 3. Hemos recorrido las diez categorías del OWASP Top Ten 2021 —A01 a A10— más las dos profundizaciones heredadas (XSS y XXE), y en cada una hemos seguido el mismo ciclo sobre BazarNube: código vulnerable → ataque ilustrativo → código corregido → entrada en el backlog. El Top Ten nos ha dado un mapa excelente de los riesgos más frecuentes, pero es precisamente eso: los "diez más frecuentes", no un checklist exhaustivo ni verificable requisito a requisito.

Para pasar de "conozco los grandes riesgos" a "puedo verificar de forma sistemática y por niveles que mi aplicación cumple un catálogo completo de requisitos de seguridad", necesitamos un estándar más detallado y comprobable. Ese es el salto natural del módulo 4: OWASP ASVS (Application Security Verification Standard) en profundidad, donde convertiremos las lecciones de este módulo en requisitos verificables organizados por niveles de aseguramiento. Muchas de las correcciones que anotamos en el backlog de BazarNube reaparecerán allí como requisitos concretos del ASVS que sabremos verificar.

Curso de OWASP: Directrices y Estándares para la Seguridad en Aplicaciones Web

Módulo 1: Introducción a OWASP

Módulo 2: Principales Proyectos de OWASP

Módulo 3: OWASP Top Ten 2021 en Profundidad

Módulo 4: OWASP ASVS (Application Security Verification Standard)

Módulo 5: OWASP SAMM (Software Assurance Maturity Model)

Módulo 6: OWASP ZAP (Zed Attack Proxy)

Módulo 7: Buenas Prácticas y Recomendaciones

Módulo 8: Ejercicios Prácticos y Casos de Estudio

Módulo 9: Evaluación y Certificación

© Copyright 2026. Todos los derechos reservados