Toate ghidurile

Securitate web pe înțeles: OWASP Top 10 cu exemple

Cele 10 categorii OWASP Top 10:2021 explicate cu cod vulnerabil, varianta corectă și un checklist minim de securitate.


Majoritatea vulnerabilităților din aplicațiile web nu sunt atacuri sofisticate, ci greșeli banale care se repetă de ani de zile: o verificare de permisiuni uitată, o interogare SQL construită prin concatenare, o parolă implicită nemodificată. OWASP Top 10 este lista de referință a celor mai importante categorii de riscuri și un punct de plecare excelent pentru orice developer. Ghidul de față le explică pe înțelesul tuturor, cu exemple de cod vulnerabil și varianta corectă.

Ce este OWASP Top 10 și ce ediție folosim

OWASP (Open Worldwide Application Security Project) este o fundație non-profit care publică resurse gratuite despre securitatea aplicațiilor. Top 10 este un document de conștientizare, actualizat o dată la câțiva ani, construit din date despre vulnerabilități colectate de la organizații și dintr-un sondaj în comunitate.

Ghidul urmează ediția 2021 (OWASP Top 10:2021), cea mai citată în cursuri, standarde interne și interviuri. OWASP a pregătit și o ediție nouă, Top 10:2025, care reorganizează câteva categorii și pune mai mult accent pe lanțul de aprovizionare software (supply chain). Principiile de mai jos rămân valabile; pentru lista curentă, consultă pagina oficială OWASP Top 10.

CodCategorie (ediția 2021)Pe scurt
A01Broken Access ControlUtilizatorii ajung la date sau acțiuni la care nu au drept
A02Cryptographic FailuresDate sensibile necriptate sau criptate greșit
A03InjectionDate de la utilizator interpretate drept cod (SQL, comenzi, XSS)
A04Insecure DesignLipsuri de securitate chiar din proiectarea funcționalității
A05Security MisconfigurationSetări implicite, erori detaliate, servicii expuse
A06Vulnerable and Outdated ComponentsDependențe cu vulnerabilități cunoscute
A07Identification and Authentication FailuresLogin, parole și sesiuni implementate greșit
A08Software and Data Integrity FailuresCod sau date folosite fără verificarea integrității
A09Security Logging and Monitoring FailuresAtacurile nu sunt înregistrate sau nu le observă nimeni
A10Server-Side Request Forgery (SSRF)Serverul este păcălit să facă cereri în numele atacatorului

A01: Broken Access Control

Este pe primul loc, și pe bună dreptate. Utilizatorul este logat, dar aplicația nu verifică dacă are voie la resursa cerută. Cel mai frecvent caz este IDOR (Insecure Direct Object Reference): schimbi un ID din URL și vezi datele altcuiva.

// Vulnerabil: orice utilizator logat poate citi orice factură
app.get('/api/invoices/:id', requireLogin, async (req, res) => {
  const invoice = await db.invoices.findById(req.params.id);
  res.json(invoice);
});

// Corect: factura trebuie să aparțină utilizatorului curent
app.get('/api/invoices/:id', requireLogin, async (req, res) => {
  const invoice = await db.invoices.findOne({
    id: req.params.id,
    ownerId: req.user.id,
  });
  if (!invoice) return res.status(404).end();
  res.json(invoice);
});

Cum te aperi:

  • Refuză implicit (deny by default) și verifică permisiunile pe server, la fiecare cerere. Un buton ascuns în interfață nu este control de acces.
  • Centralizează regulile de autorizare în middleware sau politici, în loc să le repeți prin fiecare controller.
  • Scrie teste explicite de tipul „poate utilizatorul A să citească, să modifice sau să șteargă resursele lui B?”.
  • Tot în această categorie intră și CSRF, când un alt site trimite cereri în numele utilizatorului logat. Cookie-urile cu SameSite=Lax sau Strict și token-urile anti-CSRF pentru formulare îl previn.

A02: Cryptographic Failures

Date sensibile transmise sau stocate fără protecție adecvată: parole salvate cu MD5, date de card în clar în baza de date, pagini de login servite pe HTTP, chei de criptare scrise direct în cod.

Cum te aperi:

  • HTTPS peste tot, plus header-ul Strict-Transport-Security.
  • Parolele se stochează doar ca hash cu Argon2id, scrypt sau bcrypt, niciodată cu MD5 sau SHA-1.
  • Criptează datele sensibile și în repaus (at rest), iar cheile ține-le într-un secret manager, nu în repository.
  • Nu inventa algoritmi criptografici proprii. Folosește biblioteci standard și construcții moderne, precum AES-GCM.
  • Nu colecta date de care nu ai nevoie. Datele pe care nu le ai nu pot fi furate, iar principiul te ajută și cu GDPR.

A03: Injection

Apare când date venite de la utilizator ajung într-un interpretor (baza de date, shell-ul, browserul) și sunt tratate drept cod. În ediția 2021, categoria include și XSS (Cross-Site Scripting).

