Vam tancar el mòdul 2 anunciant que en el mòdul 3 recorreríem el Top Ten categoria a categoria sobre el codi real de BazarNube, convertint el vocabulari en coneixement operatiu. Comencem per on comença la llista de 2021: A01 – Pèrdua de Control d'Accés (Broken Access Control). No és casualitat que ocupi el primer lloc: és la categoria més estesa, la que afecta més aplicacions i la que té més impacte directe sobre la confidencialitat i la integritat de les dades.

El control d'accés decideix què pot fer cada usuari autenticat. Quan falla, un client normal de BazarNube pot llegir les comandes d'un altre client, un usuari sense permisos pot accedir al tauler d'administració, o qualsevol pot modificar recursos que no li pertanyen. En aquest curs ho il·lustrarem amb un cas molt típic: un endpoint de l'API Node/Express de BazarNube que retorna comandes fent servir directament l'identificador que arriba a la URL, sense comprovar si aquella comanda és de l'usuari que pregunta.

Avís legal i ètic: totes les tècniques d'atac que es mostren són il·lustratives i amb dades fictícies. Practica només sobre sistemes propis o amb autorització explícita i per escrit. Provar el control d'accés sobre sistemes de tercers sense permís és delicte a la majoria de jurisdiccions.

Contingut

  1. Autenticació vs. autorització: no confondre A01 amb A07
  2. IDOR i referències directes insegures a objectes
  3. Escalada horitzontal i vertical de privilegis
  4. Forçat de rutes i control d'accés al client
  5. CORS mal configurat com a error de control d'accés
  6. Principis de prevenció: denegar per defecte, autoritzar al servidor
  7. Models d'autorització: RBAC i ABAC
  8. Errors comuns i consells
  9. Exercicis i solucions

  1. Autenticació vs. autorització

Són dos controls diferents i consecutius:

Concepte Pregunta que respon Categoria OWASP
Autenticació Qui ets? Ets qui dius que ets? A07 (lliçó 03-09)
Autorització / Control d'accés Un cop identificat, què pots fer? A01 (aquesta lliçó)

Un sistema pot autenticar perfectament (login sòlid, MFA, sessions ben gestionades) i tot i així tenir el control d'accés trencat: sap qui ets, però no comprova si pots accedir al recurs que demanes. A01 s'ocupa d'aquest segon control.

  1. IDOR i referències directes insegures a objectes

Un IDOR (Insecure Direct Object Reference) passa quan l'aplicació exposa una referència a un objecte intern (un id de base de dades, un nom de fitxer) i decideix l'accés només a partir d'aquell valor controlat per l'usuari, sense verificar la propietat del recurs.

Codi vulnerable a BazarNube

Lucía, la backend lead, va escriure aquest endpoint per consultar el detall d'una comanda:

// routes/orders.js  (API Node/Express de BazarNube) — VULNERABLE
router.get('/api/comandes/:comandaId', requireLogin, async (req, res) => {
  // requireLogin nomes comprova que hi ha sessio valida (autenticacio)
  const comanda = await db.query(
    'SELECT * FROM orders WHERE id = $1',
    [req.params.comandaId]
  );
  if (comanda.rows.length === 0) return res.status(404).json({ error: 'No trobat' });
  res.json(comanda.rows[0]); // retorna la comanda sigui de qui sigui
});

El middleware requireLogin garanteix que hi ha una sessió iniciada, però ningú comprova que la comanda pertanyi a l'usuari d'aquella sessió. La consulta filtra per id, no per propietari.

Com s'explota (il·lustratiu)

La clienta Ana (usuària 501) veu la seva comanda a GET /api/comandes/1042. Com que els ids són numèrics i correlatius, prova a incrementar-los:

GET /api/comandes/1043   -> comanda d'un altre client (nom, adreca, import)
GET /api/comandes/1044   -> una altra mes

Amb un simple bucle pot descarregar l'historial de comandes de tota la botiga: dades personals, adreces i línies de compra. És una fuga massiva sense necessitat de "hackejar" res; n'hi ha prou amb canviar un número.

Codi corregit

La regla és: el servidor ha de lligar cada accés a la identitat de la sessió, no a l'identificador que envia el client.

// routes/orders.js — SEGUR
router.get('/api/comandes/:comandaId', requireLogin, async (req, res) => {
  const comanda = await db.query(
    // filtrem TAMBE pel propietari pres de la sessio del servidor
    'SELECT * FROM orders WHERE id = $1 AND user_id = $2',
    [req.params.comandaId, req.session.userId]
  );
  // Retornem 404 (no 403) per no revelar que l'id existeix pero es d'un altre
  if (comanda.rows.length === 0) return res.status(404).json({ error: 'No trobat' });
  res.json(comanda.rows[0]);
});

