Qué es CSRF (Cross-Site Request Forgery) y cómo protegerse
CSRF (Cross-Site Request Forgery) es un ataque que engaña al navegador de una víctima autenticada para que envíe una solicitud HTTP maliciosa a una aplicación web sin su conocimiento, ejecutando acciones no autorizadas en su nombre. Es una de las vulnerabilidades más antiguas del hacking web, catalogada como CWE-352 por MITRE y presente durante años en el OWASP Top 10.
Aquí vas a ver cómo funciona CSRF, en qué condiciones se puede explotar, cómo practicarlo en entornos legales y, sobre todo, cómo defenderte de él como desarrollador o pentester.
Qué es CSRF y por qué ocurre
El ataque CSRF explota un comportamiento normal de los navegadores: cuando haces una petición HTTP a un dominio, el navegador adjunta automáticamente todas las cookies asociadas a ese dominio, incluida la cookie de sesión.
La aplicación vulnerable confunde ese envío automático de cookies con una solicitud legítima del usuario. No verifica si la petición la inició realmente el usuario o si viene de una página externa maliciosa.
El nombre ya lo explica: la solicitud es cross-site (viene de otro sitio) y es una forgery (una falsificación). El servidor no puede distinguirla de una petición genuina si no cuenta con mecanismos adicionales de verificación.
CSRF es especialmente peligroso por varios motivos:
- No requiere robar la sesión del usuario (el navegador la adjunta solo).
- El usuario no necesita hacer nada más que visitar la página maliciosa mientras está autenticado.
- Puede ejecutar cualquier acción que la víctima tenga permiso de realizar: cambiar email, contraseña, hacer transferencias, borrar datos, etc.
Si quieres el contexto más amplio de vulnerabilidades web, tienes nuestra guía completa de hacking web.
Cómo funciona CSRF paso a paso
El flujo de un ataque CSRF tiene tres fases:
- La víctima se autentica en la aplicación legítima (p.ej. su banco, su panel de administración). El servidor crea una cookie de sesión.
- La víctima visita la página del atacante (puede llegar por phishing, un enlace en redes sociales, etc.) mientras sigue autenticada.
- La página maliciosa dispara una petición al servidor legítimo. El navegador adjunta automáticamente la cookie de sesión. El servidor la acepta y ejecuta la acción.
Ejemplo 1: formulario HTML que se autoenvía
El atacante aloja esta página en su servidor:
<!-- Página maliciosa alojada en https://evil.com/csrf-attack.html -->
<html>
<body onload="document.getElementById('csrf-form').submit()">
<!-- Formulario invisible que apunta a la app legítima -->
<form id="csrf-form"
action="https://banco-victima.com/transferencia"
method="POST"
style="display:none">
<input type="hidden" name="destinatario" value="ES91210004184502000813144">
<input type="hidden" name="importe" value="1500">
<input type="hidden" name="concepto" value="pago">
</form>
</body>
</html>
En cuanto la víctima carga esta página, el formulario se envía solo. El navegador adjunta la cookie de sesión del banco. Si la aplicación no verifica el origen, la transferencia se ejecuta.
Ejemplo 2: petición GET vía etiqueta <img>
<!-- Si la acción crítica acepta GET (mala práctica), basta con una imagen -->
<img src="https://admin-panel.com/usuarios/delete?id=42" width="0" height="0">
Al intentar cargar la imagen, el navegador lanza una petición GET autenticada. El usuario ni se entera.
Condiciones necesarias para que CSRF funcione
Las tres condiciones tienen que cumplirse a la vez:
- Que exista una acción relevante en la aplicación: una acción que valga la pena falsificar (cambiar email, contraseña, realizar pagos, etc.).
- Que la autenticación se base solo en cookies: la sesión se mantiene mediante cookies de sesión (o autenticación HTTP Basic/Digest). Si la aplicación exige un header personalizado como
Authorization: Bearer <token>que JavaScript gestiona explícitamente, el CSRF clásico no aplica. - Que no haya parámetros impredecibles: la solicitud no lleva ningún parámetro que el atacante no pueda conocer o adivinar (de ahí la importancia del token CSRF).
«CSRF es un ataque que fuerza a un usuario final a ejecutar acciones no deseadas en una aplicación web en la que está actualmente autenticado. Con un poco de ayuda de ingeniería social, un atacante puede engañar a los usuarios de una aplicación web para que ejecuten acciones de su elección.»
Práctica en laboratorio: DVWA y PortSwigger Academy
Aviso legal: practica siempre en entornos controlados y legales. Explotar CSRF en sistemas ajenos sin permiso es un delito en España (art. 197 bis del Código Penal, penas de hasta 2 años de prisión). Usa solo los laboratorios indicados a continuación.
Opción A: DVWA (Damn Vulnerable Web Application)
DVWA es una aplicación PHP/MySQL deliberadamente vulnerable, buena para aprender en local:
- Instala DVWA con Docker:
docker run --rm -it -p 80:80 vulnerables/web-dvwa - Accede a
http://localhost, inicia sesión (admin/password) y navega a CSRF. - En nivel Low, el formulario de cambio de contraseña no lleva token. Inspecciona la petición con las DevTools del navegador o con Burp Suite.
- Crea un HTML con el formulario malicioso apuntando a
http://localhost/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change. - Ábrelo en otra pestaña mientras sigues autenticado en DVWA. Verás que la contraseña cambia sin que hagas nada.
Opción B: PortSwigger Web Security Academy
PortSwigger Web Security Academy tiene laboratorios gratuitos en la nube, sin instalar nada. Busca los labs de la sección CSRF. Puedes practicar con Burp Suite Community Edition o con OWASP ZAP para interceptar y manipular las peticiones.
Si quieres estructurar tu aprendizaje, nuestro Curso de Hacking Web cubre CSRF junto a XSS, SQLi y el resto del OWASP Top 10.
Vídeo recomendado (YouTube):
🔙 CROSS-SITE REQUEST FORGERY (CSRF) Explicado Paso a Paso y para Principiantes | Ciberseguridad 🔒
Canal: El Pingüino de Mario
Cómo prevenir CSRF: defensas reales
La defensa eficaz contra CSRF combina varias capas. Van las cuatro principales:
1. Token anti-CSRF (Synchronizer Token Pattern)
Es la defensa más sólida y la más recomendada. El servidor genera un valor aleatorio e impredecible (el token CSRF) y lo incrusta en cada formulario o petición de estado. Al recibir la solicitud, comprueba que el token coincida con el guardado en sesión.
<!-- Formulario legítimo con token anti-CSRF -->
<form method="POST" action="/cambiar-email">
<input type="hidden" name="csrf_token" value="a93f2e8b1d4c7f0e5a29...">
<input type="email" name="nuevo_email">
<button type="submit">Cambiar email</button>
</form>
El atacante no puede conocer ni leer ese token porque la Same-Origin Policy impide que su página lea el HTML del dominio legítimo. Frameworks como Django, Laravel, Spring Security y ASP.NET lo implementan automáticamente.
2. Cookies SameSite=Lax / Strict
El atributo SameSite de las cookies controla cuándo se adjuntan en peticiones cross-site:
- Con Strict, la cookie no se adjunta en ninguna petición cross-site. Protección total, pero puede romper flujos de navegación externa (p.ej. seguir un enlace a la aplicación desde un email).
- Con Lax (valor por defecto en Chrome desde 2020), la cookie se adjunta solo en navegación de nivel superior (clic en un enlace) y no en peticiones sub-recursos (formularios POST, imágenes, iframes). Elimina los vectores CSRF más comunes.
- Con None, se adjunta siempre (requiere
Secure). Necesario para flujos cross-site legítimos (SSO, widgets embebidos).
Importante: SameSite=Lax no basta por sí solo. Sigue siendo vulnerable a ataques que usan navegación de nivel superior (GET). Tiene que usarse junto con tokens anti-CSRF.
3. Verificación de cabeceras Origin y Referer
El servidor puede comprobar las cabeceras HTTP Origin y Referer para verificar que la petición viene de su propio dominio:
# Pseudocódigo Python/Flask
from flask import request, abort
ALLOWED_ORIGIN = "https://mi-app.com"
@app.before_request
def check_origin():
if request.method in ("POST", "PUT", "DELETE", "PATCH"):
origin = request.headers.get("Origin") or request.headers.get("Referer", "")
if not origin.startswith(ALLOWED_ORIGIN):
abort(403)
La limitación es que Referer puede estar ausente (usuarios con proxies, extensiones de privacidad) o suprimido. No debería ser la única defensa.
4. Patrón Double-Submit Cookie
Útil en aplicaciones sin estado (APIs REST) donde no hay sesión de servidor. El servidor genera un token aleatorio, lo guarda en una cookie y exige que también aparezca como parámetro en el cuerpo o cabecera de la petición. Como el atacante no puede leer las cookies del dominio víctima desde otro origen, no puede duplicar el valor.
// En el cliente (JavaScript):
const csrfToken = getCookie("csrf-token");
fetch("/api/cambiar-config", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": csrfToken // también en cabecera
},
body: JSON.stringify({ setting: "value" })
});
Tabla comparativa de defensas anti-CSRF
| Defensa | Cómo funciona | Limitaciones | ¿Recomendada? |
|---|---|---|---|
| Token anti-CSRF (Synchronizer) | Token aleatorio en formulario; servidor verifica contra sesión | Requiere gestión de sesión; puede fallar con XSS en el mismo dominio | Sí (defensa principal) |
| SameSite=Lax | El navegador no adjunta la cookie en peticiones POST cross-site | Vulnerable a navegación GET de nivel superior; soporte desigual en navegadores viejos | Sí (capa adicional) |
| SameSite=Strict | La cookie no se adjunta en ninguna petición cross-site | Puede romper flujos OAuth/SSO y enlaces externos legítimos | Sí (si no hay flujos cross-site) |
| Verificación Origin/Referer | Servidor comprueba la cabecera de origen de la petición | Referer puede estar ausente; susceptible a errores de configuración | Sí (capa adicional) |
| Double-Submit Cookie | Token en cookie + en parámetro; servidor verifica que coincidan | Vulnerable si el atacante controla un subdominio del sitio | Sí (para APIs sin estado) |
| Solo comprobar Referer | El servidor rechaza peticiones sin Referer conocido | Referer puede suprimirse; fácil de evitar; nunca debe ser la única defensa | No como defensa única |
Errores comunes al implementar protección CSRF
- Confiar solo en comprobar el Referer: el header
Refererpuede estar ausente o manipulado en ciertas configuraciones. No basta por sí solo. - Usar tokens predecibles o estáticos: tomar el ID de sesión como token CSRF, o un valor fijo que no rota, anula la protección. El token tiene que ser criptográficamente aleatorio y de un solo uso (o al menos por sesión).
- Ignorar peticiones GET con efectos secundarios: las acciones destructivas nunca deben ejecutarse con GET. Si un endpoint GET cambia estado, es doblemente vulnerable (imagen, enlace, prefetch).
- Pensar que HTTPS protege de CSRF: HTTPS cifra el tráfico pero no verifica el origen de la solicitud. Una página HTTPS maliciosa puede lanzar peticiones CSRF igual.
- Asumir que las APIs JSON son inmunes: si la API acepta
Content-Type: text/plaino no exige un content-type específico, puede ser vulnerable a CSRF mediante formularios conenctype="text/plain". - Olvidar que XSS rompe todas las defensas CSRF: si hay una vulnerabilidad XSS en el mismo dominio, el atacante puede leer el token desde el DOM y usarlo. XSS y CSRF hay que tratarlos juntos. Consulta nuestra guía sobre bug bounty para ver cómo reportar este tipo de cadenas de vulnerabilidades.
📚 ¿Quieres practicarlo en un laboratorio legal, paso a paso? Lo trabajamos en el Módulo 6: CSRF y SSRF de nuestro Curso de Hacking Web.
Preguntas frecuentes
¿CSRF y XSS son lo mismo?
No. XSS (Cross-Site Scripting) inyecta código malicioso en la aplicación víctima que se ejecuta en el navegador de otros usuarios. CSRF engaña al navegador para que envíe peticiones no autorizadas a la aplicación víctima sin ejecutar código en ella. Son complementarios: XSS puede usarse para robar tokens CSRF y escalar el ataque.
¿Las APIs REST con tokens JWT son vulnerables a CSRF?
Por lo general no, si el token JWT se envía solo en el header Authorization (que JavaScript gestiona explícitamente). Un atacante no puede añadir headers personalizados desde un formulario HTML. El problema surge si el JWT se guarda en una cookie, en cuyo caso aplica CSRF igual que con sesiones tradicionales.
¿Con SameSite=Lax ya no hace falta el token CSRF?
No del todo. SameSite=Lax elimina los vectores más comunes pero no todos: la navegación de nivel superior con GET sigue adjuntando la cookie. Tampoco todos los usuarios usan Chrome actualizado. La recomendación de OWASP es usar SameSite como capa complementaria, nunca como única defensa.
¿Cómo detecto si una aplicación es vulnerable a CSRF?
Mira si las peticiones de estado (POST, PUT, DELETE) incluyen un token impredecible en el cuerpo o en los headers. Si la única prueba de identidad es la cookie de sesión, hay alta probabilidad de vulnerabilidad. Herramientas como Burp Suite y OWASP ZAP tienen escáneres de CSRF integrados.
¿CSRF afecta a las aplicaciones modernas de página única (SPA)?
Las SPA que usan Authorization: Bearer <token> en los headers son en general inmunes a CSRF clásico porque un formulario HTML no puede añadir headers personalizados. Pero si la SPA usa cookies de sesión para autenticarse, la vulnerabilidad sigue ahí y hay que mitigarla con los mismos mecanismos: cookies SameSite y Double-Submit Cookie.
Aviso ético y legal
El contenido de este artículo tiene fines exclusivamente educativos. Explotar vulnerabilidades CSRF en sistemas reales sin autorización expresa y por escrito de su propietario es un delito en España tipificado en el artículo 197 bis del Código Penal, con penas de hasta 2 años de prisión (o hasta 3 si afecta a datos de carácter personal o sistemas de interés público). Practica siempre en entornos controlados como DVWA, PortSwigger Academy u otros laboratorios diseñados para ello.
