Riscuri de securitate

Bazele de date conțin adesea date sensibile și permit efectuarea de operațiuni periculoase. Pentru a lucra în siguranță cu Nette Database, aspectele cheie sunt:

  • Înțelegerea diferenței dintre API securizat și nesigur
  • Utilizarea interogărilor parametrizate
  • Validarea corespunzătoare a datelor de intrare

Ce este injecția SQL?

Injecția SQL este cel mai grav risc de securitate atunci când se lucrează cu baze de date. Aceasta apare atunci când datele nefiltrate introduse de utilizator devin parte a unei interogări SQL. Un atacator își poate introduce propriile comenzi SQL și astfel:

  • extrage date neautorizate
  • Modificarea sau ștergerea datelor din baza de date
  • Ocolirea autentificării
// ❌ COD PERICULOS - vulnerabil la injectarea SQL
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");

// Un atacator ar putea introduce o valoare de genul: ' OR '1'='1
// Interogarea rezultată ar fi: SELECT * FROM users WHERE name = '' OR '1'='1'
// Care returnează toți utilizatorii

Același lucru este valabil și pentru Database Explorer:

// ❌ COD PERICULOS - vulnerabil la injectarea SQL
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");

Interogări parametrizate securizate

Apărarea fundamentală împotriva injecției SQL este reprezentată de interogările parametrizate. Nette Database oferă mai multe modalități de utilizare a acestora.

Cea mai simplă modalitate este de a utiliza semne de întrebare:

// ✅ Interogare parametrizată securizată
$database->query('SELECT * FROM users WHERE name = ?', $name);

// ✅ Condiție sigură în Explorer
$table->where('name = ?', $name);

Acest lucru se aplică tuturor celorlalte metode din Database Explorer care permit inserarea de expresii cu marcaje de întrebare și parametri.

Pentru clauzele INSERT, UPDATE, sau WHERE, puteți trece valorile într-un array:

// ✅ Secure INSERT
$database->query('INSERT INTO users', [
	'name' => $name,
	'email' => $email,
]);

// ✅ Secure INSERT în Explorer
$table->insert([
	'name' => $name,
	'email' => $email,
]);

Validarea valorii parametrilor

Interogările parametrizate reprezintă piatra de temelie a activității sigure în bazele de date. Cu toate acestea, valorile trecute în acestea trebuie să treacă prin mai multe niveluri de validare:

Verificarea tipului

Asigurarea tipului corect de date al parametrilor este esențială – aceasta este o condiție necesară pentru utilizarea în siguranță a bazei de date Nette. Baza de date presupune că toate datele de intrare au tipul de date corect corespunzător coloanei.

De exemplu, dacă $name din exemplele anterioare a devenit în mod neașteptat un array în loc de un string, Nette Database ar încerca să introducă toate elementele sale în interogarea SQL, ceea ce ar duce la o eroare. Prin urmare, nu utilizați niciodată date nevalidate din $_GET, $_POST, sau $_COOKIE direct în interogările bazei de date.

Validarea formatului

Al doilea nivel verifică formatul datelor – de exemplu, asigurându-se că șirurile de caractere sunt codate UTF-8 și că lungimea lor corespunde definiției coloanei sau verificând dacă valorile numerice se încadrează în intervalul permis pentru tipul de date al coloanei.

La acest nivel, vă puteți baza parțial pe baza de date în sine – multe baze de date resping datele invalide. Cu toate acestea, comportamentul poate varia: unele pot trunchia în mod silențios șirurile lungi sau pot tăia numerele care sunt în afara intervalului.

Validarea specifică domeniului

Al treilea nivel implică verificări logice specifice aplicației dumneavoastră. De exemplu, verificarea faptului că valorile din casetele de selectare corespund opțiunilor disponibile, că numerele se încadrează într-un interval așteptat (de exemplu, vârsta 0–150 de ani) sau că relațiile dintre valori au sens.

  • Utilizați Nette Forms, care gestionează automat validarea corectă a tuturor intrărilor.
  • Utilizați Presenters și declarați tipurile de date ale parametrilor în metodele action*() și render*().
  • Sau implementați un strat de validare personalizat folosind instrumente PHP standard precum filter_var().

