A A01 vam distingir autenticació ("qui ets?") d'autorització ("què pots fer?") i vam dir que l'autenticació tindria la seva pròpia lliçó. Ha arribat. A07:2021 – Errors d'Identificació i Autenticació (Identification and Authentication Failures) —abans anomenada "Pèrdua d'Autenticació"— agrupa tot el que surt malament en verificar la identitat d'un usuari i en mantenir aquesta identitat durant la sessió: contrasenyes febles, força bruta i credential stuffing, gestió de sessions deficient, absència de MFA, recuperació de compte insegura.
Convé situar-la entre les seves veïnes: A07 valida qui ets (login, sessions, MFA); A01 decideix què pots fer un cop identificat; i A02 s'ocupa de com s'emmagatzemen les credencials (bcrypt/argon2). Les tres col·laboren, però resolen problemes diferents. A BazarNube revisarem el login i la gestió de sessions de l'API Express, dos dels punts més atacats de qualsevol aplicació amb comptes d'usuari.
Avís legal i ètic: els exemples d'atac (força bruta, credential stuffing) són il·lustratius i amb dades fictícies. Practica només sobre sistemes propis o amb autorització explícita.
Contingut
- Contrasenyes febles i polítiques modernes
- Força bruta i credential stuffing
- Missatges d'error i enumeració d'usuaris
- Gestió de sessions segura
- Autenticació multifactor (MFA)
- Recuperació de compte sense forats
- Errors comuns, exercicis i solucions
- Contrasenyes febles i polítiques modernes
El registre de BazarNube acceptava qualsevol contrasenya, incloses les més comunes:
// register.js — VULNERABLE: sense politica de contrasenyes
router.post('/api/register', async (req, res) => {
const { email, password } = req.body;
await createUser(email, password); // accepta "123456", "password"...
res.json({ ok: true });
});Les recomanacions actuals (alineades amb NIST i l'OWASP Authentication Cheat Sheet) han canviat respecte de les velles regles:
- Longitud mínima raonable (p. ex. 12) i permetre contrasenyes llargues (suporta almenys 64 caràcters, sense truncar).
- Comprovar contra llistes de contrasenyes compromeses/comunes (rebutjar "password", "bazarnube2024"...).
- No imposar regles de composició absurdes (majúscula + símbol obligatoris) ni caducitat periòdica forçada: fomenten patrons predictibles. Millor: longitud + comprovació d'exposició.
- Permetre gestors de contrasenyes (no bloquejar enganxar).
// register.js — SEGUR: politica moderna
const zxcvbn = require('zxcvbn'); // estima la fortalesa real
router.post('/api/register', async (req, res) => {
const { email, password } = req.body;
if (password.length < 12) return res.status(400).json({ error: 'Minim 12 caracters' });
if (zxcvbn(password).score < 3) return res.status(400).json({ error: 'Contrasenya feble' });
if (await isBreached(password)) return res.status(400).json({ error: 'Contrasenya compromesa' });
await createUser(email, password); // s'emmagatzema amb bcrypt/argon2 (veure A02)
res.json({ ok: true });
});isBreached consulta (de manera que preservi la privacitat, amb k-anonymity) si la contrasenya apareix en filtracions conegudes. Recorda: com es guarda la contrasenya ja ho vam resoldre a A02 amb bcrypt/argon2.
- Força bruta i credential stuffing
- Força bruta: provar moltes contrasenyes contra un compte.
- Credential stuffing: fer servir parells usuari/contrasenya filtrats d'altres bretxes, apostant que la gent reutilitza credencials. És avui l'atac més comú contra logins.
El login de BazarNube no tenia cap fre:
// login.js — VULNERABLE: sense limit d'intents
router.post('/api/login', async (req, res) => {
const u = await getUser(req.body.email);
if (u && await verifyPassword(req.body.password, u.pwd)) {
req.session.userId = u.id;
return res.json({ ok: true });
}
res.status(401).json({ error: 'Credencials invalides' });
});Sense límits, un atacant llança milers d'intents per minut. Defenses combinades:
// login.js — SEGUR: rate limiting + bloqueig progressiu
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({ windowMs: 15*60*1000, max: 10 }); // per IP
router.post('/api/login', loginLimiter, async (req, res) => {
const email = req.body.email;
if (await isLockedOut(email)) return res.status(429).json({ error: 'Massa intents' });
const u = await getUser(email);
if (u && await verifyPassword(req.body.password, u.pwd)) {
await resetFailures(email);
await regenerateSession(req); // veure seccio 4 (fixacio de sessio)
req.session.userId = u.id;
return res.json({ ok: true });
}
await recordFailure(email); // bloqueig progressiu per compte
res.status(401).json({ error: 'Credencials invalides' });
});| Control | Davant de |
|---|---|
| Rate limiting per IP | Força bruta des d'una font |
| Bloqueig progressiu per compte | Atacs dirigits a un usuari |
| CAPTCHA després de N fallades | Automatització massiva |
| MFA | Credential stuffing (encara que encertin la clau) |
| Detecció de logins anòmals | Credential stuffing distribuït |
El credential stuffing fa servir moltes IPs i una sola contrasenya per compte, així que el límit per IP no n'hi ha prou: per això MFA i la detecció de patrons són claus.
- Missatges d'error i enumeració d'usuaris
Un detall subtil: si el login respon "aquest correu no existeix" davant de "contrasenya incorrecta", regales a l'atacant una manera d'enumerar quins comptes existeixen. El mateix amb temps de resposta diferents.
// VULNERABLE: revela si el correu existeix
if (!u) return res.status(404).json({ error: 'Usuari no trobat' });
if (!ok) return res.status(401).json({ error: 'Contrasenya incorrecta' });
// SEGUR: missatge i comportament uniformes
// (mateixa resposta existeixi o no l'usuari; mateix cost de temps)
return res.status(401).json({ error: 'Credencials invalides' });La mateixa cura aplica al registre ("aquest correu ja està registrat") i a la recuperació de contrasenya (secció 6): respon sempre de manera uniforme.
- Gestió de sessions segura
Un cop autenticat, la sessió és la que "recorda" qui ets. Els seus errors són tan greus com els del login.
flowchart LR A[Login correcte] --> B[Regenerar id de sessio] B --> C[Cookie HttpOnly Secure SameSite] C --> D[Expiracio per inactivitat i absoluta] D --> E[Logout invalida sessio al servidor]
Bones pràctiques:
- Regenerar l'id de sessió en iniciar sessió (
regenerateSession): evita la fixació de sessió, on l'atacant fixa un id conegut abans del login i l'hereta després. - Cookies amb
HttpOnly(no accessible per JS, mitiga XSS —veure 03-04),Secure(només HTTPS) iSameSite(mitiga CSRF). - Expiració: per inactivitat (p. ex. 30 min) i absoluta (p. ex. 12 h), després de la qual cal reautenticar-se.
- Logout real: invalidar la sessió al servidor, no només esborrar la cookie al client.
- Tokens de sessió aleatoris i de prou entropia (els genera el framework; no inventis els teus).
// config de sessio segura (Express)
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false, saveUninitialized: false,
cookie: { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 30*60*1000 }
}));
function regenerateSession(req) {
return new Promise((ok, err) => req.session.regenerate(e => e ? err(e) : ok()));
}Si fas servir JWT en lloc de sessions de servidor, cuida el seu propi conjunt de riscos: signatura verificada (algoritme fixat, mai alg: none), caducitat curta, i un mecanisme de revocació (els JWT no s'invaliden sols en fer logout).
- Autenticació multifactor (MFA)
L'MFA afegeix un segon factor (alguna cosa que tens, com un TOTP o una passkey) a l'"alguna cosa que saps" (la contrasenya). És la defensa més eficaç contra el credential stuffing: encara que l'atacant tingui la contrasenya correcta, sense el segon factor no entra.
- Ofereix MFA a tots els clients de BazarNube i exigeix-la als comptes d'administració i a operacions sensibles (canvis de pagament, exportacions).
- Prefereix TOTP (apps d'autenticació) o WebAuthn/passkeys (resistents a phishing) davant d'SMS, més vulnerable a SIM swapping.
- Considera step-up authentication: demanar el segon factor només en accions crítiques, no en cada login.
- Recuperació de compte sense forats
El flux de "he oblidat la contrasenya" és un punt feble clàssic: si és insegur, l'atacant el fa servir per prendre el compte sense conèixer la contrasenya.
- Genera un token aleatori d'un sol ús i caducitat curta (p. ex. 15-30 min), guarda només el seu hash, i envia'l per un canal verificat (correu).
- No revelis si el correu existeix: respon sempre "si el compte existeix, t'hem enviat instruccions".
- Invalida el token després de fer-lo servir i després de canviar la contrasenya; tanca les sessions actives en restablir-la.
- Mai facis servir preguntes de seguretat endevinables ("nom de la teva mascota") com a únic factor de recuperació.
// recuperacio — patro segur (resum)
router.post('/api/forgot', async (req, res) => {
const u = await getUser(req.body.email);
if (u) {
const token = crypto.randomBytes(32).toString('hex');
await saveResetToken(u.id, sha256(token), Date.now() + 30*60*1000); // guarda hash + expiracio
await sendEmail(u.email, resetLink(token));
}
res.json({ message: 'Si el compte existeix, enviarem instruccions' }); // resposta uniforme
});Errors Comuns i Consells
- Login sense límits. Rate limiting + bloqueig progressiu són imprescindibles.
- Missatges que revelen si un usuari existeix. Uniforma respostes i temps (login, registre, recuperació).
- No regenerar la sessió en entrar. Obre la porta a la fixació de sessió.
- Cookies sense HttpOnly/Secure/SameSite. Facilites robatori de sessió i CSRF.
- No oferir MFA o deixar-la opcional en comptes d'administració.
- Recuperació amb tokens llargs, sense caducar o endevinables. És una via directa de presa de compte.
- Confondre A07 amb A02. A07 valida i manté la identitat; A02 emmagatzema la credencial (bcrypt/argon2).
- Consell: recolza't en solucions provades (frameworks de sessió, proveïdors d'identitat) abans de construir autenticació des de zero.
Exercicis
Exercici 1. Explica per què el rate limiting per IP no atura per si sol un atac de credential stuffing i quins controls el complementen.
Exercici 2. Aquest login revela informació. Identifica el problema i corregeix-lo:
const u = await getUser(email);
if (!u) return res.status(404).json({ error: 'Aquest correu no esta registrat' });
if (!await verify(pass, u.pwd)) return res.status(401).json({ error: 'Clau erronia' });Exercici 3. Què és la fixació de sessió i quina única línia, ben col·locada, la prevé?
Solucions
Solució 1. El credential stuffing es distribueix entre moltes IPs i prova una contrasenya (la filtrada) per compte, així que amb prou feines dispara els límits per IP. El complementen: MFA (encara que encertin la clau, falta el segon factor), detecció de patrons anòmals (moltes comptes diferents, agents sospitosos), comprovació de contrasenyes compromeses en registrar-se, i CAPTCHA/step-up davant de senyals d'automatització.
Solució 2. Permet enumerar usuaris: distingeix "correu no registrat" (404) de "clau errònia" (401). Correcció: resposta uniforme, mateix codi i missatge existeixi o no l'usuari, i tenint cura que el temps de resposta no delati el cas (return res.status(401).json({ error: 'Credencials invalides' }) en tots dos).
Solució 3. La fixació de sessió es produeix quan l'atacant aconsegueix que la víctima faci servir un id de sessió que l'atacant ja coneix (p. ex. l'hi fixa per URL) i, després del login, hereta aquella sessió autenticada. Es prevé regenerant l'id de sessió just en autenticar-se (req.session.regenerate(...)), de manera que l'id anterior queda inservible.
Conclusió
A07 protegeix la porta d'entrada i la permanència de l'usuari: polítiques de contrasenya modernes (longitud + comprovació d'exposició), frens a la força bruta i al credential stuffing (rate limiting, bloqueig, MFA), missatges uniformes contra l'enumeració, gestió de sessions sòlida (regeneració, cookies segures, expiració, logout real) i recuperació de compte amb tokens efímers d'un sol ús. Tot recolzant-se en l'emmagatzematge segur que ja vam muntar a A02.
Entrada de backlog — A07: política de contrasenyes amb longitud mínima i comprovació de contrasenyes compromeses; rate limiting + bloqueig progressiu al login; missatges de login/registre uniformes; regeneració de sessió i cookies HttpOnly/Secure/SameSite; MFA obligatòria per a administració; flux de recuperació amb token efímer hashejat.
Ja sabem identificar i autenticar. Però, podem confiar en la integritat del programari i les dades que flueixen per BazarNube —actualitzacions, objectes serialitzats, artefactes del pipeline? La lliçó següent aborda A08:2021 – Errors d'Integritat de Programari i Dades, incloent la deserialització insegura al mòdul Java.
Curs d'OWASP: Directrius i Estàndards per a la Seguretat en Aplicacions Web
Mòdul 1: Introducció a OWASP
Mòdul 2: Principals Projectes d'OWASP
- OWASP Top Ten
- OWASP ASVS (Application Security Verification Standard)
- OWASP SAMM (Software Assurance Maturity Model)
- OWASP ZAP (Zed Attack Proxy)
- Altres Projectes Clau: WSTG, Cheat Sheets i Dependency-Check
Mòdul 3: OWASP Top Ten 2021 en Profunditat
- A01:2021 – Pèrdua de Control d'Accés
- A02:2021 – Errors Criptogràfics i Exposició de Dades Sensibles
- A03:2021 – Injecció
- Cross-Site Scripting (XSS) en Profunditat
- A04:2021 – Disseny Insegur
- A05:2021 – Configuració de Seguretat Incorrecta
- Entitats Externes XML (XXE)
- A06:2021 – Components Vulnerables i Desactualitzats
- A07:2021 – Errors d'Identificació i Autenticació
- A08:2021 – Errors d'Integritat de Programari i Dades (Deserialització Insegura)
- A09:2021 – Errors de Registre i Monitorització
- A10:2021 – Server-Side Request Forgery (SSRF)
Mòdul 4: OWASP ASVS (Application Security Verification Standard)
Mòdul 5: OWASP SAMM (Software Assurance Maturity Model)
Mòdul 6: OWASP ZAP (Zed Attack Proxy)
- Introducció a ZAP
- Instal·lació i Configuració
- Escaneig de Vulnerabilitats
- Automatització de Proves de Seguretat
Mòdul 7: Bones Pràctiques i Recomanacions
- Cicle de Vida de Desenvolupament Segur (SDLC)
- Modelatge d'Amenaces (Threat Modeling)
- Integració de Seguretat en DevOps (DevSecOps)
- Formació i Conscienciació en Seguretat
- Eines i Recursos Addicionals
Mòdul 8: Exercicis Pràctics i Casos d'Estudi
- Exercici 1: Identificació de Vulnerabilitats
- Exercici 2: Implementació de Controls de Seguretat
- Cas d'Estudi 1: Anàlisi d'un Incident de Seguretat
- Cas d'Estudi 2: Millora de la Seguretat en una Aplicació Web