SQL injection

// Vulnerabil: pentru email = "' OR '1'='1" interogarea returnează toți utilizatorii
const result = await pool.query(
  `SELECT * FROM users WHERE email = '${email}'`
);

// Corect: interogare parametrizată (node-postgres)
const result = await pool.query(
  'SELECT * FROM users WHERE email = $1',
  [email]
);

Parametrii sunt trimiși separat de textul interogării, așa că baza de date nu îi poate confunda cu SQL. ORM-urile fac asta implicit, dar atenție la metodele lor „raw”, prin care poți reintroduce problema.

XSS

// Vulnerabil: un comentariu care conține <img src=x onerror="..."> execută cod
commentEl.innerHTML = comment.text;

// Corect: conținutul este afișat ca text, nu interpretat ca HTML
commentEl.textContent = comment.text;

Framework-urile moderne (React, Vue, Angular) escapează automat valorile afișate, dar portițe există în continuare: dangerouslySetInnerHTML în React, v-html în Vue sau atribute href construite din input, prin care se poate strecura un link javascript:. Dacă trebuie să afișezi HTML venit de la utilizatori, trece-l printr-un sanitizer precum DOMPurify. Ca a doua linie de apărare, configurează un Content Security Policy, care îi spune browserului de unde are voie să încarce scripturi:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

Aceeași logică se aplică la command injection: nu construi comenzi de shell din input. În Node.js, preferă execFile cu argumentele într-un array în locul lui exec cu un string concatenat.

A04: Insecure Design

Aici problema nu este un bug de implementare, ci lipsa unui control de securitate încă din proiectare. Câteva exemple:

  • Resetarea parolei pe baza unor „întrebări de securitate” ale căror răspunsuri se găsesc pe rețelele sociale.
  • Un cod de reducere de 6 cifre fără limită de încercări, care poate fi ghicit prin forță brută.
  • Un flux de checkout în care prețul vine din frontend, iar serverul îl acceptă ca atare.

Cum te aperi: threat modeling încă din faza de design (ce poate merge prost? cine ar abuza de funcționalitate și cum?), limite de rată pentru orice acțiune sensibilă, validarea regulilor de business pe server și scenarii de abuz incluse în teste, nu doar scenariile fericite.

A05: Security Misconfiguration

Codul poate fi scris corect, iar aplicația tot vulnerabilă, din cauza configurării:

  • conturi și parole implicite rămase active pe panouri de administrare, baze de date sau echipamente de rețea;
  • mesaje de eroare cu stack trace-uri afișate în producție;
  • bucket-uri de stocare în cloud lăsate publice din greșeală;
  • mod debug activ, directory listing, endpoint-uri de administrare expuse public;
  • lipsa header-elor de securitate.

Un set minim de header-e pentru o aplicație web:

Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

În ediția 2021, tot aici intră și XXE (XML External Entities): parsere XML configurate să rezolve entități externe, prin care un atacator poate citi fișiere de pe server. Dezactivează această funcție dacă procesezi XML.

În general, te aperi prin configurații identice și automatizate pentru toate mediile (infrastructure as code), prin dezactivarea a tot ce nu folosești și prin verificări periodice ale configurației.

A06: Vulnerable and Outdated Components

O aplicație modernă are sute de dependențe directe și indirecte, iar oricare dintre ele poate avea vulnerabilități cunoscute. Exemplul celebru este Log4Shell (CVE-2021-44228), o vulnerabilitate din biblioteca Java Log4j care permitea execuția de cod de la distanță și a afectat un număr uriaș de sisteme.

Primul pas este să afli ce vulnerabilități cunoscute ai deja:

# JavaScript
npm audit

# Python
pip-audit

Apoi:

  • Rulează scanarea dependențelor automat în CI și activează actualizările automate (Dependabot, Renovate).
  • Commit-uiește lockfile-ul (package-lock.json, pnpm-lock.yaml, poetry.lock), ca build-urile să fie reproductibile.
  • Elimină dependențele nefolosite și verifică dacă un pachet mai este întreținut înainte să-l adaugi.
  • Actualizează și imaginile Docker de bază, runtime-ul (Node.js, Python, Java) și sistemul de operare, nu doar pachetele aplicației.

A07: Identification and Authentication Failures

Probleme în login și în gestionarea sesiunilor:

  • lipsa limitării încercărilor, care permite credential stuffing, adică testarea automată a perechilor e-mail/parolă scurse din alte breșe;
  • parole slabe acceptate sau stocate nesigur;
  • ID-uri de sesiune în URL, neregenerate la login sau neinvalidate la logout;
  • lipsa MFA pentru conturile cu privilegii.

Cum te aperi: rate limiting și detecția comportamentului anormal la login, verificarea parolelor noi față de liste de parole compromise, MFA, passkeys, cookie-uri de sesiune HttpOnly, Secure și SameSite, plus regenerarea ID-ului de sesiune după autentificare. Cel mai sigur: nu scrie de la zero logica de autentificare dacă un framework sau un provider matur o oferă deja.

