Arribem a una de les famílies de vulnerabilitats més antigues i persistents. A03 – Injecció (Injection) es produeix quan dades controlades per l'usuari es barregen amb una instrucció que un intèrpret executarà —SQL, ordres del sistema operatiu, LDAP, consultes NoSQL— i l'intèrpret acaba executant part d'aquestes dades com si fossin instruccions. En el Top Ten 2021 aquesta categoria va créixer: va absorbir l'antic Cross-Site Scripting (XSS), que tècnicament és una injecció al navegador. Aquí tractarem la injecció del costat servidor; el XSS el desenvolupem a fons a la lliçó 03-04, així que en aquesta només el mencionem com a parent proper.

L'arrel sempre és la mateixa: no separar el codi de les dades. A BazarNube veurem una injecció SQL al cercador de productes de l'API Node/PostgreSQL, i una injecció d'ordres a una utilitat del mòdul legacy Java. L'impacte va des de llegir tota la base de dades fins a executar ordres al servidor.

Avís legal i ètic: els payloads mostrats són il·lustratius i amb dades fictícies. Practica només sobre sistemes propis o amb autorització explícita. Llançar injeccions contra sistemes aliens és delicte.

Contingut

  1. Què és la injecció i per què es produeix
  2. Injecció SQL (SQLi) a l'API de BazarNube
  3. Consultes parametritzades i ORM
  4. Injecció d'ordres del sistema operatiu
  5. Injecció LDAP i NoSQL
  6. Validació d'entrada i mínim privilegi
  7. XSS: la injecció del navegador (remès a 03-04)
  8. Errors comuns, exercicis i solucions

  1. Què és la injecció i per què es produeix

Un intèrpret rep una cadena i decideix què és instrucció i què és dada. Si construïm aquesta cadena concatenant entrada de l'usuari, aquest pot introduir metacaràcters (', ;, --, $) que trenquen l'estructura prevista i canvien el significat de la instrucció.

