Ви впевнені, що ваша база даних SQL надійно захищена від несанкціонованого втручання? SQL-ін’єкції можуть довести зворотне!
Так, ми говоримо про серйозну загрозу, а не про банальні “заходи безпеки” чи “запобігання несанкціонованому доступу”. SQL-ін’єкція – це настільки старий трюк, що кожен розробник знає про його небезпеку та методи захисту. Проте, навіть професіонали іноді припускаються помилок, і наслідки можуть бути руйнівними.
Якщо ви вже знайомі з SQL-ін’єкціями, можете переходити до другої частини статті. Однак для початківців у веб-розробці, які прагнуть до професійного зростання, варто ознайомитися з цим явищем.
Що таке SQL-ін’єкція?
Суть SQL-ін’єкції криється у її назві: SQL + ін’єкція. Слово “ін’єкція” тут означає “введення”, а не медичну процедуру. У поєднанні ці слова описують процес впровадження SQL-коду у веб-застосунок.
Впровадження SQL у веб-застосунок. . . Хммм… Чи не цим ми займаємося постійно? Так, але ми не хочемо, щоб зловмисники керували нашою базою даних. Розглянемо це на прикладі.
Уявімо, що ви створюєте веб-сайт на PHP для місцевого інтернет-магазину. Ви вирішили додати форму зворотного зв’язку:
Файл `send_message.php` зберігає дані у базі даних, щоб власники магазину могли читати повідомлення користувачів. Код може виглядати так:
<?php $name = $_POST['name']; $message = $_POST['message']; // Перевірка, чи користувач вже має повідомлення mysqli_query($conn, "SELECT * from messages where name = $name"); // Інший код
Отже, ви спочатку намагаєтесь перевірити, чи має користувач непрочитане повідомлення. Запит `SELECT * from messages where name = $name` здається простим, чи не так?
НІ! Це велика помилка!
З необережності ми відкрили шлях до руйнування нашої бази даних. Для здійснення SQL-ін’єкції зловмиснику потрібні наступні умови:
- Застосунок використовує базу даних SQL (практично кожна сучасна програма)
- З’єднання з базою даних має права на “редагування” та “видалення”
- Можна вгадати назви важливих таблиць
Третій пункт означає, що зловмисник, знаючи, що ви керуєте інтернет-магазином, припускає, що дані замовлень зберігаються в таблиці `orders`. З цією інформацією зловмисник може ввести наступне як своє ім’я:
Джо; TRUNCATE orders;? Так, ось так! Подивимось, що отримаємо у запиті PHP:
`SELECT * FROM messages WHERE name = Joe; TRUNCATE orders;`
Перша частина запиту містить помилку синтаксису (немає лапок навколо “Джо”), але крапка з комою змушує MySQL почати обробку нового запиту: `TRUNCATE orders`. Таким чином, одним махом вся історія замовлень зникає!
Тепер, коли ви розумієте принцип роботи SQL-ін’єкції, розглянемо способи її зупинити. Для успішної атаки SQL-ін’єкцією необхідні дві умови:
Запобігання SQL-ін’єкції в PHP
Оскільки з’єднання з базою даних, запити та введення користувача є необхідними елементами, як запобігти SQL-ін’єкції? На щастя, є два простих способи: 1) фільтрувати введені користувачем дані та 2) використовувати підготовлені запити.
Фільтрація даних, введених користувачем
Якщо ви використовуєте стару версію PHP (5.5 або нижче), варто пропускати всі введені користувачем дані через функцію `mysql_real_escape_string()`. Вона видаляє спеціальні символи з рядка, щоб вони втрачали своє значення при використанні у базі даних.
Наприклад, якщо є рядок на кшталт `I’m a string`, зловмисник може використати символ одинарної лапки (‘), щоб змінити запит до бази даних та здійснити SQL-ін’єкцію. Функція `mysql_real_escape_string()` перетворить цей рядок на `I\’m a string`, додаючи зворотний слеш перед лапкою. У результаті весь рядок буде сприйнятий базою даних як звичайний текст, а не як частина запиту.
Цей підхід має недолік: це застаріла техніка, яка використовується зі старими методами доступу до бази даних у PHP. Починаючи з PHP 7, ця функція більше не існує, що підводить нас до наступного рішення.
Використання підготовлених запитів
Підготовлені запити – це спосіб зробити запити до бази даних більш безпечними та надійними. Замість надсилання необробленого запиту до бази даних, ми спочатку повідомляємо їй структуру запиту. Це і є “підготовка” запиту. Після підготовки ми передаємо дані як параметризовані вхідні дані, щоб база даних могла “заповнити прогалини”, підставляючи дані у раніше відправлену структуру. Це знешкоджує будь-яку особливу силу вхідних даних, змушуючи їх розглядатися як звичайні змінні у всьому процесі. Ось приклад підготовлених запитів:
<?php $servername = "localhost"; $username = "username"; $password = "password"; $dbname = "myDB"; // Створення з'єднання $conn = new mysqli($servername, $username, $password, $dbname); // Перевірка з'єднання if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } // Підготовка та прив'язка $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $firstname, $lastname, $email); // Встановлення параметрів та виконання $firstname = "John"; $lastname = "Doe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Mary"; $lastname = "Moe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Julie"; $lastname = "Dooley"; $email = "[email protected]"; $stmt->execute(); echo "Нові записи успішно створено"; $stmt->close(); $conn->close(); ?>
Можливо, цей процес здається складним, якщо ви новачок, але концепція варта зусиль. Ось хороший вступ до цієї теми.
Для тих, хто вже знайомий з розширенням PDO PHP і використовує його для створення підготовлених запитів, є невелика порада.
Увага: обережно налаштовуйте PDO
Використовуючи PDO для доступу до бази даних, ми можемо отримати хибне відчуття безпеки. “Я використовую PDO, мені більше ні про що не потрібно думати” – типова думка. PDO (або підготовлені запити MySQLi) достатньо для запобігання SQL-ін’єкціям, але потрібно бути обережним під час налаштування. Звичайне копіювання коду з підручників може призвести до скасування всього захисту:
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
Цей параметр наказує PDO емулювати підготовлені запити, а не використовувати реальні можливості бази даних. PHP надсилає звичайні рядки запиту до бази даних, хоча ваш код виглядає так, ніби створює підготовлені запити. Тобто, ви все ще вразливі до SQL-ін’єкцій.
Рішення просте: переконайтеся, що емуляція вимкнена.
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Тепер PHP-скрипт змушений використовувати підготовлені запити на рівні бази даних, запобігаючи SQL-ін’єкціям.
Запобігання за допомогою WAF
Чи знаєте ви, що веб-додатки можна захистити від SQL-ін’єкцій за допомогою WAF (брандмауера веб-додатків)?
WAF захищає не тільки від SQL-ін’єкцій, але й від багатьох інших вразливостей рівня 7, таких як міжсайтовий скриптинг, проблеми з автентифікацією, підробка міжсайтових запитів, витік даних тощо. Можна використовувати автономні рішення, як Mod Security, або хмарні сервіси.
SQL-ін’єкції та сучасні PHP-фреймворки
SQL-ін’єкція настільки поширена, проста, неприємна і небезпечна, що всі сучасні PHP-фреймворки мають вбудовані засоби захисту. У WordPress, наприклад, є функція `$wpdb->prepare()`, а фреймворки MVC роблять всю брудну роботу за вас. У WordPress потрібно явно підготувати запит, але це особливість WordPress. 🙂
З іншого боку, сучасні веб-розробники часто не думають про SQL-ін’єкції і навіть не підозрюють про цю загрозу. Навіть якщо залишається один бекдор у їх програмі (параметр запиту `$_GET` та старі звички використовувати необроблені запити), наслідки можуть бути катастрофічними. Тому важливо виділити час для глибшого вивчення основ.
Висновок
SQL-ін’єкція – це серйозна загроза для веб-додатків, але її легко уникнути. Потрібно бути обережними при обробці введених даних користувачем та запитів до бази даних. Зауважте, що SQL-ін’єкція – не єдина загроза, пов’язана з обробкою введених даних. Не завжди ми маємо справу з безпекою веб-фреймворків, тому важливо знати про цей тип атак і не піддаватися їм.