Zagrożenia bezpieczeństwa
Bazy danych często zawierają wrażliwe dane i pozwalają na niebezpieczne operacje. Nette Database zapewnia szereg funkcji bezpieczeństwa. Kluczowe jest jednak zrozumienie różnicy między bezpiecznymi i niebezpiecznymi interfejsami API.
SQL Injection
Wstrzyknięcie kodu SQL jest najpoważniejszym zagrożeniem bezpieczeństwa podczas pracy z bazami danych. Występuje, gdy niezaznaczone dane wejściowe użytkownika stają się częścią zapytania SQL. Atakujący może wstrzyknąć własne polecenia SQL, uzyskując lub modyfikując dane w bazie danych.
// NIEBEZPIECZNY KOD - podatny na wstrzyknięcie kodu SQL
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");
// Atakujący może wprowadzić coś takiego jak ' OR '1'='1
// Wynikowym zapytaniem będzie:
// SELECT * FROM users WHERE name = '' OR '1'='1'
// To zwróci wszystkich użytkowników!
To samo dotyczy Database Explorer:
// KOD NIEBEZPIECZEŃSTWA
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");
Bezpieczne sparametryzowane zapytania
Bezpiecznym sposobem wstawiania wartości do zapytań SQL są zapytania parametryzowane. Nette Database zapewnia kilka sposobów ich użycia.
Znaki zapytania
Najprostszą metodą jest użycie znaków zastępczych:
// Bezpieczne sparametryzowane zapytania
$database->query('SELECT * FROM users WHERE name = ?', $_GET['name']);
// Bezpieczny warunek w Eksploratorze
$table->where('name = ?', $_GET['name']);
To samo dotyczy wszystkich innych metod w Database Explorer, które umożliwiają wstawianie wyrażeń ze znakami zapytania i parametrami.
Wartości muszą być typu skalarnego (string
, int
, float
,
bool
) lub null
. Jeśli np, $_GET['name']
jest tablicą, Nette Database uwzględni wszystkie
jej elementy w zapytaniu SQL, co może być niepożądane.
Tablice wartości
W przypadku klauzul INSERT
, UPDATE
lub WHERE
możemy użyć tablic wartości:
// Bezpieczny INSERT
$database->query('INSERT INTO users', [
'name' => $_GET['name'],
'email' => $_GET['email'],
]);
// Bezpieczne UPDATE
$database->query('UPDATE users SET', [
'name' => $_GET['name'],
'email' => $_GET['email'],
], 'WHERE id = ?', $_GET['id']);
Nette Database automatycznie ucieka od wszystkich wartości przekazywanych przez sparametryzowane zapytania. Musimy jednak zapewnić prawidłowy typ danych parametrów.
Klucze tablicowe nie są bezpiecznym API
Podczas gdy wartości w tablicach są bezpieczne, tego samego nie można powiedzieć o kluczach:
// ❌ NIEBEZPIECZNY KOD - klucze mogą zawierać SQL injection
$database->query('INSERT INTO users', $_GET);
$database->query('SELECT * FROM users WHERE', $_GET);
$table->where($_GET);
W przypadku poleceń INSERT
i UPDATE
jest to krytyczna luka w zabezpieczeniach – atakujący może
wstawić lub zmodyfikować dowolną kolumnę w bazie danych. Na przykład, może ustawić is_admin = 1
lub wstawić
dowolne dane do wrażliwych kolumn.
W warunkach WHERE
jest to jeszcze bardziej niebezpieczne, ponieważ umożliwia wyliczanie SQL –
technikę stopniowego pobierania informacji o bazie danych. Atakujący może próbować zbadać wynagrodzenia pracowników,
wstrzykując dane do $_GET
w ten sposób:
$_GET = ['salary >', 100000]; // rozpoczyna ustalanie przedziałów wynagrodzeń
Głównym problemem jest jednak to, że warunki WHERE
obsługują wyrażenia SQL w kluczach:
// Legalne użycie operatorów w kluczach
$table->where([
'age > ?' => 18,
'ROUND(score, ?) > ?' => [2, 75.5],
]);
// NIEBEZPIECZNE: atakujący może wstrzyknąć własny SQL
$_GET = ['1) UNION SELECT name, salary FROM users WHERE (is_admin = ?' => 1];
$table->where($_GET); // pozwala atakującemu uzyskać uprawnienia administratora
Jest to po raz kolejny SQL injection.
Kolumny na białej liście
Jeśli chcesz zezwolić użytkownikom na wybór kolumn, zawsze używaj białej listy:
// Bezpieczne przetwarzanie - tylko dozwolone kolumny
$allowedColumns = ['name', 'email', 'active'];
$values = array_intersect_key($_GET, array_flip($allowedColumns));
$database->query('INSERT INTO users', $values);
Dynamiczne identyfikatory
W przypadku dynamicznych nazw tabel i kolumn należy użyć symbolu zastępczego ?name
:
// Bezpieczne korzystanie z zaufanych identyfikatorów
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// UNSAFE - nigdy nie używaj danych wejściowych użytkownika
$database->query('SELECT ?name FROM users', $_GET['column']);
Symbol ?name
powinien być używany tylko dla zaufanych wartości zdefiniowanych w kodzie aplikacji. W przypadku
wartości dostarczonych przez użytkownika należy ponownie użyć białej listy.