flowchart LR
  A[Entrada de l'usuari] --> B{Es concatena a la instruccio?}
  B -- Si --> C[L'interpret executa dades com a codi]
  B -- No, va com a parametre --> D[La dada mai canvia l'estructura]
  C --> E[Injeccio]
  D --> F[Segur]

La defensa de fons és separar codi de dades: el desenvolupador defineix l'estructura de la instrucció i el motor tracta l'entrada de l'usuari sempre com a valor, mai com a sintaxi.

  1. Injecció SQL a BazarNube

El cercador de productes de l'API es va escriure concatenant el terme de cerca:

// routes/search.js — VULNERABLE a SQLi
router.get('/api/products/search', async (req, res) => {
  const q = req.query.q;
  const sql = "SELECT id, name, price FROM products WHERE name LIKE '%" + q + "%'";
  const result = await db.query(sql); // concatenacio directa
  res.json(result.rows);
});

Com s'explota (il·lustratiu)

Amb una cerca normal ?q=samarreta, la consulta és correcta. Però un atacant envia:

?q=x%' UNION SELECT id, email, pwd FROM users --

La consulta resultant es converteix en:

SELECT id, name, price FROM products WHERE name LIKE '%x%'
UNION SELECT id, email, pwd FROM users --%'

El UNION afegeix als resultats els correus i hashes de contrasenyes de la taula users, i -- comenta la resta. El cercador de productes acaba filtrant credencials. Variants d'aquesta tècnica permeten llegir qualsevol taula, esborrar dades (; DROP TABLE ...) o extreure informació a cegues (blind SQLi) mesurant temps de resposta.

Correcció: consultes parametritzades

// routes/search.js — SEGUR amb consulta parametritzada
router.get('/api/products/search', async (req, res) => {
  const q = req.query.q ?? '';
  const sql = 'SELECT id, name, price FROM products WHERE name LIKE $1';
  const result = await db.query(sql, ['%' + q + '%']); // q va com a PARAMETRE
  res.json(result.rows);
});

La diferència és essencial: $1 és un marcador de posició. El driver de PostgreSQL envia l'estructura de la consulta i els valors per separat; el motor mai interpreta el contingut de q com a SQL. Encara que q contingui ' UNION SELECT ..., es buscarà literalment aquell text al nom del producte. El codi i les dades queden separats.

  1. Consultes parametritzades i ORM

Enfocament Exemple Seguretat
Concatenació de cadenes "... WHERE id=" + id Vulnerable
Consulta parametritzada query(sql, [id]) Segur
ORM / query builder Product.findByPk(id) Segur (fa servir paràmetres per sota)

Un ORM com Sequelize o Prisma genera consultes parametritzades automàticament, cosa que redueix el risc. Compte: aquesta protecció es perd si fas servir la "via d'escapament" de l'ORM per a SQL cru concatenant entrada:

// PERILL: fins i tot amb ORM, aixo torna a ser vulnerable
sequelize.query("SELECT * FROM products WHERE name = '" + name + "'");
// Correcte: fer servir replacements/bind
sequelize.query('SELECT * FROM products WHERE name = ?', { replacements: [name] });

  1. Injecció d'ordres del sistema operatiu

El mòdul legacy Java té una utilitat que genera miniatures invocant una eina externa. Es va construir concatenant el nom de fitxer:

// ThumbnailService.java — VULNERABLE a injeccio d'ordres
public void generate(String filename) throws IOException {
    // filename arriba des d'una peticio de l'usuari
    String cmd = "convert /uploads/" + filename + " -resize 100x100 /thumbs/" + filename;
    Runtime.getRuntime().exec(cmd);   // executa una cadena a la shell
}

Si filename és foto.png; rm -rf /data, la shell executa dues ordres. L'atacant aconsegueix execució d'ordres al servidor. Correcció: no invocar una shell i passar els arguments per separat, a més de validar el nom.

// ThumbnailService.java — SEGUR
public void generate(String filename) throws IOException, InterruptedException {
    if (!filename.matches("[A-Za-z0-9_.-]{1,64}")) {   // allowlist estricta
        throw new IllegalArgumentException("Nom de fitxer invalid");
    }
    ProcessBuilder pb = new ProcessBuilder(
        "convert", "/uploads/" + filename, "-resize", "100x100", "/thumbs/" + filename);
    // ProcessBuilder NO fa servir shell: cada argument es passa tal qual, sense interpretar ; | &
    pb.start().waitFor();
}

ProcessBuilder amb arguments separats evita que la shell interpreti metacaràcters, i la llista blanca (matches) rebutja qualsevol nom que no sigui alfanumèric. Doble barrera.

  1. Injecció LDAP i NoSQL

El mateix principi (separar codi de dades) aplica a altres intèrprets:

  • LDAP: construir filtres concatenant entrada permet injectar *)(uid=* per saltar autenticacions. Solució: escapar segons RFC 4515 o fer servir APIs que parametritzin el filtre.
  • NoSQL (MongoDB): enviar objectes en lloc de cadenes permet operadors. Si el login fa find({user, pass}) amb pass agafat tal qual del JSON, un atacant envia {"pass": {"$ne": null}} i aconsegueix "qualsevol contrasenya diferent de null".
// NoSQL — VULNERABLE: accepta objectes del client
db.users.findOne({ user: req.body.user, pass: req.body.pass });
// SEGUR: forcar tipus primitius abans de consultar
const user = String(req.body.user), pass = String(req.body.pass);
db.users.findOne({ user, pass }); // (i a mes comparar hash, veure A02)

Forçar els tipus (String(...)) impedeix que el client colgui operadors com $ne o $gt.

  1. Validació d'entrada i mínim privilegi

La parametrització és la defensa principal, però es reforça amb defensa en profunditat:

  1. Validació d'entrada per llista blanca: accepta només allò esperat (tipus, longitud, format). No confiïs en "llistes negres" de caràcters prohibits; sempre hi ha una manera de saltar-les.
  2. Mínim privilegi a la base de dades: el compte que fa servir l'API de BazarNube no cal que sigui superusuari ni pugui fer DROP TABLE. Si només llegeix/escriu en certes taules, una SQLi té molt menys abast.
  3. Escapament com a últim recurs: quan no puguis parametritzar (p. ex. un nom de columna dinàmic), valida contra una llista blanca de valors permesos, mai concatenis directament.
  4. Limitar resultats i errors: no retornis errors SQL crus al client (faciliten l'explotació; veure A05).
Capa Què aporta
Consulta parametritzada / ORM Elimina la injecció d'origen
Validació per allowlist Rebutja entrada malformada abans d'arribar al motor
Mínim privilegi a la BD Redueix l'impacte si alguna cosa falla
Errors genèrics No donen pistes a l'atacant

  1. XSS: la injecció del navegador

El Cross-Site Scripting també és injecció, però l'intèrpret és el navegador (executa JavaScript injectat) en lloc de la base de dades. Per la seva importància i per les seves tècniques pròpies (codificació de sortida contextual, CSP, escapaments de React) el tractem en la seva pròpia lliçó: 03-04 – Cross-Site Scripting (XSS) en Profunditat. Aquí només deixem constància que comparteix la mateixa arrel: barrejar dades de l'usuari amb codi que un altre motor executarà.

Errors Comuns i Consells

  • Concatenar entrada en consultes. Encara que "sembli un número", parametritza sempre.
  • Confiar en llistes negres de caràcters. Són evitables; fes servir llistes blanques i parametrització.
  • Escapar a mà el SQL. És fràgil; deixa l'escapament al driver mitjançant paràmetres.
  • Fer servir l'ORM i després caure en raw query concatenada. Anul·la tota la protecció.
  • Passar entrada a una shell. Evita Runtime.exec(string)/child_process.exec; fes servir APIs amb arguments separats.
  • Compte de BD amb permisos totals. Aplica mínim privilegi: limita taules i operacions.
  • Consell: habilita un escàner SAST/DAST en CI (ho veurem amb ZAP a M6) per detectar concatenacions perilloses.

Exercicis

Exercici 1. Reescriu de manera segura aquest endpoint de BazarNube:

router.get('/api/orders', async (req, res) => {
  const status = req.query.status;
  const rows = await db.query(
    "SELECT * FROM orders WHERE user_id = " + req.session.userId +
    " AND status = '" + status + "'");
  res.json(rows.rows);
});

Exercici 2. El login del mòdul Java fa servir "... WHERE user='" + u + "' AND pass='" + p + "'". Mostra un payload que salti l'autenticació i explica per què la parametrització l'evita.

Exercici 3. Per què ProcessBuilder("convert", filename, ...) és més segur que Runtime.exec("convert " + filename)? Continua sent recomanable validar filename?

Solucions

Solució 1. Parametritzar tots dos valors (inclòs user_id, encara que vingui de la sessió, per consistència):

router.get('/api/orders', async (req, res) => {
  const rows = await db.query(
    'SELECT * FROM orders WHERE user_id = $1 AND status = $2',
    [req.session.userId, req.query.status]);
  res.json(rows.rows);
});

Solució 2. Amb u = admin' -- la consulta passa a ... WHERE user='admin' --' AND pass='...': el -- comenta la comprovació de contrasenya i l'atacant entra com a admin. Amb paràmetres, admin' -- es buscaria literalment com a nom d'usuari (no existeix), perquè el motor mai interpreta aquella cadena com a sintaxi SQL.

Solució 3. ProcessBuilder amb arguments separats no llança una shell, així que metacaràcters com ;, | o & es passen com a text literal a convert, no s'interpreten com a separadors d'ordres. Tot i així convé validar filename per llista blanca: evita rutes malicioses (../../etc/passwd) i entrades que trenquin l'eina. Defensa en profunditat.

Conclusió

La injecció es derrota separant codi de dades: consultes parametritzades o ORM per a SQL, APIs amb arguments separats per a ordres del SO, forçat de tipus per a NoSQL i escapament adequat per a LDAP, tot reforçat amb validació per llista blanca i mínim privilegi. És una defensa mecànica i molt efectiva: ben aplicada, elimina la categoria gairebé per complet.

Entrada de backlog — A03: parametritzat el cercador de productes i el llistat de comandes; substituït Runtime.exec per ProcessBuilder amb validació a ThumbnailService; forçat de tipus al login NoSQL; creat un compte de BD amb permisos mínims per a l'API.

Hem mencionat que el XSS és la injecció que s'executa al navegador. És tan rellevant per al front React de BazarNube que li dediquem la lliçó següent sencera: Cross-Site Scripting (XSS) en Profunditat.

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