Ризики безпеки

Бази даних часто містять конфіденційні дані і дозволяють виконувати небезпечні операції. Nette Database надає ряд функцій безпеки. Однак дуже важливо розуміти різницю між безпечними та небезпечними API.

Ін'єкція SQL

SQL-ін'єкція є найсерйознішим ризиком безпеки при роботі з базами даних. Вона виникає, коли неконтрольоване введення користувача стає частиною SQL-запиту. Зловмисник може вставити свої власні SQL-команди, отримуючи або змінюючи дані в базі даних.

// ❌ НЕБЕЗПЕЧНИЙ КОД - вразливий до SQL ін'єкцій
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");

// Зловмисник може ввести щось на кшталт ' OR '1'='1
// Результуючий запит буде виглядати наступним чином:
// SELECT * FROM users WHERE name = '' OR '1'='1'
// Це поверне всіх користувачів!

Те ж саме стосується і Провідника баз даних:

// ❌ КОД НЕБЕЗПЕКИ
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");

Безпечні параметризовані запити

Безпечний спосіб вставляти значення в SQL-запити – це параметризовані запити. Nette Database надає кілька способів їх використання.

Знаки питання-заповнювачі

Найпростіший метод – використання знаків питання-заповнювачів:

// ✅ Безпечні параметризовані запити
$database->query('SELECT * FROM users WHERE name = ?', $_GET['name']);

// ✅ Безпечний стан в Провіднику
$table->where('name = ?', $_GET['name']);

Те саме стосується всіх інших методів у Провіднику баз даних, які дозволяють вставляти вирази зі знаками питання-заповнювачами та параметрами.

Значення повинні бути скалярного типу (string, int, float, bool) або null. Якщо, наприклад, $_GET['name'] є масивом, Nette Database включить всі його елементи в SQL-запит, що може бути небажаним.

Масиви значень

Для речень INSERT, UPDATE або WHERE ми можемо використовувати масиви значень:

// ✅ Безпечна ВСТАВКА
$database->query('INSERT INTO users', [
	'name' => $_GET['name'],
	'email' => $_GET['email'],
]);

// Безпечне Оновлення
$database->query('UPDATE users SET', [
	'name' => $_GET['name'],
	'email' => $_GET['email'],
], 'WHERE id = ?', $_GET['id']);

База даних Nette автоматично екранує всі значення, передані через параметризовані запити. Однак, ми повинні переконатися, що параметри мають правильний тип даних.

Масивні ключі не є безпечним 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-перебір – техніку поступового отримання інформації про базу даних. Зловмисник може спробувати дослідити зарплати співробітників, вставивши дані в $_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);

// НЕБЕЗПЕЧНО - ніколи не використовуйте введення користувача
$database->query('SELECT ?name FROM users', $_GET['column']);

Символ ?name слід використовувати лише для довірених значень, визначених у коді програми. Для значень, наданих користувачем, знову використовуйте білий список.

версію: 4.0