Detalls clau:

  • req.session.userId prové del servidor (de la sessió), mai d'un paràmetre que el client pugui manipular.
  • La condició AND user_id = $2 converteix la comprovació d'autorització en part de la mateixa consulta: si la comanda no és teva, simplement no existeix per a tu.
  • Respondre 404 en lloc de 403 evita filtrar l'existència de recursos aliens (un petit reforç davant l'enumeració).

Com a reforç addicional, molts equips substitueixen els ids seqüencials per UUID o identificadors no endevinables. Compte: això dificulta l'enumeració, però no substitueix la comprovació de propietat. Un UUID filtrat (en un log, en un email) continuaria essent accessible. La defensa real és la comprovació al servidor.

  1. Escalada horitzontal i vertical de privilegis

  • Escalada horitzontal: accedeixes a recursos d'un altre usuari del mateix nivell. L'IDOR de dalt és exactament això: l'Ana llegint comandes d'altres clients.
  • Escalada vertical: obtens privilegis d'un rol superior. Per exemple, un client que invoca un endpoint d'administració.

Exemple vertical a BazarNube

// routes/admin.js — VULNERABLE: nomes comprova login, no el rol
router.post('/api/admin/products/:id/price', requireLogin, async (req, res) => {
  await db.query('UPDATE products SET price = $1 WHERE id = $2',
    [req.body.price, req.params.id]);
  res.json({ ok: true });
});

Qualsevol client autenticat podria canviar preus. La correcció exigeix comprovar el rol al servidor:

// middleware/authorize.js
function requireRole(...roles) {
  return (req, res, next) => {
    if (!roles.includes(req.session.role)) {
      return res.status(403).json({ error: 'Prohibit' });
    }
    next();
  };
}

// routes/admin.js — SEGUR
router.post('/api/admin/products/:id/price',
  requireLogin, requireRole('admin'), async (req, res) => { /* ... */ });

El rol es pren de req.session.role, establert al login a partir de la base de dades, mai d'un camp enviat pel client (com un role al cos o en una cookie manipulable).

  1. Forçat de rutes i control d'accés al client

Un error freqüent en SPAs com el front React de BazarNube és amagar botons o rutes d'administració al client i creure que això protegeix. No protegeix: el navegador és territori de l'atacant.

// AdminLink.jsx — aixo es UX, NO seguretat
{user.role === 'admin' && <Link to="/admin">Tauler</Link>}

Amagar l'enllaç millora l'experiència, però qualsevol pot navegar directament a /admin o, més important, cridar POST /api/admin/... amb curl. Tota decisió d'autorització s'ha de repetir i fer complir al servidor. El client pot reflectir permisos; el servidor els ha d'imposar.

El forçat de rutes (forced browsing) consisteix precisament a demanar directament URLs no enllaçades (/api/admin, /backup.zip, /api/internal/metrics) confiant que estiguin desprotegides. Es preveu aplicant el mateix control d'accés a tots els endpoints, inclosos els que no apareixen a la interfície.

  1. CORS mal configurat

CORS (Cross-Origin Resource Sharing) controla quins orígens web poden llegir respostes de la teva API des del navegador. Una configuració laxa pot convertir-se en un error de control d'accés.

// VULNERABLE: reflecteix qualsevol origen i a mes permet credencials
app.use(cors({
  origin: (o, cb) => cb(null, true), // accepta TOTS els origens
  credentials: true                  // ...enviant cookies de sessio
}));

Reflectir l'Origin de l'atacant juntament amb credentials: true permet que un lloc maliciós faci peticions autenticades a l'API de BazarNube en nom de la víctima i llegeixi les respostes. La correcció és una llista blanca explícita:

// SEGUR
const allowed = ['https://bazarnube.com', 'https://app.bazarnube.com'];
app.use(cors({
  origin: (o, cb) => cb(null, !o || allowed.includes(o)),
  credentials: true
}));

Mai facis servir origin: '*' combinat amb credencials (el mateix navegador ho bloqueja, però reflectir l'origen és l'equivalent perillós).

  1. Principis de prevenció

flowchart TD
  A[Peticio entrant] --> B{Sessio valida?}
  B -- No --> X[401 No autenticat]
  B -- Si --> C{Autoritzat per aquest recurs?}
  C -- No --> Y[403/404 Denegat per defecte]
  C -- Si --> D[Executar accio]

Les regles d'or d'A01:

  1. Denegar per defecte. Tot endpoint parteix de "prohibit" i només es permet allò explícitament autoritzat. Res queda obert per oblit.
  2. Autoritzar sempre al servidor, amb identitat presa de la sessió, no de paràmetres del client.
  3. Centralitzar la lògica d'autorització en middlewares/serveis reutilitzables, no dispersa i copiada endpoint a endpoint (on és fàcil oblidar-la).
  4. Comprovar la propietat del recurs en cada accés a objectes (evita IDOR).
  5. Registrar els errors de control d'accés i alertar davant patrons d'abús (enllaça amb A09, lliçó 03-11).

  1. Models d'autorització: RBAC i ABAC

