Varnostna tveganja

Baza podatkov pogosto vsebuje občutljive podatke in omogoča izvajanje nevarnih operacij. Za varno delo z Nette Database je ključno:

  • Razumeti razliko med varnim in nevarnim API-jem
  • Uporabljati parametrizirane poizvedbe
  • Pravilno validirati vhodne podatke

Kaj je SQL Injection?

SQL injection je najresnejše varnostno tveganje pri delu z bazo podatkov. Nastane, ko neobdelan vnos uporabnika postane del SQL poizvedbe. Napadalec lahko vstavi lastne SQL ukaze in s tem:

  • Pridobi nepooblaščen dostop do podatkov
  • Spremeni ali izbriše podatke v bazi podatkov
  • Obide avtentikacijo
// ❌ NEVARNA KODA - ranljiva za SQL injection
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");

// Napadalec lahko vnese na primer vrednost: ' OR '1'='1
// Rezultatna poizvedba bo potem: SELECT * FROM users WHERE name = '' OR '1'='1'
// Kar vrne vse uporabnike

Enako velja tudi za Database Explorer:

// ❌ NEVARNA KODA - ranljiva za SQL injection
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");

Parametrizirane poizvedbe

Osnovna obramba pred SQL injection so parametrizirane poizvedbe. Nette Database ponuja več načinov njihove uporabe.

Najenostavnejši način je uporaba nadomestnih vprašajev:

// ✅ Varna parametrizirana poizvedba
$database->query('SELECT * FROM users WHERE name = ?', $name);

// ✅ Varen pogoj v Explorerju
$table->where('name = ?', $name);

To velja za vse druge metode v Database Explorer, ki omogočajo vstavljanje izrazov z nadomestnimi vprašaji in parametri.

Za ukaze INSERT, UPDATE ali klavzulo WHERE lahko vrednosti posredujemo v polju:

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

// ✅ Varen INSERT v Explorerju
$table->insert([
	'name' => $name,
	'email' => $email,
]);

Validacija vrednosti parametrov

Parametrizirane poizvedbe so osnovni gradnik varnega dela z bazo podatkov. Vendar pa morajo vrednosti, ki jih vstavljamo vanje, preiti več ravni preverjanj:

Tipska kontrola

Najpomembnejše je zagotoviti pravilen podatkovni tip parametrov – to je nujen pogoj za varno uporabo Nette Database. Baza podatkov predpostavlja, da imajo vsi vhodni podatki pravilen podatkovni tip, ki ustreza danemu stolpcu.

Na primer, če bi bil $name v prejšnjih primerih nepričakovano polje namesto niza, bi Nette Database poskusila vstaviti vse njegove elemente v SQL poizvedbo, kar bi povzročilo napako. Zato nikoli ne uporabljajte nevalidiranih podatkov iz $_GET, $_POST ali $_COOKIE neposredno v poizvedbah baze podatkov.

Formatna kontrola

Na drugi ravni preverjamo format podatkov – na primer, ali so nizi v UTF-8 kodiranju in njihova dolžina ustreza definiciji stolpca, ali pa so številske vrednosti v dovoljenem obsegu za dani podatkovni tip stolpca.

Pri tej ravni validacije se lahko delno zanesemo tudi na samo bazo podatkov – mnoge baze podatkov zavrnejo nevalidne podatke. Vendar pa se obnašanje lahko razlikuje, nekatere lahko dolge nize tiho skrajšajo ali števila izven obsega obrežejo.

Domenska kontrola

Tretjo raven predstavljajo logične kontrole, specifične za vašo aplikacijo. Na primer preverjanje, da vrednosti iz izbirnih polj ustrezajo ponujenim možnostim, da so števila v pričakovanem obsegu (npr. starost 0–150 let) ali da medsebojne odvisnosti med vrednostmi imajo smisel.

Priporočeni načini validacije

  • Uporabljajte Nette Obrazce, ki samodejno zagotovijo pravilno validacijo vseh vnosov
  • Uporabljajte Presenterje in navedite pri parametrih v action*() in render*() metodah podatkovne tipe
  • Ali implementirajte lastno validacijsko plast z uporabo standardnih PHP orodij, kot je filter_var()

