A07 Authentication Failures
El login es la puerta de entrada. Los intentos ilimitados, las contraseñas reutilizadas, las sesiones que nunca expiran y los tokens que no se verifican de verdad dejan que un atacante entre como un usuario real.
Qué es
Los fallos de autenticación son debilidades al confirmar la identidad de un usuario o al gestionar la sesión que sigue: permitir y fuerza bruta, aceptar contraseñas débiles o filtradas, exponer ids de sesión, omitir o verificar mal los tokens, u ofrecer un flujo de recuperación débil.
El :2025 lo lista como A07 con el nombre Authentication Failures.
Cómo funciona el ataque
- Credential stuffing: listas de pares de correo y contraseña filtrados de otros sitios se reutilizan a escala contra tu login.
- Fuerza bruta y password spraying: unas pocas contraseñas comunes en muchas cuentas, por debajo de cualquier bloqueo ingenuo.
- Manejo débil de JWT: el servidor acepta alg: none, nunca verifica la firma ni la expiración, o usa un secreto fácil de adivinar.
- y robo de sesión: el id de sesión no se rota después del login, viaja en la URL, o la cookie no tiene HttpOnly ni Secure.
- Bypass de la recuperación de cuenta: enlaces de restablecimiento que nunca expiran o preguntas de seguridad con respuestas públicas.
Código vulnerable
Sin throttling, una comparación rápida de un hash sin salt, un id de sesión conservado después del login y un token que se decodifica pero nunca se verifica.
app.post("/login", async (req, res) => {
const user = await db.user.findByEmail(req.body.email);
if (user && user.passwordHash === sha1(req.body.password)) {
req.session.userId = user.id; // mismo id de sesión que antes del login
return res.json({ ok: true });
}
res.status(401).json({ error: "wrong email or password" }); // intentos ilimitados
});
const claims = jwt.decode(token); // decode() NO verifica la firmaclaims = jwt.decode(token, options={"verify_signature": False})La corrección
Aplica throttling por cuenta y por IP, verifica las contraseñas con un hash lento, regenera la sesión en el login y define las flags de la cookie. Verifica los tokens con un algoritmo fijado, una clave real y comprobaciones de emisor, audiencia y expiración. Cuando puedas, usa passkeys o un proveedor de identidad en lugar de construir el login tú mismo, y agrega un segundo factor para las cuentas sensibles.
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, limit: 10, keyGenerator: (r) => `${r.ip}:${r.body?.email}` });
app.post("/login", limiter, async (req, res) => {
const user = await db.user.findByEmail(req.body.email);
const ok = user && (await argon2.verify(user.passwordHash, req.body.password));
if (!ok) return res.status(401).json({ error: "wrong email or password" }); // el mismo mensaje en ambos casos
req.session.regenerate(() => { // id nuevo tras el login: derrota la fixation
req.session.userId = user.id;
res.json({ ok: true });
});
});
app.use(session({ cookie: { httpOnly: true, secure: true, sameSite: "lax", maxAge: 3600_000 } }));claims = jwt.decode(
token,
key=PUBLIC_KEY,
algorithms=["RS256"], # fija el algoritmo; nunca aceptes "none"
audience="api.example.com",
issuer="https://id.example.com",
options={"require": ["exp", "iat", "sub"]},
)http.sessionManagement(s -> s.sessionFixation().migrateSession())
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults())); // verifica firma, exp y emisor mediante configuraciónDemuestra la corrección
Prueba los comportamientos de los que depende un atacante: el undécimo intento se rechaza, el id de sesión cambia tras el login y los tokens manipulados o sin firma se rechazan.
def test_login_is_throttled(client):
codes = [client.post("/login", json={"email": "a@x.io", "password": f"guess{i}"}).status_code for i in range(15)]
assert 429 in codes
def test_session_id_changes_after_login(client, user):
before = client.get("/").cookies.get("sid")
client.post("/login", json=user.credentials)
assert client.cookies.get("sid") != before
def test_unsigned_token_is_rejected(client):
forged = b64(b'{"alg":"none"}') + "." + b64(b'{"sub":"admin"}') + "."
assert client.get("/api/me", headers={"Authorization": f"Bearer {forged}"}).status_code == 401
def test_cookie_flags(client, user):
cookie = client.post("/login", json=user.credentials).headers["set-cookie"]
assert "HttpOnly" in cookie and "Secure" in cookieChecklist
- Aplica y monitorea el login por cuenta, por IP y por dispositivo; agrega retraso progresivo o desafío, y no solo bloqueo total (que habilita la denegación de servicio).
- Verifica las contraseñas nuevas contra una lista de contraseñas filtradas; exige longitud en lugar de reglas arbitrarias de complejidad.
- Ofrece autenticación multifactor, idealmente resistente al phishing (passkeys, ), y exígela a los administradores.
- Rota el id de sesión en el login y en los cambios de privilegio; hazlo expirar; invalídalo al cerrar sesión.
- Verifica cada token: firma, algoritmo, emisor, audiencia, expiración. Nunca confíes solo en el decode.
- Haz la recuperación tan fuerte como el login: enlaces de un solo uso y corta duración; la misma respuesta genérica para correos conocidos y desconocidos.
conexiones en el mapa
- A06 Insecure Designdefine el flujo de recuperación deA07 Authentication Failures
- A01 Broken Access Controlnecesita una identidad confiable deA07 Authentication Failures
- A04 Cryptographic Failuresprotege las credenciales deA07 Authentication Failures
actualizado el 14/10/2026 · fuentes para profundizar · reportar un error
en otros dominios
- Bot managementsecopsel credential stuffing es, ante todo, tráfico automatizado
Desbloquea
pon a prueba lo que entendiste
Cargando preguntas…