Construimos un servidor de identidad —Bachué— que autentica con contraseña, segundo factor y sesión por cookie. Las cookies llevan SameSite=Lax, y dábamos por sentado lo que "todo el mundo sabe": que SameSite=Lax protege del CSRF.
No nos fiamos de nuestra propia revisión. El autor de un código es el peor situado para auditarlo: lo que pensó ya lo siente verificado. Así que le pedimos a tres modelos distintos que lo atacaran, cada uno por su cuenta, con un solo encargo: encuentra el defecto más grave; piensa como quien quiere entrar.
Una falsa alarma, y luego la de verdad
El primer modelo gritó "¡CSRF!" con la mecánica equivocada: creía que SameSite=Lax deja pasar un POST cross-site. No lo deja —Lax no envía la cookie en un POST de otro sitio—. Un segundo modelo lo refutó. Hasta ahí, ruido.
El tercero encontró el real, y es sutil: el login-CSRF, que SameSite no cubre.
Por qué Lax protege el logout pero no el login
SameSite=Lax protege los endpoints que necesitan la cookie de la víctima: cerrar sesión, cambiar ajustes. Un POST desde el sitio del atacante llega sin esa cookie, el servidor no ve sesión, y no pasa nada.
Pero /entrar y /registrarse son distintos: no necesitan la cookie de la víctima. El atacante pone sus propias credenciales en el formulario, y lo único que hace el servidor es fijar una cookie nueva en la respuesta. Y un Set-Cookie de primera parte se honra siempre, venga la petición de donde venga. Resultado: la víctima queda navegando autenticada como el atacante, sin saberlo —y todo lo que teclee después vive en la cuenta del atacante—. SameSite no tiene ningún efecto sobre esto.
Afirmar no es demostrar
"SameSite cubre el CSRF" era una afirmación, y como toda frontera de seguridad, parecía aguantar hasta que alguien la cruzó. La demostración —tres adversarios independientes— costó minutos y encontró lo que una sola mirada, incluida la nuestra, habría dado por bueno.
El arreglo es una línea de disciplina: todo POST que cambie estado —también los que aún no exigen sesión— comprueba que la petición es de primera parte (Sec-Fetch-Site) antes de procesarla. Como regla global, no como parche por endpoint que el próximo olvide.
La lección vale más que el bug: no basta con revisar tu propio código de seguridad. Ponle adversarios independientes a demostrar que la frontera aguanta. Si solo la afirmas, un día no aguanta —callada, en el punto donde nadie volvió a mirar—.
Xiliux