🍪 Cookies y sus Flags de seguridad
Cómo funcionan las cookies de sesión, qué flags las protegen y qué ocurre cuando no están bien configuradas.
¿Qué es una cookie?
Una cookie es un pequeño fragmento de datos que el servidor envía al navegador mediante la cabecera
Set-Cookie. El navegador la almacena y la reenvía automáticamente en cada petición al mismo dominio.
Su uso más importante en seguridad: mantener sesiones de usuario. Después de hacer login, el servidor te entrega un token de sesión en una cookie; en cada petición el navegador lo envía y el servidor sabe quién eres.
Formato completo
nombre=valor
El dato en sí. Para sesiones suele ser un token opaco largo y aleatorio, nunca información sensible en claro.
Domain y Path
A qué dominio y ruta se envía la cookie. Domain=.ejemplo.com incluye subdominios.
Expires / Max-Age
Cuándo expira. Sin este atributo es una session cookie: desaparece al cerrar el navegador.
HttpOnly / Secure / SameSite
Los tres flags de seguridad. Cada uno protege contra un vector de ataque diferente.
Ver tus cookies en el navegador
document.cookie.
lab_session sin la flag HttpOnly.
Podrás leerla con document.cookie y exfiltrarla mediante XSS. Así es como funciona el robo de sesión.
Flag HttpOnly — Contra el robo de cookies por XSS
La flag HttpOnly instruye al navegador para que no exponga la cookie a JavaScript.
document.cookie simplemente no la incluirá en su resultado.
Cómo configurarla
// ❌ Vulnerable — cookie accesible desde JS
res.cookie('session', token);
// ✅ Seguro — JavaScript no puede leerla
res.cookie('session', token, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
Flag Secure — Contra el robo en tránsito
La flag Secure indica al navegador que solo envíe la cookie a través de HTTPS,
nunca por HTTP plano. Protege contra ataques de interceptación en la red (Man-in-the-Middle).
Secure con la cabecera Strict-Transport-Security (HSTS)
garantiza que el navegador nunca haga peticiones HTTP al servidor, eliminando cualquier vector
de degradación de protocolo (SSL stripping).
Set-Cookie: session=abc123; Secure; HttpOnly
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Flag SameSite — Contra CSRF
La flag SameSite controla si el navegador envía la cookie en peticiones cross-site
(originadas desde un dominio diferente). Es la defensa principal contra ataques CSRF.
Los tres valores
| Valor | Petición misma web | Navegación GET cross-site | POST / iframe cross-site | Protege contra CSRF |
|---|---|---|---|---|
None |
✅ Sí | ✅ Sí | ✅ Sí | ❌ No |
Lax Default |
✅ Sí | ⚠️ Solo top-level GET | ❌ No | ⚠️ Parcial |
Strict |
✅ Sí | ❌ No | ❌ No | ✅ Sí |
SameSite=None
La cookie se envía en todos los contextos, incluyendo peticiones desde otros dominios. Necesario para integraciones (pagos, widgets), pero peligroso como valor por defecto. Requiere también la flag Secure.
SameSite=Lax
Comportamiento por defecto en navegadores modernos. La cookie se envía en navegación de nivel superior (clics en enlaces) pero no en peticiones POST, iframes o carga de recursos desde otros sitios.
SameSite=Strict
La cookie nunca se envía en peticiones cross-site, ni siquiera al hacer clic en un enlace desde otro sitio. Máxima protección contra CSRF, pero puede afectar la usabilidad (redirecciones post-login).
Recomendación
Para cookies de sesión: usa SameSite=Strict si tu app no necesita funcionar desde enlaces externos, o SameSite=Lax como mínimo. Combínalo con tokens anti-CSRF para defensa en profundidad.
Ejemplo visual: SameSite=None vs Strict
CSRF — Cross-Site Request Forgery
¿Qué es CSRF?
CSRF (Cross-Site Request Forgery, o falsificación de petición entre sitios) es un ataque que fuerza al navegador de la víctima a ejecutar una petición autenticada a una aplicación web en la que está logueada, sin su conocimiento ni consentimiento.
La clave es que el navegador envía las cookies automáticamente. El atacante no necesita robar la cookie: solo necesita que el navegador de la víctima haga una petición al sitio objetivo, y el navegador la incluirá solo.
Flujo de ataque CSRF clásico
Set-Cookie: session=TOKEN<form action="https://banco.com/transferir" method="POST" id="f">
<input type="hidden" name="destino" value="atacante123">
<input type="hidden" name="cantidad" value="5000">
</form>
<script>document.getElementById('f').submit();</script>
session=TOKENDefensas contra CSRF
1. Token anti-CSRF
El servidor genera un token aleatorio y lo incluye en cada formulario como campo oculto. En cada POST verifica que el token coincide. El atacante no puede conocer el token.
<input type="hidden"
name="_csrf"
value="X7k2mN9pQr...">
2. SameSite=Strict
El flag SameSite impide que el navegador incluya la cookie en peticiones cross-site. Sin cookie, la petición no está autenticada y el servidor la rechaza.
3. Verificar Origin / Referer
El servidor comprueba que la cabecera Origin o Referer de la petición coincide con el dominio esperado. Si viene de evil.com, se rechaza.
4. Double Submit Cookie
El servidor establece una cookie con un valor aleatorio y exige que ese mismo valor se envíe también como parámetro de formulario. El atacante no puede leer la cookie para duplicarla.
CSRF + XSS = combo letal
Si hay XSS en el mismo dominio, un atacante puede:
- Leer el token anti-CSRF del DOM con
document.querySelector('[name=_csrf]').value - Incluirlo en la petición forjada junto con la cookie de sesión
- Bypassear todas las protecciones CSRF simultáneamente
Por eso se dice que XSS invalida CSRF: si hay XSS en tu dominio, los tokens anti-CSRF no sirven de nada.