Lucrul sigur cu coloanele

În secțiunea anterioară, am acoperit modul de validare corespunzătoare a valorilor parametrilor. Cu toate acestea, atunci când se utilizează matrici în interogările SQL, trebuie acordată aceeași atenție cheilor acestora.

// ❌ COD PERICULOS - cheile array nu sunt salubrizate
$database->query('INSERT INTO users', $_POST);

Pentru comenzile INSERT și UPDATE, acesta este un defect de securitate major – un atacator poate introduce sau modifica orice coloană din baza de date. Acesta ar putea, de exemplu, să seteze is_admin = 1 sau să introducă date arbitrare în coloane sensibile (cunoscută sub numele de Vulnerabilitatea atribuirii în masă).

În condițiile WHERE, este și mai periculos, deoarece acestea pot conține operatori:

// ❌ COD PERICULOS - cheile array nu sunt salubrizate
$_POST['salary >'] = 100000;
$database->query('SELECT * FROM users WHERE', $_POST);
// execută interogarea WHERE (`salary` > 100000)

Un atacator poate utiliza această abordare pentru a descoperi sistematic salariile angajaților. Ar putea începe cu o interogare pentru salarii mai mari de 100 000, apoi mai mici de 50 000, iar prin restrângerea treptată a intervalului, poate dezvălui salariile aproximative ale tuturor angajaților. Acest tip de atac se numește enumerare SQL.

Metodele where() și whereOr() sunt și mai flexibile și acceptă expresii SQL, inclusiv operatori și funcții, atât în chei, cât și în valori. Acest lucru oferă unui atacator posibilitatea de a efectua injecții SQL complexe:

// ❌ COD PERICULOS - atacatorul își poate introduce propriul SQL
$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1'];
$table->where($_POST);
// execută interogarea WHERE (0) UNION SELECT nume, salariu FROM utilizatori WHERE (1)

Acest atac încheie condiția inițială cu 0), adaugă propriul SELECT folosind UNION pentru a obține date sensibile din tabelul users și se încheie cu o interogare corectă din punct de vedere sintactic folosind WHERE (1).

Lista albă a coloanelor

Pentru a lucra în siguranță cu numele coloanelor, aveți nevoie de un mecanism care să garanteze că utilizatorii pot interacționa numai cu coloanele permise și nu le pot adăuga pe cele proprii. Încercarea de a detecta și de a bloca numele de coloane periculoase (lista neagră) nu este fiabilă – un atacator poate găsi întotdeauna o nouă modalitate de a scrie un nume de coloană periculos pe care nu ați anticipat-o.

Prin urmare, este mult mai sigur să inversați logica și să definiți o listă explicită de coloane permise (whitelisting):

// Coloane pe care utilizatorul este autorizat să le modifice
$allowedColumns = ['name', 'email', 'active'];

// Eliminați toate coloanele neautorizate din intrare
$filteredData = array_intersect_key($userData, array_flip($allowedColumns));

// ✅ Acum se poate utiliza în siguranță în interogări, cum ar fi:
$database->query('INSERT INTO users', $filteredData);
$table->update($filteredData);
$table->where($filteredData);

Identificatori dinamici

Pentru numele dinamice ale tabelelor și coloanelor, utilizați marcajul ?name. Acest lucru asigură scăparea corespunzătoare a identificatorilor în conformitate cu sintaxa bazei de date respective (de exemplu, utilizarea ghilimelelor în MySQL):

// ✅ Utilizarea în siguranță a identificatorilor de încredere
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// Rezultat în MySQL: SELECT `name` FROM `users`

Important: utilizați simbolul ?name numai pentru valorile de încredere definite în codul aplicației. Pentru valorile furnizate de utilizator, utilizați din nou o listă albă. În caz contrar, riscați vulnerabilități de securitate:

// ❌ PERICULOS - nu folosiți niciodată datele introduse de utilizator
$database->query('SELECT ?name FROM users', $_GET['column']);
versiune: 4.0