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.

wersja: 4.0