CSRF (Cross-Site Request Forgery): qué es, cómo se explota y cómo se corrige
· 14 min de lectura
Aprende qué es el Cross-Site Request Forgery, por qué las cookies que el navegador envía solo permiten falsificar acciones de la víctima, cómo se prueba en un laboratorio autorizado y cómo se corrige con tokens anti-CSRF, cookies SameSite y la comprobación de Origin, y qué relación tiene con XSS y CORS.
Cuando inicias sesión en una web, el servidor te da una cookie de sesión y tu navegador la guarda. A partir de ahí, cada vez que el navegador hace una petición a esa web, adjunta la cookie automáticamente. Es lo que te permite navegar sin volver a escribir la contraseña en cada clic.
Esa comodidad tiene un efecto secundario: el navegador adjunta la cookie sin importar qué página inició la petición. Si estás en la web de un atacante y esa página hace que tu navegador envíe un formulario a tu tienda o a tu correo, la petición llega con tu sesión. El servidor ve una petición válida, de un usuario autenticado, y la ejecuta. Eso es el Cross-Site Request Forgery (CSRF): falsificar una petición en nombre de la víctima. En esta lección verás cómo funciona, cuándo es explotable hoy con las protecciones modernas de los navegadores y cómo se corrige.
Practica estas técnicas solo en sistemas propios o en entornos autorizados, como los laboratorios de whoami-labs. Hacer que otra persona ejecute acciones en su cuenta sin saberlo es un delito.
Qué es un CSRF#
Un CSRF ocurre cuando una aplicación acepta una acción que cambia algo (cambiar el correo, transferir puntos, borrar un recurso) basándose solo en la cookie de sesión, sin comprobar que la petición la inició de verdad el usuario desde la propia aplicación.

