Риск за сигурността
Базите данни често съдържат чувствителни данни и позволяват опасни операции. Базата данни Nette предоставя редица функции за сигурност. От решаващо значение е обаче да се разбере разликата между безопасни и опасни API.
SQL инжектиране
SQL инжектирането е най-сериозният риск за сигурността при работа с бази данни. То възниква, когато непроверен потребителски вход стане част от SQL заявка. Атакуващият може да инжектира свои собствени SQL команди, като по този начин получава или променя данни в базата данни.
// ❌ БЕЗОПАСЕН КОД - уязвим към SQL инжекция
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");
// Нападателят може да въведе нещо подобно на: ' ИЛИ '1'='1
// Получената заявка ще бъде:
// SELECT * FROM users WHERE name = '' OR '1'='1'
// Това връща всички потребители!
Същото се отнася и за Database Explorer:
// ❌ КОД ЗА БЕЗОПАСНОСТ
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");
Безопасни параметризирани заявки
Безопасният начин за вмъкване на стойности в SQL заявките е чрез параметризирани заявки. Базата данни Nette предоставя няколко начина за използването им.
Заместващи въпросителни знаци
Най-простият метод е да се използват заместващи въпросителни знаци:
// ✅ Безопасни параметризирани заявки
$database->query('SELECT * FROM users WHERE name = ?', $_GET['name']);
// ✅ Безопасно условие в Explorer
$table->where('name = ?', $_GET['name']);
Същото важи и за всички други методи в Database Explorer, които позволяват вмъкване на изрази с въпросителни знаци и параметри.
Стойностите трябва да са от скаларен тип (string
,
int
, float
, bool
) или null
. Ако например
$_GET['name']
е масив, Nette Database ще включи всички негови елементи в SQL
заявката, което може да е нежелателно.
Масиви от стойности
За клаузите INSERT
, UPDATE
или WHERE
можем да използваме
масиви от стойности:
// ✅ Безопасен INSERT
$database->query('INSERT INTO users', [
'name' => $_GET['name'],
'email' => $_GET['email'],
]);
// ✅ Безопасен UPDATE
$database->query('UPDATE users SET', [
'name' => $_GET['name'],
'email' => $_GET['email'],
], 'WHERE id = ?', $_GET['id']);
Nette Database автоматично ескапира всички стойности, предадени чрез параметризирани заявки. Трябва обаче да осигурим правилния тип данни на параметрите.
Ключовете на масива не са сигурен API
Докато стойностите в масивите са безопасни, същото не може да се каже за ключовете:
// ❌ БЕЗОПАСЕН КОД - ключовете могат да съдържат SQL инжекция
$database->query('INSERT INTO users', $_GET);
$database->query('SELECT * FROM users WHERE', $_GET);
$table->where($_GET);
За командите INSERT
и UPDATE
това е критичен недостатък в
сигурността – атакуващият може да вмъкне или промени всяка колона в
базата данни. Например, той може да зададе is_admin = 1
или да вмъкне
произволни данни в чувствителни колони.
В условията на WHERE
това е още по-опасно, защото позволява SQL
enumeration – техника за постепенно извличане на информация за базата
данни. Нападателят може да се опита да проучи заплатите на служителите,
като инжектира в $_GET
по този начин:
$_GET = ['salary >', 100000]; // започва да определя диапазони на заплатите.
Основният проблем обаче е, че WHERE
условията поддържат SQL изрази
в ключовете:
// Законосъобразно използване на оператори в ключовете
$table->where([
'age > ?' => 18,
'ROUND(score, ?) > ?' => [2, 75.5],
]);
// ❌ БЕЗОПАСНО: атакуващият може да инжектира свой собствен SQL
$_GET = ['1) UNION SELECT name, salary FROM users WHERE (is_admin = ?' => 1];
$table->where($_GET); // позволява на нападателя да получи администраторски заплати
Това отново е SQL инжекция.
Създаване на бял списък на колоните
Ако искате да разрешите на потребителите да избират колони, винаги използвайте бял списък:
// ✅ Безопасна обработка - само разрешени колони
$allowedColumns = ['name', 'email', 'active'];
$values = array_intersect_key($_GET, array_flip($allowedColumns));
$database->query('INSERT INTO users', $values);
Динамични идентификатори
За динамични имена на таблици и колони използвайте заместителя
?name
:
// ✅ Безопасно използване на надеждни идентификатори
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// ❌ UNSAFE - никога не използвайте потребителски вход
$database->query('SELECT ?name FROM users', $_GET['column']);
Символът ?name
трябва да се използва само за доверени стойности,
дефинирани в кода на приложението. За стойности, предоставени от
потребителя, отново използвайте бял списък.