Varno delo s stolpci

V prejšnjem odseku smo si ogledali, kako pravilno validirati vrednosti parametrov. Pri uporabi polj v SQL poizvedbah pa moramo enako pozornost posvetiti tudi njihovim ključem.

// ❌ NEVARNA KODA - niso obdelani ključi v polju
$database->query('INSERT INTO users', $_POST);

Pri ukazih INSERT in UPDATE je to temeljna varnostna napaka – napadalec lahko v bazo podatkov vstavi ali spremeni kateri koli stolpec. Lahko bi si na primer nastavil is_admin = 1 ali vstavil poljubne podatke v občutljive stolpce (t.i. Mass Assignment Vulnerability).

V pogojih WHERE je to še nevarnejše, saj lahko vsebujejo operatorje:

// ❌ NEVARNA KODA - niso obdelani ključi v polju
$_POST['salary >'] = 100000;
$database->query('SELECT * FROM users WHERE', $_POST);
// izvede poizvedbo WHERE (`salary` > 100000)

Napadalec lahko ta pristop izkoristi za sistematično ugotavljanje plač zaposlenih. Začne na primer s poizvedbo o plačah nad 100.000, nato pod 50.000 in s postopnim zoževanjem obsega lahko odkrije približne plače vseh zaposlenih. Ta tip napada se imenuje SQL enumeration.

Metodi where() in whereOr() sta še veliko bolj fleksibilni in podpirata v ključih in vrednostih SQL izraze, vključno z operatorji in funkcijami. To daje napadalcu možnost izvedbe SQL injection:

// ❌ NEVARNA KODA - napadalec lahko vstavi lasten SQL
$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1'];
$table->where($_POST);
// izvede poizvedbo WHERE (0) UNION SELECT name, salary FROM users WHERE (1)

Ta napad zaključi prvotni pogoj z 0), priključi lasten SELECT z UNION, da pridobi občutljive podatke iz tabele users, in zaključi sintaktično pravilno poizvedbo z WHERE (1).

Beli seznam stolpcev

Za varno delo z imeni stolpcev potrebujemo mehanizem, ki zagotavlja, da lahko uporabnik dela samo z dovoljenimi stolpci in ne more dodati lastnih. Lahko bi poskusili zaznati in blokirati nevarna imena stolpcev (črni seznam), vendar je ta pristop nezanesljiv – napadalec lahko vedno najde nov način, kako zapisati nevarno ime stolpca, ki ga nismo predvideli.

Zato je veliko varneje obrniti logiko in definirati ekspliciten seznam dovoljenih stolpcev (beli seznam):

// Stolpci, ki jih lahko uporabnik ureja
$allowedColumns = ['name', 'email', 'active'];

// Odstranimo vse nedovoljene stolpce iz vnosa
$filteredData = array_intersect_key($userData, array_flip($allowedColumns)); // array_flip for performance

// ✅ Zdaj lahko varno uporabimo v poizvedbah, kot na primer:
$database->query('INSERT INTO users', $filteredData);
$table->update($filteredData);
$table->where($filteredData);

Dinamični identifikatorji

Za dinamična imena tabel in stolpcev uporabite nadomestni znak ?name. Ta zagotavlja pravilno ubežanje identifikatorjev glede na sintakso dane baze podatkov (npr. z uporabo povratnih narekovajev v MySQL):

// ✅ Varna uporaba zaupanja vrednih identifikatorjev
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// Rezultat v MySQL: SELECT `name` FROM `users`

Pomembno: simbol ?name uporabljajte samo za zaupanja vredne vrednosti, definirane v kodi aplikacije. Za vrednosti od uporabnika ponovno uporabite beli seznam. Sicer se izpostavljate varnostnim tveganjem:

// ❌ NEVARNO - nikoli ne uporabljajte vnosa od uporabnika
$database->query('SELECT ?name FROM users', $_GET['column']);
različica: 4.0