Hay que entender bien qué puede y qué no puede hacer el atacante:
- Puede hacer que el navegador de la víctima envíe una petición a otro sitio, con sus cookies.
- No puede, en principio, leer la respuesta. La política del mismo origen del navegador se lo impide.
Por eso el CSRF sirve para provocar acciones, no para robar datos. Pero una sola acción bien elegida, como cambiar el correo de la cuenta, puede bastar para quedarse con ella usando después la función de «olvidé mi contraseña».
Por qué ocurre: la causa raíz#
Imagina el endpoint que cambia el correo de una cuenta, en PHP:
// POST /cuenta/email
session_start();
if (empty($_SESSION['usuario_id'])) {
http_response_code(401);
exit;
}
$db->prepare("UPDATE usuarios SET email = ? WHERE id = ?")
->execute([$_POST['email'], $_SESSION['usuario_id']]);
echo "Correo actualizado";
El código comprueba que hay sesión y usa una consulta parametrizada. Parece correcto. Pero solo responde a la pregunta «¿quién eres?» y no a «¿de verdad quisiste hacer esto?». Todo lo que necesita la petición para funcionar es algo que el navegador añade solo: la cookie.
El atacante solo tiene que preparar una página que envíe ese formulario:
<!-- Página del atacante: se envía sola al cargarse -->
<html>
<body>
<form id="f" action="https://tienda.lab/cuenta/email" method="POST">
<input type="hidden" name="email" value="[email protected]">
</form>
<script>document.getElementById("f").submit();</script>
</body>
</html>
Si la víctima tiene la sesión iniciada y la cookie viaja en esa petición, el correo de su cuenta cambia sin que haga clic en nada.
Variantes de CSRF#
| Variante | Cómo se dispara | Qué necesita | Notas |
|---|---|---|---|
| GET con efectos | Una imagen o un enlace hacia la URL de la acción | Que la aplicación cambie datos con GET | La más fácil de explotar y la que menos frena SameSite=Lax |
| Formulario POST | Un formulario oculto que se envía solo | Que la acción acepte un formulario normal | El caso clásico |
| Petición JSON | Un formulario que imita un cuerpo JSON o una petición con fetch | Que el servidor no exija el Content-Type JSON | Si exige JSON real, el navegador hace una comprobación previa (preflight) |
| Login CSRF | Un formulario que inicia sesión con la cuenta del atacante | Que el login no tenga protección | La víctima trabaja sin saberlo en la cuenta del atacante |
Peticiones GET que cambian cosas#
Si una aplicación borra, compra o cambia datos con una URL GET, el CSRF es trivial: basta con que el navegador de la víctima cargue esa URL, por ejemplo como si fuera una imagen.
<img src="https://tienda.lab/carrito/vaciar?confirmar=1" width="1" height="1">
Por eso existe una regla básica de HTTP: GET no debe cambiar el estado del servidor. Es la primera defensa contra el CSRF y muchas aplicaciones la incumplen.
Login CSRF#
Es una variante curiosa: el atacante hace que la víctima inicie sesión en la cuenta del atacante. Si la víctima guarda después su tarjeta o sus búsquedas, quedan en la cuenta del atacante. Por eso también el formulario de inicio de sesión debe estar protegido.
Cuándo es explotable#
Para que un CSRF funcione hoy tienen que darse varias condiciones a la vez:
- Una acción interesante: cambiar el correo o la contraseña, transferir saldo, crear un usuario administrador, cambiar permisos.
- Sesión basada en cookies: si la aplicación usa un token en una cabecera
Authorizationque el JavaScript añade a mano, el navegador no lo envía solo y el CSRF clásico no funciona. - Ningún valor impredecible: si la petición necesita un dato que el atacante no puede conocer, como la contraseña actual o un token anti-CSRF, no puede falsificarla.
- Que la cookie viaje en la petición entre sitios: aquí entra SameSite, que ha cambiado mucho el panorama.
SameSite: la protección del navegador y sus límites#
El atributo SameSite de una cookie le dice al navegador cuándo enviarla en peticiones que vienen de otro sitio:
| Valor | Cuándo se envía la cookie desde otro sitio | Efecto frente a CSRF |
|---|---|---|
| Strict | Nunca | Protege casi todo, pero llegar desde un enlace externo te muestra la página sin sesión |
| Lax | Solo en navegaciones de nivel superior con métodos seguros, como seguir un enlace (GET) | Bloquea los POST entre sitios, no los GET con efectos |
| None | Siempre (exige Secure) | No protege |
Los navegadores basados en Chromium, como Chrome y Edge, tratan como Lax por defecto las cookies que no declaran SameSite. Pero no todos los navegadores aplican ese valor por defecto de la misma forma, así que no confíes en él: declara tú el atributo. Además, Lax tiene límites que conviene conocer:
- GET con efectos: una navegación GET de nivel superior lleva la cookie, así que una acción por GET sigue siendo vulnerable.
- Sitio no es lo mismo que origen: para SameSite,
blog.tienda.labytienda.labson el mismo sitio. Un XSS o una página controlable en un subdominio puede lanzar peticiones que el navegador considera del mismo sitio. - Excepciones temporales: Chrome permite durante un tiempo breve los POST de nivel superior con cookies recién creadas sin SameSite explícito, para no romper algunos inicios de sesión.
Cómo se detecta#
Pruebas manuales#
Revisa cada acción que cambie algo y hazte estas preguntas:
- Captura la petición en Burp. ¿Lleva algún token o valor impredecible en el cuerpo o en una cabecera?
- Si lo lleva, quítalo y reenvía. ¿El servidor la acepta igual?
- Cámbialo por otro valor o por el token de otra sesión. ¿Lo acepta?
- Cambia el método: ¿funciona la misma acción por GET?
- Mira la cookie de sesión: ¿qué valor de SameSite tiene?
- Elimina o cambia la cabecera
OriginyReferer. ¿El servidor lo nota?
| Prueba | Resultado | Qué indica |
|---|---|---|
| Quitar el token | La acción se ejecuta | El token no se valida: vulnerable |
| Token de otra sesión | La acción se ejecuta | El token no está ligado a la sesión |
| Cambiar POST por GET | La acción se ejecuta | Hay un camino por GET que Lax no frena |
| Origin de otro sitio | La acción se ejecuta | No se comprueba el origen |
| Cookie sin SameSite o con None | Viaja en peticiones entre sitios | Depende solo del token |
Herramientas#
- Burp Suite: en la versión Professional, la opción para generar una prueba de concepto de CSRF crea el formulario a partir de una petición capturada. En cualquier versión puedes escribirlo a mano.
- Herramientas de desarrollador del navegador: la pestaña de aplicación muestra el atributo SameSite de cada cookie, y la de red muestra si viaja.
- Revisión de código: busca acciones que cambian datos sin pasar por el middleware de CSRF del framework o rutas excluidas de él.
Explotación paso a paso en un laboratorio#
Todavía no hay un laboratorio de whoami-labs dedicado solo a CSRF, así que este recorrido usa una aplicación de práctica propia. GUESTBOOK te sirve para practicar la parte de sesiones y XSS que se relaciona con este ataque, como verás al final.
Paso 1: elegir la acción y capturarla#
Inicia sesión con una cuenta de prueba, cambia tu correo de forma legítima y captura la petición:
POST /cuenta/email HTTP/1.1
Host: IP_DEL_LAB
Cookie: session=TU_SESION
Content-Type: application/x-www-form-urlencoded
email=nuevo%40correo.lab
No hay ningún token: solo la cookie.
Paso 2: comprobar la cookie#
En la respuesta del login, mira cómo se crea la cookie:
Set-Cookie: session=TU_SESION; Path=/; HttpOnly
No declara SameSite. En un navegador Chromium se trataría como Lax; en un laboratorio puede estar configurada con SameSite=None; Secure para que practiques el caso clásico.
Paso 3: preparar la página del atacante#
En tu máquina de ataque, crea index.html con el formulario autoenviado de la sección anterior, apuntando a http://IP_DEL_LAB/cuenta/email, y sírvelo:
python3 -m http.server 8000
Paso 4: lanzar la prueba con una segunda cuenta#
Con otra cuenta de prueba (la «víctima»), en el mismo navegador, abre http://IP_ATACANTE:8000/. Vuelve después a la aplicación: si el correo de la víctima ahora es el del atacante, el CSRF está confirmado. Si no funcionó, revisa en la pestaña de red si la cookie viajó: si no lo hizo, SameSite te ha frenado y tendrás que buscar un camino por GET o en el mismo sitio.
Paso 5: la relación con XSS#
Aquí entra GUESTBOOK. Un XSS almacenado en el propio sitio anula todas las defensas contra CSRF: el código del atacante ya corre en el mismo origen, puede leer el token de la página y enviar la petición como si fuera la aplicación. Por eso un XSS se considera más grave que un CSRF, y por eso las defensas contra CSRF no sirven si el sitio tiene XSS.
Fíjate en que el CSRF solo necesita que el servidor no pregunte nada que el atacante no sepa. Un único valor impredecible y ligado a la sesión en cada acción rompe el ataque.
Qué puede lograr un atacante#
| Impacto | Ejemplo real |
|---|---|
| Toma de la cuenta | Cambiar el correo y luego restablecer la contraseña |
| Acciones financieras | Transferir saldo, comprar o canjear puntos |
| Escalada en paneles | Hacer que un administrador cree un usuario con privilegios |
| Cambios de configuración | Desactivar la autenticación en dos pasos o añadir una dirección de reenvío |
| Publicación en nombre de la víctima | Mensajes, comentarios o reseñas |
| Login CSRF | La víctima guarda datos en la cuenta del atacante |
Cómo se corrige#

