Un navegador manda tus cookies con cualquier petición a tu dominio, sea quien sea el que la haya provocado. Así, una página de otro sitio puede contener un formulario que envía al tuyo, y si tu visitante tiene la sesión abierta, llega autenticada y parece del todo legítima en tus registros.
Lo que puede lograr
- Cambiar una dirección de correo y luego pedir un restablecimiento de contraseña a la nueva.
- Hacer un pedido, transferir algo, borrar algo.
- Cualquier cosa que tu sitio haga con un POST y una sesión.
El token
Un valor aleatorio guardado en la sesión y repetido en el formulario. Una página de otro sitio no puede leer tu sesión y, por tanto, no puede conocer el valor.
// when rendering the form
$_SESSION["csrf"] = $_SESSION["csrf"] ?? bin2hex(random_bytes(32));
?>
<input type="hidden" name="csrf" value="<?= htmlspecialchars($_SESSION['csrf']) ?>">
// when handling it
if (!hash_equals($_SESSION["csrf"] ?? "", $_POST["csrf"] ?? "")) {
http_response_code(400); exit;
}
Compara con hash_equals, no con ==. Una comparación normal se detiene en el primer carácter distinto, y el tiempo que tarda revela cuánto del token era correcto.
Y el atributo de la cookie
session.cookie_samesite = Lax
SameSite=Lax le dice al navegador que no envíe la cookie en un POST entre sitios, lo que detiene esta clase de ataque antes de que tu código la vea. Es una segunda capa, no un sustituto: solo protege donde el navegador es reciente y se porta bien.
Un GET no debe cambiar nada
Un enlace que borra algo es un CSRF esperando a ocurrir, y peor aún: un rastreador o una vista previa de enlace lo dispararán por su cuenta. Todo lo que cambie el estado va por POST y con token.
Cualquier framework trae esto incorporado y activado. Si lo desactivaste en un punto de acceso para que algo funcionara, ese punto es justamente el que hay que mirar.