Una cuenta puede tener una contraseña robusta y MFA, y aun así quedar comprometida si alguien obtiene una sesión ya autenticada. La explicación está en lo que ocurre después del login: muchas aplicaciones dejan de pedir los factores de autenticación y pasan a reconocer una credencial que el navegador envía en cada petición.
El 4 de noviembre de 2026, BleepingComputer recogió el anuncio del despliegue progresivo de Device Bound Session Credentials (DBSC) para los accesos a vercel.com. El caso afecta a las sesiones del panel de Vercel; no significa que todas las aplicaciones alojadas en esa plataforma incorporen automáticamente esta protección. Cobertura del anuncio.
La idea útil más allá de la noticia es esta: ¿cómo limitar el valor de una cookie robada si el atacante intenta utilizarla desde otro equipo? Para entender la respuesta, primero hay que separar autenticación, sesión y prueba de posesión.
Una sesión conserva el resultado de una autenticación
Imagina una aplicación de documentos. Inicias sesión, superas el segundo factor y abres tres páginas. Volver a pedir todas las credenciales en cada petición sería incómodo. El servidor establece una sesión que permite continuar la interacción durante un tiempo limitado.
En un diseño habitual, el navegador recibe una cookie con un identificador aleatorio. El servidor mantiene la asociación entre ese identificador y la cuenta autenticada. La cookie no necesita contener la contraseña, los documentos ni los permisos completos.
Si ese identificador funciona como una credencial bearer, poseerlo y presentarlo puede bastar para usar la sesión. No prueba por sí solo que la petición procede del dispositivo que inició sesión. NIST distingue precisamente entre presentar el secreto de sesión y demostrar criptográficamente su posesión. NIST SP 800-63B-4: gestión de sesiones.
Cookie, access token y refresh token no son sinónimos
| Elemento | Función | Dónde se presenta normalmente |
|---|---|---|
| Cookie usada para la sesión | Transportar la credencial que reconoce la aplicación web | El navegador la adjunta según dominio, ruta y políticas de cookies |
| Access token | Autorizar acceso a determinados recursos de una API | Al servidor de recursos |
| Refresh token | Solicitar nuevos access tokens, cuando el flujo lo contempla | Al servidor de autorización |
Una cookie es un mecanismo de almacenamiento y transporte; un token tiene un papel dentro de un protocolo. Una aplicación puede guardar un token en una cookie, pero eso no vuelve equivalentes los conceptos. Tampoco todos los tokens son JWT. RFC 6749, secciones 1.4 y 1.5.
Además, cookie de sesión tiene otro significado en HTTP: una cookie sin Expires ni Max-Age, cuya duración depende de la sesión del navegador. Una cookie que mantiene un login puede tener expiración explícita y ser persistente. Cerrar el navegador no es un mecanismo fiable de revocación en el servidor. MDN: Set-Cookie.
Si necesitas profundizar en los roles de los tokens, el artículo sobre OAuth, OpenID Connect y PKCE desarrolla ese flujo; aquí nos centraremos en la continuidad del login.
Session hijacking: reutilizar una sesión que ya existe
Session hijacking, o secuestro de sesión, consiste en aprovechar una sesión válida para actuar como su titular. No exige necesariamente adivinar una contraseña ni romper el algoritmo que firma una cookie.
Un infostealer es malware orientado a extraer información, incluidas credenciales y datos del navegador. Con acceso suficiente al equipo puede conseguir material de sesión desde almacenamiento o memoria. También puede haber filtraciones por registros que capturan cabeceras completas o por fallos de la aplicación. El método concreto depende del navegador, el sistema y los privilegios del atacante.
El servidor puede recibir una cookie auténtica, vigente y correctamente firmada, pero presentada por otra persona. La integridad de una credencial no demuestra quién la está utilizando. OWASP trata el robo de cookies como un problema que requiere prevención y detección contextual. Cookie Theft Mitigation Cheat Sheet.
Por qué MFA no detiene necesariamente el replay
MFA protege la autenticación que crea la sesión. Si las siguientes peticiones solo requieren la cookie, reutilizarla puede evitar que se vuelva a ejecutar ese desafío. Esto no vuelve inútil a MFA: muestra que el inicio de sesión y la continuidad de la sesión necesitan controles diferentes.
Por ejemplo, descargar un documento y cambiar el correo de recuperación no tienen el mismo riesgo. Para operaciones sensibles puede corresponder una autenticación reciente o una verificación adicional, aunque exista una sesión válida. OWASP: reautenticación para funciones sensibles.
Las passkeys y WebAuthn pueden reforzar el login; eso no convierte automáticamente la cookie posterior en una credencial vinculada al dispositivo.
Qué protegen HttpOnly, Secure y SameSite
Estos atributos siguen siendo necesarios, pero cada uno limita una vía distinta:
| Atributo | Protección | Límite importante |
|---|---|---|
HttpOnly | Impide leer la cookie mediante APIs como document.cookie | Un XSS puede seguir enviando peticiones autenticadas desde la página |
Secure | Restringe su envío a conexiones HTTPS | No impide leerla desde un equipo comprometido |
SameSite | Limita el envío en contextos entre sitios | No vincula la cookie a una clave ni sustituye todos los controles CSRF |
El prefijo __Host- exige, en navegadores compatibles, Secure, Path=/ y ausencia de Domain. Ayuda a restringir el alcance al host que la establece. No cifra la cookie ni impide reutilizarla si se roba. MDN: atributos y prefijos de cookies.
CSRF significa Cross-Site Request Forgery: provocar que el navegador de una víctima envíe una operación autenticada que no pretendía realizar. SameSite=Lax permite ciertos accesos entre sitios, y same-site no equivale a same-origin. Las operaciones que cambian estado necesitan una defensa adecuada, como tokens CSRF o validación estricta del origen, según el diseño. No deberían ejecutarse mediante GET. OWASP: prevención de CSRF.
Fijación, renovación y revocación de sesiones
En la fijación de sesión, el atacante intenta que la víctima autentique usando un identificador que él ya conoce. La defensa es generar un nuevo identificador después de autenticar y al cambiar el nivel de privilegio, invalidando el anterior.
También conviene distinguir tres operaciones:
- Extender la vigencia: actualizar un plazo mientras continúa la actividad.
- Rotar el identificador: sustituir la credencial anterior por otra.
- Revocar: dejar de aceptar la sesión en el servidor.
Una expiración por inactividad puede mantenerse viva si el atacante genera tráfico. Un límite absoluto impone un máximo aunque haya actividad. Ambos deben verificarse en el servidor: Max-Age instruye al navegador, pero no impide que otro cliente reenvíe un valor antiguo.
Cerrar sesión debe invalidar el registro o mecanismo equivalente del servidor y limpiar la cookie. Borrar únicamente la copia del navegador no inutiliza las copias que ya se filtraron. OWASP: gestión de sesiones.
En una investigación real, decide además si debes revocar todas las sesiones de la cuenta, sus refresh tokens o credenciales asociadas. Cambiar una contraseña no garantiza ese efecto si tu implementación no lo contempla.
DBSC: renovar la sesión exige demostrar posesión de una clave
Device Bound Session Credentials añade una relación criptográfica entre una sesión y una clave administrada por el navegador. No consiste en agregar un identificador de dispositivo a una cookie ni en comparar la dirección IP.
El navegador crea un par de claves. El servidor asocia la pública con la sesión; la privada se protege mediante los mecanismos disponibles en la plataforma. Chrome documenta el uso de un TPM, Trusted Platform Module, en Windows: un componente de seguridad que puede mantener claves y realizar operaciones sin entregar su material privado a la aplicación. Chrome: presentación de DBSC en Windows.
La prueba de posesión, o proof-of-possession, demuestra que el cliente puede usar esa clave privada. El servidor verifica una firma; no recibe la clave privada ni necesita conocer la huella biométrica de la persona.
El flujo conceptual
- Tras autenticar, el servidor anuncia el registro de una sesión DBSC.
- El navegador registra la clave pública; el servidor devuelve la configuración de la sesión y cookies de corta duración.
- La aplicación sigue reconociendo esas cookies en sus peticiones habituales.
- Cuando falta una cookie necesaria o ha caducado, el navegador puede pausar la petición e iniciar su renovación.
- El servidor solicita una prueba, verifica la respuesta firmada y emite nuevas cookies si la sesión continúa autorizada.
La guía de Chrome describe endpoints de registro y renovación y cabeceras como Secure-Session-Registration y Secure-Session-Challenge. No basta con activar Secure en una cookie para implementar DBSC. Chrome: guía de integración.
No se pudo mostrar el diagrama. Puedes consultar su definición.
sequenceDiagram
participant B as Navegador
participant K as Clave protegida del dispositivo
participant S as Servidor
Note over B,S: Sesión DBSC ya registrada tras autenticar
B->>S: Petición con cookie de corta duración
S-->>B: Respuesta autorizada
Note over B: Falta la cookie requerida o ha caducado
B->>S: Solicitar renovación
S-->>B: Desafío para probar posesión
B->>K: Firmar prueba vinculada al desafío
K-->>B: Firma, sin entregar la clave privada
B->>S: Prueba firmada
S->>S: Validar prueba y vigencia de la sesión
S-->>B: Nueva cookie de corta duración
B->>S: Reanudar petición pendiente
El diagrama muestra una renovación correcta con desafío; omite los errores y detalles de formato. La expiración que protege frente a replay tiene que ser efectiva en el servidor, no solo en el almacenamiento del navegador.
Una copia puede conservar una ventana de uso
DBSC no firma necesariamente cada petición de la aplicación. Una cookie breve que el servidor todavía acepta puede ser reutilizada durante su vigencia. La diferencia aparece al renovarla: copiar el valor no proporciona la clave necesaria para continuar obteniendo credenciales válidas desde otro equipo.
La duración de esa ventana depende del intervalo y la política del servicio. No existe una promesa general de «cookie robada instantáneamente inútil». El documento explicativo describe explícitamente este límite temporal. DBSC explainer: objetivos y límites.
Qué queda fuera del alcance de DBSC
Una clave difícil de extraer no garantiza un equipo sano. El borrador del protocolo aclara que DBSC no impide el abuso mientras el atacante permanece en el dispositivo: el malware podría utilizar la capacidad de firma local o actuar a través del navegador. Tampoco garantiza protección si el registro de la sesión ocurre bajo control del atacante.
DBSC no acredita el modelo del equipo ni su estado de seguridad. Además, busca evitar un identificador estable para rastrear a la persona entre sitios; las claves se asocian a sesiones. Borrador de DBSC: seguridad y privacidad.
Por tanto, no lo utilizaría como sustituto de:
- Prevenir XSS y controlar código de terceros.
- Proteger el endpoint y eliminar malware antes de restablecer el acceso.
- Comprobar permisos por recurso en el backend.
- Revocar sesiones y exigir reautenticación ante incidentes.
Esos controles responden a problemas diferentes. Si tu API entrega un documento sin comprobar su propietario, vincular la sesión al equipo no corrige la autorización. El artículo sobre controles esenciales de una API desarrolla esa otra parte del diseño.
Cookies tradicionales, DBSC y DPoP
DPoP, Demonstrating Proof of Possession, es un mecanismo de OAuth para restringir el uso de tokens a quien puede probar posesión de una clave. Sus pruebas están vinculadas a la petición mediante datos como método y URI; cuando corresponde, incluyen el hash del access token. RFC 9449.
| Mecanismo | Credencial habitual | Dónde interviene la prueba |
|---|---|---|
| Sesión bearer tradicional | Cookie con identificador o token | Presentar la credencial puede bastar |
| DBSC | Cookies de corta duración y una sesión ligada a una clave | En el registro y la renovación, según el protocolo |
| DPoP | Tokens OAuth vinculados a una clave | En solicitudes de tokens y accesos protegidos mediante DPoP |
DPoP no obliga a que la clave resida en un TPM. La resistencia a su extracción depende del cliente y su almacenamiento. Tampoco resuelve por sí solo la ejecución de código malicioso en ese cliente. No es un nuevo atributo de cookie ni un reemplazo intercambiable de DBSC. RFC 9449: código no confiable en el cliente.
Compatibilidad y degradación de protección
La cobertura del anuncio de Vercel describía un despliegue gradual en Chrome para Windows y macOS. Es un dato de noviembre de 2026, no una garantía para todo navegador, equipo o cuenta. Antes de adoptarlo, verifica soporte de la plataforma, disponibilidad de almacenamiento protegido y comportamiento de tu proveedor. Anuncio recogido por BleepingComputer.
La especificación consultada es un Editor’s Draft, trabajo en curso; no debe presentarse como una recomendación W3C final. Estado del documento DBSC.
También hay que diseñar los fallos de renovación. Chrome documenta casos como indisponibilidad del endpoint, errores del TPM y restricciones de cookies. Si se conserva una cookie de larga duración para fallback, decide qué permite realmente: aceptar con ella todas las operaciones sensibles puede anular la protección buscada. Chrome: caveats y fallback.
Ejemplo en Node.js: proteger el ciclo de vida de una sesión
El siguiente módulo usa Express 5 y express-session. Implementa sesiones tradicionales del lado del servidor; no implementa DBSC ni DPoP. Su objetivo es separar la autenticación, la creación de la sesión y su invalidación.
El contrato de autenticar es importante: recibe la entrada del login, valida las credenciales y los factores que correspondan, y devuelve una identidad confiable o null. No puede limitarse a devolver un userId recibido del navegador. Aquí lo inyectamos para integrar tu mecanismo de autenticación sin inventar otro sistema de contraseñas.
Para preparar el módulo con Node.js 22 o superior:
mkdir ejemplo-sesiones
cd ejemplo-sesiones
pnpm init
pnpm add express@5.3.0 express-session@1.19.0Guarda esto como sesiones.mjs:
import express from 'express';
import session from 'express-session';
import { promisify } from 'node:util';
const INACTIVIDAD = 15 * 60 * 1000;
const DURACION_MAXIMA = 8 * 60 * 60 * 1000;
const NOMBRE = '__Host-session';
const COOKIE = { httpOnly: true, secure: true, sameSite: 'lax', path: '/' };
export function crearApp({ store, secret, origin, autenticar }) {
if (!store || typeof secret !== 'string' || Buffer.byteLength(secret) < 32) {
throw new Error('Falta un almacén o un secreto de sesión adecuado');
}
if (new URL(origin).protocol !== 'https:' || new URL(origin).origin !== origin) {
throw new Error('El origen debe ser HTTPS y no incluir rutas');
}
const app = express();
app.disable('x-powered-by');
app.use((req, res, next) => {
res.set('Cache-Control', 'no-store');
if (!['GET', 'HEAD', 'OPTIONS'].includes(req.method) && req.get('Origin') !== origin) {
return res.sendStatus(403);
}
next();
});
app.use(express.json({ limit: '8kb' }));
app.use(session({
name: NOMBRE,
secret,
store,
resave: false,
saveUninitialized: false,
rolling: true,
cookie: { ...COOKIE, maxAge: INACTIVIDAD },
}));
app.post('/login', async (req, res) => {
// El adaptador valida credenciales y MFA antes de devolver una identidad.
const usuario = await autenticar(req.body);
if (!usuario || typeof usuario.id !== 'string' || !usuario.id) {
return res.sendStatus(401);
}
await promisify(req.session.regenerate).call(req.session);
req.session.userId = usuario.id;
req.session.createdAt = Date.now();
req.session.lastSeen = req.session.createdAt;
await promisify(req.session.save).call(req.session);
res.sendStatus(204);
});
app.use(async (req, res, next) => {
const sesion = req.session;
if (!sesion.userId) {
return res.sendStatus(401);
}
const ahora = Date.now();
const expirada = !Number.isFinite(sesion.createdAt)
|| !Number.isFinite(sesion.lastSeen)
|| ahora - sesion.createdAt >= DURACION_MAXIMA
|| ahora - sesion.lastSeen >= INACTIVIDAD;
if (expirada) {
await promisify(sesion.destroy).call(sesion);
res.clearCookie(NOMBRE, COOKIE);
return res.sendStatus(401);
}
sesion.lastSeen = ahora;
next();
});
app.get('/cuenta', (req, res) => {
res.json({ userId: req.session.userId });
});
app.post('/logout', async (req, res) => {
await promisify(req.session.destroy).call(req.session);
res.clearCookie(NOMBRE, COOKIE);
res.sendStatus(204);
});
app.use((error, req, res, next) => {
if (res.headersSent) return next(error);
const estado = error.status === 400 || error.status === 413 ? error.status : 500;
res.status(estado).json({ error: 'No se pudo completar la solicitud' });
});
return app;
}La API se ha contrastado con la documentación oficial de express-session. regenerate() cambia el ID; save() confirma el guardado antes de responder; destroy() elimina la sesión. rolling: true renueva el vencimiento de la cookie, no cambia su identificador ni equivale a una prueba de posesión.
Decisiones que debes adaptar
- Almacén: se exige uno explícito. En producción utiliza un store compartido con TTL y semántica adecuada para sesiones. El
MemoryStorede la biblioteca sirve para pruebas, no para múltiples instancias ni persistencia fiable. - Secreto:
secretfirma el identificador de la cookie. Genera al menos 32 bytes aleatorios y suminístralos mediante configuración protegida. El chequeo de longitud del ejemplo no verifica entropía; tampoco cifra el contenido de la sesión. - Transporte: sirve la aplicación mediante HTTPS. Si TLS termina en un proxy, configura
trust proxypara tu topología real y restringe el acceso directo al backend; no confíes indiscriminadamente en cabeceras reenviadas. - Tiempo: 15 minutos de inactividad y 8 horas de duración máxima son decisiones didácticas, no valores universales. Ajusta el riesgo y la experiencia de uso.
Para este ejemplo de navegador y mismo origen, se rechazan operaciones de escritura sin un Origin exactamente igual al configurado, incluso si falta o vale null. No uses coincidencias parciales de dominio. Flujos OAuth, clientes no navegador y formularios heredados pueden necesitar una estrategia distinta, como tokens CSRF. El control de origen no sustituye autenticación: un cliente ajeno al navegador puede construir esa cabecera. OWASP: verificación del origen.
El módulo no incluye limitación de intentos de login, autorización de recursos, un inventario de sesiones por usuario ni resolución de carreras en el store. Antes de integrarlo en producción, completa esas responsabilidades. Una revocación también debe contemplar peticiones concurrentes ya autorizadas: no puede deshacer operaciones que ya se ejecutaron.
Qué comprobé del ejemplo
Probé el módulo mediante HTTPS local, con identidad ficticia y un almacén en memoria exclusivo de la prueba. Las comprobaciones verificaron:
- Sin identidad validada, no se crea una sesión autenticada.
- La cookie incluye
Secure,HttpOnly,SameSite=LaxyPath=/, sinDomain. - Volver a autenticar cambia el identificador e invalida el anterior.
- Un origen ajeno no puede ejecutar el logout; el logout válido revoca la sesión.
- El servidor rechaza sesiones que superan la inactividad o duración absoluta aunque se reenvíe la cookie.
No se probaron DBSC, hardware seguro ni servicios de terceros. Para repetir estas comprobaciones en tu integración, utiliza un usuario de prueba y un servidor HTTPS local, conserva temporalmente la cookie anterior en el cliente de pruebas y verifica los códigos 200, 401 y 403 correspondientes. No registres cookies reales en logs ni capturas.
Diseñar qué ocurre cuando la sesión deja de ser confiable
Un diseño práctico necesita responder a tres preguntas: qué permite hacer la sesión actual, cuánto tiempo puede continuar y cómo se corta su acceso cuando cambia el riesgo.
Las señales de cambios de entorno ayudan a investigar, pero una IP o un User-Agent no constituyen una prueba criptográfica: pueden variar legítimamente o ser imitados. Úsalos con contexto para reducir falsos positivos. OWASP: detección del robo de cookies.
Ante sospecha de robo, combina revocación, revisión de actividad, recuperación desde un dispositivo confiable y nueva autenticación. Una sesión activa no debería bastar para cambiar todos los factores de recuperación sin controles adicionales.
DBSC puede reducir el valor de extraer una cookie y venderla o reutilizarla desde otra máquina. Su aporte depende de la clave protegida, la renovación y la política del servidor. Los atributos de cookies, la autenticación fuerte y la autorización siguen teniendo su propia responsabilidad.
Referencias
- BleepingComputer: despliegue de DBSC en Vercel, 4 de noviembre de 2026.
- Chrome: Device Bound Session Credentials, guía de integración.
- Chrome: presentación de DBSC en Windows.
- W3C Web Application Security: borrador de DBSC.
- DBSC: documento explicativo y modelo de amenazas.
- RFC 9449: OAuth 2.0 Demonstrating Proof of Possession.
- RFC 6749: access tokens y refresh tokens.
- NIST SP 800-63B-4: gestión de sesiones.
- OWASP: Session Management Cheat Sheet.
- OWASP: Cookie Theft Mitigation Cheat Sheet.
- OWASP: Authentication Cheat Sheet.
- OWASP: Cross-Site Request Forgery Prevention Cheat Sheet.
- MDN: Set-Cookie.
- Express: express-session.