A08: Software and Data Integrity Failures

Cod sau date folosite fără verificarea faptului că nu au fost modificate: actualizări nesemnate, pipeline-uri CI/CD la care are acces oricine, deserializarea nesigură a datelor primite de la client, scripturi încărcate de pe CDN-uri terțe fără nicio verificare.

Pentru scripturile externe, Subresource Integrity (SRI) garantează că browserul execută exact fișierul la care te aștepți. Valoarea din integrity este hash-ul SHA-384 al fișierului, codificat base64; dacă fișierul de pe CDN se schimbă, browserul refuză să-l execute:

<script
  src="https://cdn.example.com/lib@1.2.3/lib.min.js"
  integrity="sha384-..."
  crossorigin="anonymous"></script>

Alte măsuri: protejează pipeline-ul de CI/CD (acces minim, secrete gestionate corect, branch protection), semnează artefactele de build și nu deserializa obiecte arbitrare primite din rețea.

A09: Security Logging and Monitoring Failures

Dacă nu înregistrezi și nu urmărești evenimentele importante, un atac poate dura luni fără ca cineva să-l observe.

Ce merită logat:

  • login-urile reușite și eșuate, resetările de parolă, schimbările de permisiuni;
  • eșecurile de autorizare, de exemplu cineva care încearcă sistematic ID-uri care nu-i aparțin;
  • erorile de validare apărute în volum neobișnuit.

Ce nu loghezi niciodată: parole, token-uri, ID-uri de sesiune, date de card sau alte date personale de care nu ai nevoie.

{
  "timestamp": "2026-10-25T09:14:03Z",
  "event": "auth.login_failed",
  "userId": null,
  "ip": "198.51.100.23",
  "reason": "invalid_credentials",
  "requestId": "req_7f3a2b"
}

Logurile structurate (JSON) sunt ușor de căutat și de agregat. La fel de important: cineva trebuie să primească alerte când apar tipare suspecte, iar logurile trebuie păstrate într-un loc din care un atacator nu le poate șterge ușor.

A10: Server-Side Request Forgery (SSRF)

Apare când aplicația descarcă o resursă de la un URL primit de la utilizator (previzualizări de link-uri, import de imagini după URL, webhooks) fără restricții. Atacatorul îi dă un URL intern, iar serverul, aflat în rețeaua privată, face cererea în locul lui.

// Vulnerabil: serverul accesează orice URL primit
app.post('/api/preview', async (req, res) => {
  const response = await fetch(req.body.url);
  res.send(await response.text());
});

Un URL precum http://169.254.169.254/ țintește serviciul de metadate al instanțelor din cloud, care poate expune credențiale temporare, iar http://localhost:6379 poate atinge un Redis intern.

Cum te aperi:

  • Folosește o listă de domenii permise (allowlist) oriunde se poate.
  • Altfel, rezolvă numele prin DNS, blochează adresele private, loopback și link-local (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.0.0/16 și echivalentele IPv6), apoi conectează-te exact la adresa verificată, ca să eviți trucurile de tip DNS rebinding.
  • Nu urma automat redirecționările sau verifică din nou fiecare destinație.
  • Pe AWS, folosește IMDSv2, care cere un token de sesiune pentru accesul la metadate.
  • Nu returna utilizatorului răspunsul brut al cererii interne.

Checklist minim pentru orice proiect

ZonăVerificare
AccesFiecare endpoint verifică autentificarea și proprietatea resursei
InputInterogări parametrizate, validare după schemă, fără innerHTML cu date externe
TransportHTTPS peste tot, HSTS, cookie-uri Secure, HttpOnly, SameSite
ParoleArgon2id sau bcrypt, rate limiting la login, MFA disponibil
Dependențenpm audit sau echivalent în CI, actualizări automate
ConfigurareFără parole implicite, fără stack trace-uri în producție, header-e de securitate
SecreteNiciun secret în repository; secret manager sau variabile de mediu
LoguriEvenimentele de securitate sunt logate și declanșează alerte

Pentru detalii de implementare pe fiecare subiect, OWASP Cheat Sheet Series este cea mai practică resursă gratuită.

Pașii următori

  • Testează-ți propriile aplicații: încearcă IDOR schimbând ID-uri, introdu ' OR '1'='1 în formulare, verifică header-ele de securitate în DevTools. Fă asta doar pe aplicațiile tale sau în laboratoare create special, precum OWASP Juice Shop.
  • Pentru partea defensivă și imaginea de ansamblu, urmează roadmap-ul Cyber Security; pentru monitorizare, detecție și răspuns la incidente, roadmap-ul SOC Analyst.
  • Dacă ești developer, transformă checklist-ul de mai sus în parte din code review și aprofundează practicile de server în roadmap-ul Backend.