SQL Injection — un tip de atac asupra bazei de date a aplicației, prin care atacatorul injectează cod SQL malițios în parametrii interogării, obținând acces neautorizat la date sau posibilitatea de a le modifica. Conform datelor OWASP (2025), SQL Injection rămâne una dintre vulnerabilitățile critice, putând duce la compromiterea totală a bazei de date. Injectarea codului SQL permite atacatorului să citească, să modifice și să șteargă înregistrări, iar în unele cazuri — să obțină acces la sistemul de operare al serverului.
Principalele puncte
SQL Injection este o vulnerabilitate care apare atunci când aplicația formează o interogare SQL prin concatenarea șirurilor cu datele utilizatorului. Atacatorul transmite un parametru de interogare un șir special construit care modifică structura comenzii SQL. În loc să fie date, șirul introdus devine parte a codului SQL, permițând atacatorului să execute interogări arbitrare în baza de date. Conform Verizon Data Breach Report (2025), SQL Injection este prezent în 8% din toate scurgerile de date investigate.
În ciuda cunoașterii vulnerabilității (primele mențiuni — sfârșitul anilor 1990), SQL Injection apare în aplicațiile moderne. Motivul — factorul uman: dezvoltatorii permit concatenarea șirurilor în cod, codul legacy nu este refactorizat, iar framework-urile ORM sunt utilizate incorect (de exemplu, interogări raw cu substituirea valorilor prin șiruri). Conform datelor Veracode (2026), aproximativ 14% din toate aplicațiile scanate conțin cel puțin o vulnerabilitate SQLi.
O injecție SQL reușită oferă atacatorului o gamă largă de posibilități: citirea oricăror tabele ale bazei de date, inclusiv hash-uri de parole și date personale ale utilizatorilor; modificarea și ștergerea înregistrărilor; executarea operațiunilor administrative (DROP TABLE, TRUNCATE); în unele configurații — executarea de la distanță a comenzilor prin xp_cmdshell (MSSQL) sau INTO OUTFILE (MySQL). Consecințele — de la scurgerea datelor utilizatorilor până la pierderea totală a controlului asupra sistemului.
Injecțiile SQL se clasifică după metoda de extragere a datelor din baza de date. Alegerea metodei depinde de modul în care aplicația procesează rezultatele interogării și mesajele de eroare. Clasificarea OWASP distinge trei tipuri principale: In-band (datele extrase prin același canal), Inferential/Blind (deducere logică) și Out-of-band (datele transmise printr-un alt canal).
| Tip | Metoda de extragere a datelor | Complexitate | Frecvență |
|---|---|---|---|
| In-band (clasic) | Direct prin rezultatul interogării | Scăzută | Ridicată |
| Blind SQLi | Deducere logică după răspunsurile serverului | Ridicată | Medie |
| Out-of-band | Printr-un canal extern (DNS, HTTP) | Medie | Scăzută |
Cel mai răspândit tip. Atacatorul injectează cod SQL în parametrul interogării, iar rezultatul injectării este vizibil direct în răspunsul serverului. Două subtipuri: Error-based (prin mesajele de eroare ale BD) și UNION-based (prin operatorul UNION SELECT). Error-based folosește informațiile din mesajele de eroare, de exemplu eroarea de sintaxă MySQL care poate dezvălui numele tabelului sau structura interogării. UNION-based permite combinarea rezultatelor unei interogări legitime cu date din alte tabele ale bazei de date.
Folosită când aplicația nu afișează rezultatele interogării sau mesajele de eroare. Atacatorul pune întrebări de tip „da/nu”, trimițând interogări cu condiții logice și analizând diferența în răspunsul serverului (de exemplu, timpul de răspuns sau conținutul paginii). Time-based Blind SQLi folosește funcția de întârziere (SLEEP, WAITFOR DELAY) pentru confirmarea condițiilor — dacă pagina se încarcă mai mult, condiția este adevărată. Această metodă este foarte lentă — extragerea unei înregistrări poate dura ore.
# Exemplu Blind SQL Injection (time-based)
# Dacă SQLi este vulnerabil, SLEEP(2) se execută sub condiție
import requests
import time
payload = "' OR IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a',SLEEP(2),0) -- "
url = "http://example.com/user?id=1" + payload
start = time.time()
response = requests.get(url)
elapsed = time.time() - start
# Dacă răspunsul a venit după >2 secunde — prima literă a parolei este 'a'
print(f"Time: {elapsed:.2f}s - First letter is 'a'" if elapsed > 2 else "First letter is not 'a'")
Datele sunt transmise nu prin răspunsul HTTP, ci prin canale alternative: interogări DNS, cereri HTTP către un server extern, SMTP. Folosită când aplicația nu returnează rezultatele interogării și nu arată erori. MySQL suportă funcția LOAD_FILE(), care poate iniția o interogare DNS, iar MSSQL — xp_dirtree pentru trimiterea datelor către un server SMB la distanță. Out-of-band SQL Injection este eficient, dar necesită configurații suplimentare pe serverul atacatorului și prezența anumitor funcții ale BD.
Mecanismul SQL Injection se bazează pe faptul că SQL folosește ghilimele pentru literalii de șir. Dacă aplicația substituie datele de intrare ale utilizatorului direct în interogarea SQL fără escapare, atacatorul poate „închide” șirul și adăuga cod SQL arbitrar. De exemplu, în interogarea SELECT * FROM users WHERE name = '$input' substituirea ' OR '1'='1 transformă interogarea în SELECT * FROM users WHERE name = '' OR '1'='1', ceea ce returnează toți utilizatorii.
Să considerăm un formular de login cu interogarea SELECT * FROM users WHERE username = '$user' AND password = '$pass'. Dacă atacatorul introduce în câmpul username admin' -- , iar parola o lasă goală, interogarea rezultată devine SELECT * FROM users WHERE username = 'admin' -- ' AND password = ''. Caracterele -- comentează restul interogării, iar condiția de verificare a parolei este dezactivată. Serverul returnează înregistrarea utilizatorului admin, iar atacatorul se conectează fără a cunoaște parola.
# Exemplu SQL Injection — ocolirea autentificării
# COD VULNERABIL: concatenare directă de șiruri
def login_vulnerable(username, password):
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
return cursor.fetchone() is not None
# username = "admin' --" anulează verificarea parolei
# VERSIUNEA SIGURĂ: interogare parametrizată
def login_secure(username, password):
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))
return cursor.fetchone() is not None
Operatorul UNION SELECT permite combinarea rezultatelor a două interogări SELECT. Dacă atacatorul găsește un parametru vulnerabil, adaugă UNION SELECT cu o interogare dintr-un alt tabel. De exemplu: ' UNION SELECT username, password FROM admins --. Condiția de succes — numărul de coloane în ambele interogări trebuie să coincidă. Numărul de coloane se determină prin ORDER BY (substituirea ' ORDER BY 1--, apoi 2, 3... până la eroare). Cunoscând numărul de coloane, atacatorul substituie UNION SELECT cu același număr de câmpuri.
Aplicațiile mobile se confruntă cu SQL Injection atât pe partea de server (API), cât și pe client — în bazele de date locale (SQLite, Realm). Deși riscul SQLi pe server în API-ul mobil este similar aplicațiilor web, baza de date locală creează un vector suplimentar. Dacă aplicația stochează date în SQLite și execută interogări cu concatenare de șiruri, datele malițioase care au intrat în baza de date locală prin API pot provoca SQLi la procesarea ulterioară.
Baza de date SQLite locală pe dispozitiv este, de asemenea, vulnerabilă la SQL Injection dacă aplicația formează interogări prin concatenarea șirurilor. Content Providers pe Android și Core Data pe iOS folosesc parametrizarea implicit, dar interogările raw necesită atenția dezvoltatorului. SQLite nu suportă interogări multiple prin punct și virgulă, ceea ce limitează posibilitățile atacatorului, dar nu protejează împotriva citirii datelor prin condiții WHERE. Folosește întotdeauna selectionArgs în Android și NSPredicate cu parametri în iOS.
API-ul la care se conectează aplicația mobilă este la fel de vulnerabil ca orice server web. Dezvoltatorii mobili consideră adesea că SQLi este doar o problemă a backend-ului, dar vulnerabilitatea apare la nivelul endpoint-ului API care primește parametri de la client. Separarea responsabilităților nu protejează: dacă dezvoltatorul backend-ului a uitat să parametrizeze interogarea, aplicația mobilă a utilizatorului devine un vector de atac. Cereți backend-ului să folosească ORM sau prepared statements.
// Exemplu SQL Injection în SQLite local pe Android
// COD VULNERABIL: concatenare directă
fun getUserVulnerable(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = $userId", null
)
}
// VERSIUNEA SIGURĂ: parametrizare prin selectionArgs
fun getUserSecure(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = ?",
arrayOf(userId)
)
}
Protecția împotriva SQL Injection se bazează pe un principiu simplu: nu aveți niciodată încredere în datele de intrare ale utilizatorului în interogările SQL. Singura metodă fiabilă — parametrizarea interogărilor (prepared statements), în care codul SQL și datele sunt transmise separat. Toate celelalte metode — escaparea, validarea, WAF — sunt straturi suplimentare de protecție, dar nu înlocuiesc parametrizarea. Conform datelor OWASP (2025), parametrizarea previne 100% din atacurile SQL Injection.
Când se folosesc prepared statements, interogarea SQL este mai întâi compilată de serverul BD fără date, iar apoi valorile parametrilor sunt transmise separat. Baza de date tratează parametrii ca date, nu ca cod executabil. Chiar dacă atacatorul transmite ' OR '1'='1, BD interpretează aceasta ca o valoare de șir, nu ca cod SQL. Prepared statements sunt suportate de toate limbajele și framework-urile moderne: PDO în PHP, PreparedStatement în Java, cursor.execute în Python.
ORM-urile moderne (Hibernate, Entity Framework, SQLAlchemy, Room) folosesc automat parametrizarea la executarea interogărilor, dacă dezvoltatorul nu trece la interogări raw. Totuși, ORM nu protejează complet: construcții precum @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true) în JPA necesită transmiterea parametrilor prin parametri numiți, nu prin concatenare. Query Builder-ii (Knex, jOOQ) parametrizează și ei interogările implicit, dacă nu se folosesc metode raw.
Escaparea caracterelor speciale (mysql_real_escape_string) — o metodă învechită care nu protejează împotriva tuturor tipurilor de SQL Injection. Problema: escaparea depinde de codificare și poate fi ocolită folosind codificări multi-byte (de exemplu, GBK în sistemele asiatice). Folosiți escaparea doar în codul legacy unde parametrizarea nu este posibilă, și întotdeauna împreună cu validarea strictă a tipului datelor de intrare.
| Metodă | Eficiență | Recomandare |
|---|---|---|
| Prepared Statements | 100% | Obligatoriu pentru toate interogările |
| ORM (utilizare corectă) | 99% | Recomandat |
| Escaparea șirurilor | 70% (depinde de codificare) | Doar legacy |
| Validarea intrării (white-list) | 50% (doar pentru numere) | Suplimentar |
| WAF (Web Application Firewall) | 60% | Suplimentar |
Contul aplicației trebuie să aibă privilegii minim necesare: SELECT, INSERT, UPDATE, DELETE — doar pe tabelele care sunt cu adevărat necesare aplicației. Interziceți utilizarea DROP, TRUNCATE, CREATE pentru contul aplicației. Aceasta va limita daunele chiar și în cazul unui SQL Injection reușit: atacatorul nu va putea șterge tabele sau executa operațiuni administrative.
Testarea regulată pentru SQL Injection ar trebui să fie parte a pipeline-ului de dezvoltare securizată. Combinația de analiză statică, scanare dinamică și testare de penetrare manuală oferă cele mai bune rezultate. Conform Synopsys Cybersecurity Report (2025), scanerele automate găsesc până la 70% din vulnerabilitățile SQLi, dar atacurile complexe Blind necesită testare manuală.
Pentru aplicațiile mobile, este importantă și analiza SQLite local: verificați toate apelurile rawQuery, interogările ContentProvider și interogările Room cu rawQuery. Instrumente: Android Studio Lint (detectează SQLi în SQLite), MobSF (Mobile Security Framework) pentru analiză statică și dinamică automată a APK/IPA. Se recomandă, de asemenea, testarea endpoint-urilor API prin sqlmap cu proxy de interceptare a traficului aplicației mobile.
Întrebări frecvente
SQL Injection atacă bazele de date relaționale prin interogări SQL. NoSQL Injection acționează asupra bazelor de date non-relaționale (MongoDB, Couchbase) prin operatorii lor de interogare ($gte, $ne, $where). În MongoDB injecția este posibilă dacă aplicația formează un document BSON dintr-un șir JSON. Mecanismele de protecție sunt similare: parametrizarea și validarea tipurilor.
Găsiți toate locurile unde interogările SQL sunt formate prin concatenarea șirurilor cu datele utilizatorului. Căutați pattern-uri precum "SELECT ... WHERE id = " + userId sau f"UPDATE ... SET name = '{name}'". Fiecare astfel de șir este o potențială injecție SQL. Înlocuiți-le pe toate cu interogări parametrizate sau prepared statements.
Framework-urile ORM protejează automat doar dacă folosiți metodele lor Query Builder și parametrii numiți. Dacă folosiți interogări raw (nativeQuery în JPA, rawQuery în Room), protecția nu funcționează — trebuie să transmiteți parametrii prin expresii pregătite, nu prin concatenare de șiruri.
Second-Order SQL Injection este un atac în care datele malițioase sunt stocate în BD ca sigure, dar apoi utilizate într-o altă interogare fără escapare. De exemplu, atacatorul se înregistrează cu username ' OR '1'='1. Datele sunt stocate ca șir — la înregistrare nu există atac. Dar dacă o altă interogare substituie username în SQL fără parametrizare, injecția se activează.
NoSQL Injection este potențial mai periculoasă din cauza conștientizării mai reduse a dezvoltatorilor. Dezvoltatorii știu despre SQL Injection și majoritatea folosesc ORM, dar despre NoSQL Injection știu puțini. În MongoDB o interogare formatată incorect poate returna toate documentele colecției. Protecția — aceleași prepared statements (parametrizare BSON) și validarea strictă a datelor de intrare.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și