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.
| Cod | Categorie (ediția 2021) | Pe scurt |
|---|---|---|
| A01 | Broken Access Control | Utilizatorii ajung la date sau acțiuni la care nu au drept |
| A02 | Cryptographic Failures | Date sensibile necriptate sau criptate greșit |
| A03 | Injection | Date de la utilizator interpretate drept cod (SQL, comenzi, XSS) |
| A04 | Insecure Design | Lipsuri de securitate chiar din proiectarea funcționalității |
| A05 | Security Misconfiguration | Setări implicite, erori detaliate, servicii expuse |
| A06 | Vulnerable and Outdated Components | Dependențe cu vulnerabilități cunoscute |
| A07 | Identification and Authentication Failures | Login, parole și sesiuni implementate greșit |
| A08 | Software and Data Integrity Failures | Cod sau date folosite fără verificarea integrității |
| A09 | Security Logging and Monitoring Failures | Atacurile nu sunt înregistrate sau nu le observă nimeni |
| A10 | Server-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=LaxsauStrictș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 |
|---|---|
| Acces | Fiecare endpoint verifică autentificarea și proprietatea resursei |
| Input | Interogări parametrizate, validare după schemă, fără innerHTML cu date externe |
| Transport | HTTPS peste tot, HSTS, cookie-uri Secure, HttpOnly, SameSite |
| Parole | Argon2id sau bcrypt, rate limiting la login, MFA disponibil |
| Dependențe | npm audit sau echivalent în CI, actualizări automate |
| Configurare | Fără parole implicite, fără stack trace-uri în producție, header-e de securitate |
| Secrete | Niciun secret în repository; secret manager sau variabile de mediu |
| Loguri | Evenimentele 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.