La regla de oro: toda acción que cambie algo debe exigir una prueba de que la pidió la propia aplicación, no solo la cookie.
Tokens anti-CSRF (patrón del token sincronizado)#
El servidor genera un valor aleatorio, lo guarda en la sesión y lo incluye en cada formulario. Al recibir una acción, comprueba que el token enviado coincide con el de la sesión. El atacante no puede leerlo desde otro sitio, así que no puede falsificar la petición.
// Al crear la sesión
$_SESSION['csrf'] = bin2hex(random_bytes(32));
// En el formulario (el valor va escapado para HTML)
// <input type="hidden" name="csrf" value="...">
// Al procesar la acción
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
http_response_code(403);
exit('Token CSRF no válido');
}
hash_equals compara en tiempo constante. En aplicaciones de una sola página, el token suele enviarse en una cabecera personalizada, como X-CSRF-Token.
Usa la protección de tu framework#
Django, Laravel, Rails, Symfony, ASP.NET Core y Spring Security traen protección contra CSRF. Lo importante es no desactivarla en rutas «por comodidad» y comprobar que cubre todas las acciones.
Cookies con SameSite declarado#
Set-Cookie: session=...; Path=/; Secure; HttpOnly; SameSite=Lax
Usa Lax como mínimo, o Strict para cookies de aplicaciones sensibles como un panel de administración. Es una capa adicional muy eficaz, pero no sustituye al token.
Comprobar Origin y Referer#
En acciones que cambian estado, verifica que la cabecera Origin (o, si falta, Referer) corresponda a tu propio dominio. Los navegadores modernos también envían cabeceras Sec-Fetch-Site, que indican si la petición viene del mismo origen, del mismo sitio o de otro.
GET nunca cambia el estado#
Las acciones que modifican datos van por POST, PUT, PATCH o DELETE. Esto, junto con SameSite=Lax, cierra muchos casos.
Confirmación en acciones críticas#
Para cambiar la contraseña o el correo, pide la contraseña actual o un segundo factor. Es un valor que el atacante no conoce.
Y no olvides CORS#
CORS no protege contra CSRF: decide quién puede leer respuestas, no quién puede enviar peticiones. Pero una configuración mala de CORS (permitir cualquier origen con credenciales) sí empeora las cosas, porque deja que otro sitio lea la respuesta y, con ella, el token anti-CSRF.
Errores comunes al defenderse#
- Generar el token pero no validarlo, o validarlo solo si viene en la petición.
- Tokens no ligados a la sesión. Si cualquier token válido sirve para cualquier usuario, el atacante usa el suyo.
- Confiar solo en SameSite. No protege las acciones por GET ni los ataques desde subdominios, y el valor por defecto no es igual en todos los navegadores.
- Confiar en que la petición es JSON. Si el servidor también acepta formularios o no comprueba el Content-Type, el CSRF vuelve.
- Excluir rutas del middleware de CSRF para que funcione una integración y olvidarse de ellas.
- Ignorar el XSS. Un solo XSS en el sitio anula todas las defensas contra CSRF.
Preguntas frecuentes#
¿Qué diferencia hay entre CSRF y XSS?#
En el XSS el atacante ejecuta su código dentro del sitio vulnerable y puede leer y hacer casi todo. En el CSRF el atacante está fuera: solo puede hacer que el navegador envíe peticiones, sin leer las respuestas.
¿Si uso SameSite=Lax ya no necesito tokens?#
Reduce mucho el riesgo, pero no cubre las acciones por GET, los ataques desde subdominios del mismo sitio ni navegadores que no apliquen el valor por defecto. Los tokens siguen siendo la defensa principal.
¿Las APIs con JWT en una cabecera son vulnerables a CSRF?#
Si el token viaja en la cabecera Authorization y lo añade tu JavaScript, el navegador no lo envía solo, así que el CSRF clásico no funciona. Si guardas el JWT en una cookie, vuelves a necesitar protección.
¿CORS protege contra CSRF?#
No. CORS controla qué orígenes pueden leer respuestas. Los formularios entre sitios se envían igual. Una mala configuración de CORS, en cambio, sí puede ayudar a un atacante a leer el token.
¿Es legal probar un CSRF?#
Solo en sistemas propios, en laboratorios o en programas de bug bounty que lo permitan, y siempre con cuentas de prueba. Provocar acciones en la cuenta de otra persona sin su permiso es un delito.
Resumen#
- Un CSRF ocurre cuando una acción se autoriza solo con la cookie, que el navegador envía automáticamente aunque la petición la inicie otro sitio.
- El atacante provoca acciones, pero normalmente no puede leer las respuestas.
- SameSite=Lax frena los POST entre sitios, pero no los GET con efectos ni los ataques desde el mismo sitio.
- Se corrige con tokens anti-CSRF ligados a la sesión, SameSite declarado, comprobación de Origin y GET sin efectos.
- Un XSS en el sitio anula estas defensas, y una mala configuración de CORS las debilita.