A la lliçó anterior vam assegurar qui pot accedir a cada dada. Ara protegim la dada en si. A02 – Errors Criptogràfics (Cryptographic Failures) agrupa tot el que surt malament quan la informació sensible no es xifra, es xifra amb algorismes febles, o es gestiona malament la protecció de claus i secrets. Al Top Ten 2021 aquesta categoria va absorbir l'antiga "Exposició de Dades Sensibles" de 2017: el focus va passar de la conseqüència (la dada exposada) a la causa (l'error criptogràfic que la va permetre).
L'impacte és directe sobre la confidencialitat: contrasenyes, dades personals, números de targeta (PAN) o tokens que acaben en mans equivocades per viatjar en clar, desar-se sense xifrar o protegir-se amb algorismes trencats. A BazarNube trobarem dues joies típiques del codi heretat: contrasenyes desades amb un hash obsolet i números de targeta emmagatzemats "per comoditat". Les arreglarem.
Avís legal i ètic: els exemples fan servir dades fictícies. No processis ni emmagatzemis dades reals de targetes fora d'un entorn certificat, i prova només sobre sistemes propis o amb autorització explícita.
Contingut
- Identificar dades sensibles i el seu cicle de vida
- Protecció en trànsit: TLS ben configurat
- Protecció en repòs: xifratge i gestió de claus
- Hashing de contrasenyes: bcrypt/argon2 davant de MD5/SHA1
- Dades que directament no hauries d'emmagatzemar (PAN, CVV)
- Gestió de secrets i claus
- Errors comuns i consells
- Exercicis i solucions
- Identificar dades sensibles i el seu cicle de vida
Abans de xifrar cal saber què protegir. A BazarNube classifiquem:
| Dada | Sensibilitat | En trànsit | En repòs |
|---|---|---|---|
| Contrasenya | Crítica | TLS | Hash lent amb sal (mai xifratge reversible) |
| Dades personals (nom, adreça) | Alta | TLS | Xifrades a la BD |
| PAN (número de targeta) | Crítica (PCI-DSS) | TLS | Preferible no emmagatzemar; si és obligatori, tokenitzar/xifrar |
| CVV | Crítica | TLS | Prohibit emmagatzemar-lo, ni xifrat |
| Token de sessió | Alta | TLS + cookie segura | No persistir en clar |
La regla de partida: minimitza. La dada més segura és la que no deses.
- Protecció en trànsit: TLS
Tot el trànsit de BazarNube (front React, API Express, mòdul Java) ha d'anar sobre HTTPS/TLS. Errors freqüents:
- Endpoints interns "només HTTP" perquè "estan a la xarxa privada" (un atacant que entra a la xarxa els escolta).
- TLS mal configurat: versions obsoletes (SSLv3, TLS 1.0/1.1), xifres febles.
- Manca d'HSTS, que força el navegador a fer servir sempre HTTPS.
// app.js — forcar HTTPS i activar HSTS a Express
const helmet = require('helmet');
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true, preload: true }));
// Redirigir HTTP -> HTTPS (darrere d'un proxy/terminador TLS)
app.use((req, res, next) => {
if (req.headers['x-forwarded-proto'] === 'http') {
return res.redirect(301, 'https://' + req.headers.host + req.url);
}
next();
});HSTS amb maxAge d'un any i includeSubDomains evita atacs de degradació (SSL stripping). Veurem les capçaleres de seguretat amb més detall a A05 (03-06); aquí ens interessa la part criptogràfica: xifrar el canal sempre.
- Protecció en repòs: xifratge i gestió de claus
Les dades personals dels clients es xifren a PostgreSQL. Distingeix:
- Xifratge simètric (AES-256-GCM) per a dades que necessites recuperar en clar (p. ex. una adreça d'enviament).
- Hashing (irreversible) per al que només necessites comparar (contrasenyes). Mai xifris una contrasenya de manera reversible.
// crypto/fieldCrypto.js — xifratge autenticat de camps amb AES-256-GCM
const crypto = require('crypto');
const KEY = Buffer.from(process.env.FIELD_KEY, 'hex'); // 32 bytes, des del gestor de secrets
function encrypt(plaintext) {
const iv = crypto.randomBytes(12); // IV unic per operacio
const cipher = crypto.createCipheriv('aes-256-gcm', KEY, iv);
const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag(); // integritat (GCM)
return Buffer.concat([iv, tag, enc]).toString('base64');
}
function decrypt(blob) {
const raw = Buffer.from(blob, 'base64');
const iv = raw.subarray(0, 12), tag = raw.subarray(12, 28), data = raw.subarray(28);
const decipher = crypto.createDecipheriv('aes-256-gcm', KEY, iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8');
}Punts clau: AES-GCM aporta xifratge i autenticitat (detecta manipulació); l'IV és aleatori i únic per operació; la clau viu en un gestor de secrets, mai al codi.
- Hashing de contrasenyes: el cas de BazarNube
Aquí tenim el clàssic del codi legacy. El registre d'usuaris desava així les contrasenyes:
// auth.js — VULNERABLE: hash rapid, sense sal, trencat
const crypto = require('crypto');
function storePassword(user, password) {
const hash = crypto.createHash('md5').update(password).digest('hex');
return db.query('UPDATE users SET pwd = $1 WHERE id = $2', [hash, user.id]);
}Per què això és un desastre
- MD5/SHA1 estan trencats per a aquest ús: són ràpids i hi ha col·lisions conegudes.
- Sense sal: dos usuaris amb la mateixa contrasenya tenen el mateix hash; una rainbow table els revienta a l'instant.
- Ràpids = dolents per a contrasenyes: un atacant amb una GPU prova milers de milions de MD5 per segon. Si roba la taula
users, recupera la majoria de contrasenyes en hores.
Explotació il·lustrativa
Amb la BD filtrada, l'atacant pren el hash 5f4dcc3b5aa765d61d8327deb882cf99, el busca en qualsevol base de dades pública de hashes MD5 i obté a l'instant la contrasenya password. Sense sal ni cost, és gairebé instantani.
Codi corregit amb bcrypt
// auth.js — SEGUR: bcrypt amb sal automatica i cost ajustable
const bcrypt = require('bcrypt');
const COST = 12; // factor de treball; pujar amb el temps
async function storePassword(user, password) {
const hash = await bcrypt.hash(password, COST); // genera i inclou la sal
return db.query('UPDATE users SET pwd = $1 WHERE id = $2', [hash, user.id]);
}
async function verifyPassword(password, storedHash) {
return bcrypt.compare(password, storedHash); // comparacio en temps constant
}bcrypt genera una sal aleatòria per contrasenya (inclosa al mateix hash) i és deliberadament lent (el factor de cost 12 obliga a moltes iteracions), cosa que fa inviable el cracking massiu. La comparació és en temps constant, evitant atacs de temporització.
Comparativa d'algorismes
| Algorisme | Apte per a contrasenyes? | Motiu |
|---|---|---|
| MD5 / SHA1 | No | Trencats, ràpids, sense sal |
| SHA-256 "a seques" | No | Massa ràpid per a contrasenyes |
| bcrypt | Sí | Lent, amb sal, molt provat |
| argon2id | Sí (preferit avui) | Resistent a GPU/ASIC, cost en memòria |
| PBKDF2 | Acceptable | Vàlid amb moltes iteracions (ús heretat/FIPS) |
Per a un servei nou, argon2id és la recomanació actual de l'OWASP Password Storage Cheat Sheet; bcrypt continua essent una opció sòlida i àmpliament disponible.
Migració sense conèixer les contrasenyes
BazarNube no pot "desxifrar" els MD5 antics per tornar-los a fer hash. L'estratègia és tornar a fer hash al pròxim login vàlid: si l'usuari encerta amb el hash vell, es recalcula amb bcrypt i es marca migrat. Els que no tornen, es forcen a reiniciar la contrasenya després d'un termini.
async function login(email, password) {
const u = await getUser(email);
if (u.pwd_alg === 'md5') {
if (md5(password) !== u.pwd) return fail();
await storePassword(u, password); // rehash amb bcrypt
await setAlg(u, 'bcrypt');
return ok(u);
}
return (await verifyPassword(password, u.pwd)) ? ok(u) : fail();
}
- Dades que no hauries d'emmagatzemar
El mòdul de facturació Java desava el número de targeta complet (PAN) "per mostrar l'historial". Això entra en l'àmbit de PCI-DSS i és un risc enorme.
- El CVV mai es pot emmagatzemar, ni xifrat, sota cap circumstància.
- El PAN només s'ha d'emmagatzemar si és imprescindible, xifrat o tokenitzat.
- El més habitual és delegar el pagament en un proveïdor (PSP) que retorna un token; BazarNube desa només els últims 4 dígits per mostrar, i res més.
// PaymentRecord.java (modul legacy) — VULNERABLE
record PaymentRecord(String pan, String cvv, BigDecimal amount) {} // desa PAN i CVV// PaymentRecord.java — SEGUR: nomes un token del PSP i els ultims 4 digits
record PaymentRecord(String pspToken, String last4, BigDecimal amount) {}
// El PAN complet i el CVV mai toquen la nostra base de dades.Menys dades sensibles emmagatzemades = menor superfície d'exposició i menor abast regulatori.
- Gestió de secrets i claus
De res serveix AES-256 si la clau està al repositori Git.
flowchart LR A[App BazarNube] -->|demana secret a l'arrencada| B[Gestor de secrets] B --> A C[Repositori Git] -. mai conte secrets .-> A
Bones pràctiques:
- Secrets fora del codi: variables d'entorn injectades o un gestor de secrets (Vault, AWS Secrets Manager, etc.). Mai a Git, ni en un
.envversionat. - Rotació de claus periòdica i davant sospita de compromís.
- Separació de claus per entorn (dev/staging/prod) i per propòsit.
- Si un secret es filtra en un commit, rota'l: purgar-lo de l'historial no n'hi ha prou, cal donar-lo per compromès.
Errors Comuns i Consells
- Xifrar contrasenyes de manera reversible. Les contrasenyes es fan hash (bcrypt/argon2), no es xifren.
- Fer servir SHA-256 sense més per a contrasenyes. És ràpid; necessites un algorisme lent amb sal.
- Desar CVV o PAN complet sense necessitat. Delega en un PSP i desa només un token.
- HTTP a la xarxa interna. El xifratge en trànsit s'aplica també dins del perímetre.
- Claus al repositori. Fes servir un gestor de secrets; rota el que s'hagi filtrat.
- Reutilitzar l'IV a AES. Cada operació necessita un IV únic; amb GCM, reutilitzar-lo trenca la seguretat.
- Consell: classifica les dades primer. No pots protegir allò que no saps que tens.
Exercicis
Exercici 1. Un company proposa desar contrasenyes amb SHA-256 + sal. Resol el problema del MD5? Què recomanaries?
Exercici 2. Detecta els dos errors criptogràfics en aquest fragment i corregeix-los:
const key = 'clau-super-secreta-123';
const iv = Buffer.alloc(16, 0); // IV fix de zeros
const cipher = crypto.createCipheriv('aes-256-cbc', key.padEnd(32,'0'), iv);Exercici 3. BazarNube necessita mostrar "targeta acabada en 4242" a l'historial. Què hauria d'emmagatzemar exactament i què no?
Solucions
Solució 1. Afegir sal a SHA-256 elimina les rainbow tables, però SHA-256 continua essent massa ràpid: un atac de força bruta amb GPU continua essent viable. La recomanació és un algorisme lent dissenyat per a contrasenyes: argon2id (preferit) o bcrypt, que incorporen sal i cost ajustable.
Solució 2. Dos errors: (1) l'IV és fix (tot zeros): trenca la confidencialitat en xifrar repetidament; ha de ser aleatori per operació (crypto.randomBytes). (2) La clau és una cadena al codi, farcida amb zeros: ha de ser una clau aleatòria de 32 bytes provinent del gestor de secrets. A més, convé AES-256-GCM (autenticat) en lloc de CBC. Vegeu la implementació de la secció 3.
Solució 3. Emmagatzemar únicament: un token del proveïdor de pagament (per a recobraments/referència) i els últims 4 dígits (last4) més potser la marca (Visa/Mastercard). Mai el PAN complet ni el CVV.
Conclusió
A02 ens recorda que protegir dades és un problema de causa (criptografia i gestió de claus), no només de conseqüència. Al backlog de BazarNube apuntem: TLS + HSTS en tot el trànsit, AES-256-GCM per a dades personals en repòs, migració de contrasenyes de MD5 a bcrypt/argon2 mitjançant rehash al login, eliminació del PAN/CVV a favor de tokens del PSP i trasllat de tots els secrets a un gestor amb rotació.
Entrada de backlog — A02: reemplaçat el hashing MD5 per bcrypt (cost 12) amb pla de migració; eliminat l'emmagatzematge de PAN/CVV al mòdul Java; xifratge de camps personals amb AES-GCM; secrets moguts fora de Git.
Hem protegit la dada en trànsit i en repòs. Però hi ha una família d'errors on la dada de l'usuari s'esmuny dins d'un intèrpret i en pren el control: la injecció. És la lliçó següent, A03:2021 – Injecció, amb SQLi a l'API Node/PostgreSQL de BazarNube i una injecció al mòdul legacy 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
