Saltearse al contenido

CSRF (Cross-Site Request Forgery)

Fuerza al navegador de una víctima autenticada a enviar una petición no deseada a una aplicación donde tiene sesión. El atacante no ve la respuesta; abusa de que el navegador adjunta automáticamente las credenciales (cookies) a toda petición dirigida a ese sitio, venga del origen que venga. Si una acción con efectos puede reproducirse conociendo solo su estructura y la app se fía solo de la cookie, es vulnerable. En esencia, CSRF es un fallo de verificación de intención: la app no comprueba que la petición la quiso hacer el usuario.

sequenceDiagram
    participant U as Víctima (sesión activa)
    participant M as Sitio del atacante
    participant S as Aplicación objetivo
    U->>M: visita la página maliciosa
    M-->>U: formulario con autoenvío a S
    U->>S: petición con las cookies de sesión
    S-->>U: acción ejecutada como la víctima

El impacto es el de la acción que se fuerza con la sesión de la víctima:

  • Cambiar email o contraseña → el atacante toma la cuenta (ATO) sin conocer la contraseña actual.
  • Transferencias, compras, cambios de plan.
  • Crear usuarios o claves API, cambiar permisos.
  • Modificar reglas persistentes: reenvío de correo, webhooks, DNS del router, recuperación de cuenta.
  • Login CSRF: forzar a la víctima a autenticarse con la cuenta del atacante para capturar su actividad (búsquedas, tarjetas guardadas).

Tres condiciones necesarias:

  1. Acción relevante que al atacante le interese provocar.
  2. Gestión de sesión basada en cookies que el navegador envía sola (no tokens en cabecera que haya que añadir por JS).
  3. Parámetros predecibles: sin secretos que el atacante no pueda adivinar.

Por diseño, el navegador adjunta las cookies del destino en cualquier petición cross-site. El atributo SameSite modula ese comportamiento:

  • None: se envían siempre (requiere Secure).
  • Lax (por defecto en navegadores modernos): se envían en navegaciones top-level GET (clic en un enlace), pero no en POST cross-site ni en subrecursos.
  • Strict: no se envían en ninguna petición cross-site.

Además, las peticiones JSON con Content-Type: application/json disparan un preflight CORS, lo que bloquea el CSRF clásico… salvo que el endpoint acepte Content-Type “simples” (text/plain, application/x-www-form-urlencoded).

  • GET con efectos: basta una etiqueta que cargue la URL (<img>, <link>).
  • POST: formulario que se autoenvía.
  • JSON/APIs: explotable si el servidor acepta text/plain/form o no valida el tipo (evita el preflight).
  • Multipart (enctype=multipart/form-data): para endpoints que esperan ficheros.
  • Login CSRF: autenticar a la víctima con credenciales del atacante.
  • CSRF → XSS: cuando la acción forzada introduce un payload almacenado.
  • CSRF asistido por CORS/clickjacking: leer el token (CORS laxo) o completar un flujo (clickjacking) cuando falta un parámetro.
  1. Captura cada acción que cambia estado y comprueba si lleva token anti-CSRF (campo oculto, cabecera, cookie espejo).
  2. Si lo lleva, prueba sistemáticamente a:
    • Quitarlo por completo.
    • Dejarlo vacío.
    • Usar un token de otra sesión / otra cuenta (¿está ligado a la sesión?).
    • Usar un token válido pero caducado, o uno con la estructura correcta.
    • Cambiar POST→GET (muchos frameworks aceptan ambos).
    • Cambiar el Content-Type (JSON → text/plain) para esquivar preflight.
  3. Revisa el SameSite de la cookie de sesión y si la app valida Origin/Referer.
<!-- 1) POST autoenviado -->
<form action="https://banco.tld/email/change" method="POST" id="x">
<input type="hidden" name="email" value="atacante@evil.tld">
</form>
<script>document.getElementById('x').submit()</script>
<!-- 2) GET con efectos -->
<img src="https://banco.tld/transfer?to=atacante&amount=1000">
<!-- 3) API JSON sin preflight (text/plain) -->
<form action="https://api.tld/v1/role" method="POST" enctype="text/plain">
<input name='{"role":"admin","x":"' value='"}'>
</form>
<script>document.forms[0].submit()</script>
<!-- 4) fetch cross-site (modo no-cors, para endpoints simples) -->
<script>
fetch('https://banco.tld/email/change',{method:'POST',mode:'no-cors',
credentials:'include',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'email=atacante@evil.tld'});
</script>