Model Idea Exemple a BazarNube Quan fer-lo servir
RBAC (basat en rols) El permís depèn del rol de l'usuari admin, suport, client Regles estables i per rol
ABAC (basat en atributs) El permís depèn d'atributs d'usuari, recurs i context "un venedor només edita productes de la seva botiga" Regles fines, dependents de la dada

BazarNube combina tots dos: RBAC per separar client/suport/admin, i una capa ABAC per a "cada venedor gestiona només el seu". L'IDOR de la secció 2 és, en el fons, una regla ABAC (comanda.user_id == sessio.userId) que faltava.

Errors Comuns i Consells

  • Confiar en el client. Amagar botons no és autoritzar. Repeteix la comprovació al servidor, sempre.
  • Fer servir l'id del client com a font d'identitat. Pren el userId/role de la sessió del servidor, mai del cos, la query o una cookie no signada.
  • Autorització copiada i enganxada. Si cada endpoint reimplementa la comprovació, algun se n'oblidarà. Centralitza en middlewares.
  • Suposar que un UUID protegeix. Dificulta enumerar, no autoritza. Mantén la comprovació de propietat.
  • Oblidar els mètodes "secundaris". Protegeixes GET però deixes PUT/DELETE oberts. Cobreix tots els verbs i tots els endpoints, inclosos els interns.
  • Consell: escriu proves automàtiques d'autorització ("l'usuari A no pot llegir la comanda de B") i executa-les a CI. Els IDOR es detecten molt bé amb tests.

Exercicis

Exercici 1. El següent endpoint de BazarNube permet descarregar una factura. Identifica la vulnerabilitat i corregeix-la.

router.get('/api/invoices/:id/pdf', requireLogin, async (req, res) => {
  const inv = await db.query('SELECT pdf_path FROM invoices WHERE id = $1',
    [req.params.id]);
  res.download(inv.rows[0].pdf_path);
});

Exercici 2. Marc proposa "arreglar" l'IDOR de comandes canviant els ids numèrics per UUID i no tocar la consulta. És suficient? Justifica-ho.

Exercici 3. Classifica cada escenari com a escalada horitzontal o vertical: a) Un client accedeix al carret d'un altre client. b) Un usuari de suport executa una acció reservada a administració. c) Un venedor edita un producte d'una altra botiga.

Solucions

Solució 1. És un IDOR: es descarrega qualsevol factura per id sense comprovar la propietat, i a més no valida que la fila existeixi (fallaria amb undefined). Correcció:

router.get('/api/invoices/:id/pdf', requireLogin, async (req, res) => {
  const inv = await db.query(
    'SELECT pdf_path FROM invoices WHERE id = $1 AND user_id = $2',
    [req.params.id, req.session.userId]);
  if (inv.rows.length === 0) return res.status(404).json({ error: 'No trobat' });
  res.download(inv.rows[0].pdf_path);
});

Solució 2. No és suficient. L'UUID només dificulta endevinar ids, però si un es filtra (email, log, historial del navegador compartit) l'accés continua obert perquè no hi ha comprovació de propietat. La defensa correcta és afegir AND user_id = $2. L'UUID és una capa complementària, no la principal.

Solució 3. a) horitzontal; b) vertical; c) horitzontal (mateix nivell de rol, diferent propietari del recurs; és una regla ABAC).

Conclusió

La pèrdua de control d'accés encapçala el Top Ten perquè és fàcil d'introduir (n'hi ha prou amb oblidar una comprovació) i molt danyina (fuga o manipulació de dades alienes). Les claus que anotem al backlog de BazarNube: denegar per defecte, prendre la identitat de la sessió del servidor, comprovar propietat i rol en cada accés, centralitzar l'autorització i no confiar mai en el client.

Entrada de backlog — A01: corregit l'IDOR a GET /api/comandes/:comandaId (afegit filtre per user_id), afegit requireRole('admin') als endpoints d'administració i substituït el CORS reflectit per llista blanca. Pendent: tests d'autorització a CI.

Un cop sabem que només la persona adequada accedeix a una dada, sorgeix la pregunta següent: quan aquella dada viatja o es desa, està protegida? Això ens porta a la lliçó següent, A02:2021 – Errors Criptogràfics i Exposició de Dades Sensibles, on veurem com BazarNube protegeix contrasenyes, targetes i trànsit.

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

Mòdul 3: OWASP Top Ten 2021 en Profunditat

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)

Mòdul 7: Bones Pràctiques i Recomanacions

Mòdul 8: Exercicis Pràctics i Casos d'Estudi

Mòdul 9: Avaluació i Certificació

© Copyright 2026. Tots els drets reservats