Aloja el PoC en tu dominio y consigue que la víctima lo abra (enlace, iframe, publicidad).

  • SameSite=Lax por defecto: no cubre navegaciones top-level GET. Si una acción con efectos acepta GET, sigue siendo explotable con un simple enlace. Tampoco cubre bien dos minutos tras emitir la cookie (ventana histórica en algunos navegadores) ni el salto entre subdominios/sitios hermanos.
  • Method override: APIs que aceptan _method=POST o X-HTTP-Method-Override permiten mandar como GET/formulario algo que debía ser POST.
  • Token no ligado a sesión: si el token es válido entre cuentas, róbalo de tu propia sesión y reúsalo en la de la víctima.
  • Token predecible / estático: si no es aleatorio por sesión, se adivina.
  • Double-submit débil: si la validación compara cookie vs parámetro y puedes fijar la cookie (vía un subdominio que controles o una inyección), eludes la comprobación.
  • CORS mal configurado: si la app refleja el Origin con Allow-Credentials: true, lee el token anti-CSRF con un fetch autenticado y envíalo tú.
  • Validación de Referer saltable: cuando solo valida “si hay Referer”, omítelo con Referrer-Policy: no-referrer (meta) o un contexto data:.

Burp Suite (Engagement tools → Generate CSRF PoC), extensiones de generación de PoC, y curl para validar la ausencia/omisión de token.

Toma de cuenta directa (cambio de email/clave), acciones privilegiadas, persistencia vía reglas (reenvío, webhooks), y CSRF→XSS→compromiso mayor. Un login CSRF puede capturar datos sensibles que la víctima introduce creyendo estar en su cuenta.

  • Peticiones que cambian estado con Origin/Referer ausente o externo.
  • Patrones de token ausente/ inválido/ reutilizado en endpoints sensibles.
  • Referers cross-site anómalos hacia acciones críticas; picos coordinados desde una misma campaña (mismo enlace malicioso).
  • Cambios de email/clave seguidos de actividad desde una IP nueva.

Logs con Origin/Referer/Content-Type por petición, WAF, y métricas de acciones sensibles por usuario/origen.

  1. Token anti-CSRF (synchronizer token): valor impredecible, ligado a la sesión, obligatorio y validado en servidor en toda acción que cambie estado. Alternativa: double-submit bien implementado (token en cookie + cabecera, comparados), robusto solo si el atacante no puede fijar la cookie.
  2. Cookies SameSite: Lax por defecto mitiga, pero para acciones críticas usa Strict y token; no dependas solo del default.
  3. Validar Origin/Referer en servidor para peticiones con efectos (rechazar si no coincide con el propio origen).
  4. APIs: exigir una cabecera personalizada (X-Requested-With) o Authorization: Bearer (no se adjunta sola) y rechazar Content-Type simples en endpoints JSON (forzar application/json dispara preflight).
  5. Re-autenticación / step-up para acciones muy sensibles (cambio de email, transferencias, borrado de cuenta).

Invalidar sesiones afectadas, revertir cambios forzados (email/reglas/claves), avisar a la víctima, y añadir token/validación de Origin en el endpoint vulnerable.

  • ING Direct (2008) — el paper académico de Zeller & Felten demostró CSRF que permitía transferencias en la banca online; caso de estudio fundacional.
  • YouTube (2008) — CSRF que permitía múltiples acciones en la cuenta de la víctima (favoritos, suscripciones, mensajes).
  • Routers domésticos — oleadas de CSRF que cambiaban el DNS del router desde una web maliciosa (p. ej. campañas contra routers en Brasil y Reino Unido), redirigiendo todo el tráfico de la víctima.
  • Plugins de CMS (WordPress, etc.) — CSRF (a menudo encadenado con XSS almacenado) es de los tipos de CVE más frecuentes en plugins.

CVEs concretos en NVD (https://nvd.nist.gov/vuln/search) y GitHub Advisories (https://github.com/advisories).

  • Identificadas todas las acciones que cambian estado.
  • Comprobada presencia y validación del token (quitar/vaciar/otra sesión/reutilizar).
  • Probada conversión POST→GET y cambio de Content-Type (preflight).
  • Revisado SameSite y validación de Origin/Referer.
  • Probado method override y double-submit con cookie fijada.
  • CORS evaluado como vía para robar el token.
  • PoC funcional para al menos una acción de impacto (idealmente ATO).