Що таке ін’єкція SQL і як її запобігти в додатках PHP?

Отже, ви вважаєте, що ваша база даних SQL є продуктивною та захищеною від миттєвого знищення? Що ж, SQL Injection не погоджується!

Так, ми говоримо про миттєве знищення, тому що я не хочу починати цю статтю зі звичайної недолугої термінології «посилення безпеки» та «запобігання зловмисному доступу». SQL-ін’єкція — це настільки старий трюк у книзі, що всі, кожен розробник, знають про це дуже добре і добре знають, як цьому запобігти. За винятком одного дивного випадку, коли вони помиляються, і результати можуть бути просто катастрофічними.

Якщо ви вже знаєте, що таке SQL Injection, можете перейти до другої половини статті. Але для тих, хто тільки починає працювати у сфері веб-розробки та мріє обійняти більш високі посади, варто ознайомитись.

Що таке SQL Injection?

Ключ до розуміння SQL-ін’єкції — у її назві: SQL + ін’єкція. Слово «ін’єкція» тут не має жодних медичних конотацій, а скоріше є вживанням дієслова «вводити». Разом ці два слова передають ідею розміщення SQL у веб-програмі.

Розміщення SQL у веб-програмі. . . хммм . . Чи не це те, що ми робимо? Так, але ми не хочемо, щоб зловмисники керували нашою базою даних. Розберемося в цьому на прикладі.

Припустімо, ви створюєте типовий веб-сайт на PHP для місцевого магазину електронної комерції, тож ви вирішили додати таку контактну форму:

<form action="record_message.php" method="POST">
  <label>Your name</label>
  <input type="text" name="name">
  
  <label>Your message</label>
  <textarea name="message" rows="5"></textarea>
  
  <input type="submit" value="Send">
</form>

І припустімо, що файл send_message.php зберігає все в базі даних, щоб власники магазину могли читати повідомлення користувачів пізніше. Він може мати такий код:

<?php

$name = $_POST['name'];
$message = $_POST['message'];

// check if this user already has a message
mysqli_query($conn, "SELECT * from messages where name = $name");

// Other code here

Отже, ви спочатку намагаєтеся перевірити, чи цей користувач уже має непрочитане повідомлення. Запит SELECT * із повідомлень, де name = $name здається досить простим, чи не так?

НЕПРАВИЛЬНО!

Через свою невинність ми відкрили двері для миттєвого знищення нашої бази даних. Щоб це сталося, зловмисник повинен виконати такі умови:

  • Програма працює на базі даних SQL (сьогодні майже кожна програма працює)
  • Поточне підключення до бази даних має дозволи на «редагування» та «видалення» бази даних
  • Назви важливих таблиць можна вгадати

Третій пункт означає, що тепер, коли зловмисник знає, що ви керуєте магазином електронної комерції, ви, швидше за все, зберігаєте дані про замовлення в таблиці замовлень. Озброївшись усім цим, все, що зловмисник повинен зробити, це вказати це як своє ім’я:

  Як зберегти вкладки Google Chrome на потім

Джо; скорочувати замовлення;? Так, сер! Давайте подивимося, яким буде запит, коли його виконає скрипт PHP:

SELECT * FROM messages WHERE name = Joe; скорочувати замовлення;

Гаразд, перша частина запиту містить синтаксичну помилку (без лапок навколо «Джо»), але крапка з комою змушує механізм MySQL почати інтерпретувати нову: скорочувати замовлення. Просто так, одним махом, вся історія замовлень зникає!

Тепер, коли ви знаєте, як працює SQL-ін’єкція, настав час поглянути, як її зупинити. Дві умови, які повинні бути виконані для успішного впровадження SQL:

  • Сценарій PHP повинен мати права на зміну/видалення бази даних. Я думаю, що це стосується всіх програм, і ви не зможете зробити свої програми лише для читання. 🙂 І вгадайте що, навіть якщо ми видалимо всі привілеї модифікації, SQL-ін’єкція все одно може дозволити комусь виконувати запити SELECT і переглядати всю базу даних, включаючи конфіденційні дані. Іншими словами, зниження рівня доступу до бази даних не працює, і вашій програмі це все одно потрібно.
  • Введені користувачем дані обробляються. Єдиний спосіб, яким може працювати 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, який додає зворотну косу риску до одиночної лапки, екрануючи її. У результаті весь рядок тепер передається як нешкідливий рядок до бази даних замість того, щоб мати можливість брати участь у маніпулюванні запитами.

      Як увімкнути режим низьких даних на вашому iPhone

    У цього підходу є один недолік: це дуже, дуже стара техніка, яка поєднується зі старішими формами доступу до бази даних у PHP. Починаючи з PHP 7, ця функція навіть більше не існує, що підводить нас до наступного рішення.

    Використовуйте підготовлені твердження

    Підготовлені заяви — це спосіб зробити запити до бази даних більш безпечними та надійними. Ідея полягає в тому, що замість того, щоб надсилати необроблений запит до бази даних, ми спочатку повідомляємо базі даних структуру запиту, який ми будемо надсилати. Це те, що ми маємо на увазі під «підготовкою» заяви. Після підготовки оператора ми передаємо інформацію як параметризовані вхідні дані, щоб база даних могла «заповнити прогалини», підключивши вхідні дані до структури запиту, яку ми надіслали раніше. Це забирає будь-яку особливу силу, яку можуть мати вхідні дані, змушуючи їх розглядатися як прості змінні (або корисні навантаження, якщо хочете) у всьому процесі. Ось як виглядають підготовлені заяви:

    <?php
    $servername = "localhost";
    $username = "username";
    $password = "password";
    $dbname = "myDB";
    
    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    
    // Check connection
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }
    
    // prepare and bind
    $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
    $stmt->bind_param("sss", $firstname, $lastname, $email);
    
    // set parameters and execute
    $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 "New records created successfully";
    
    $stmt->close();
    $conn->close();
    ?>

    Я знаю, що процес звучить надто складним, якщо ви новачок у підготовлених заявах, але концепція варта зусиль. Ось хороший вступ до нього.

    Для тих, хто вже знайомий із розширенням PDO PHP і використовує його для створення готових операторів, у мене є невелика порада.

    Попередження: будьте обережні, налаштовуючи PDO

    Використовуючи PDO для доступу до бази даних, ми можемо отримати помилкове відчуття безпеки. «Ну, я використовую PDO. Тепер мені більше ні про що не потрібно думати» — так загалом відбувається наше мислення. Це правда, що PDO (або підготовлені оператори MySQLi) достатньо для запобігання різного роду атак SQL-ін’єкцій, але ви повинні бути обережними, налаштовуючи його. Зазвичай просто скопіювати та вставити код із підручників або попередніх проектів і рухатися далі, але це налаштування може скасувати все:

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

    Цей параметр наказує PDO емулювати підготовлені оператори, а не фактично використовувати функцію підготовлених операторів бази даних. Отже, PHP надсилає прості рядки запиту до бази даних, навіть якщо ваш код виглядає так, ніби він створює підготовлені оператори та встановлює параметри тощо. Іншими словами, ви так само вразливі до впровадження SQL, як і раніше. 🙂

      Як дискретно набрати номер екстреної допомоги з вашого iPhone

    Рішення просте: переконайтеся, що ця емуляція має значення false.

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

    Тепер PHP-скрипт змушений використовувати підготовлені оператори на рівні бази даних, запобігаючи різного роду впровадження SQL.

    Запобігання використанню WAF

    Чи знаєте ви, що також можна захистити веб-додатки від впровадження SQL за допомогою WAF (брандмауера веб-додатків)?

    Що ж, не лише впровадження SQL, а й багато інших уразливостей рівня 7, як-от міжсайтовий скриптинг, несправна автентифікація, міжсайтова підробка, розкриття даних тощо. Ви можете використовувати автономне розміщення, наприклад Mod Security, або хмарне, як описано нижче.

    SQL ін’єкції та сучасні фреймворки PHP

    Впровадження SQL настільки поширене, таке просте, таке неприємне і настільки небезпечне, що всі сучасні веб-фреймворки PHP мають вбудовані засоби протидії. У WordPress, наприклад, ми маємо функцію $wpdb->prepare(), тоді як якщо ви використовуєте фреймворк MVC, він виконує всю брудну роботу за вас, і вам навіть не потрібно думати про те, щоб запобігти ін’єкції SQL. Трохи дратує те, що в WordPress потрібно чітко готувати заяви, але привіт, ми говоримо про WordPress. 🙂

    У будь-якому разі я хочу сказати, що сучасній версії веб-розробників не потрібно думати про SQL-ін’єкції, і, як наслідок, вони навіть не підозрюють про цю можливість. Таким чином, навіть якщо вони залишають відкритим один бекдор у своїй програмі (можливо, це параметр запиту $_GET і старі звички запускати брудні запити), результати можуть бути катастрофічними. Тому завжди краще знайти час, щоб глибше зануритися в основи.

    Висновок

    SQL Injection — це дуже неприємна атака на веб-програму, але її легко уникнути. Як ми побачили в цій статті, обережність під час обробки введених даних користувачами (до речі, SQL-ін’єкція — не єдина загроза, яку створює обробка введених даних користувачами) і запитів до бази даних — це все, що потрібно. Тим не менш, ми не завжди працюємо з безпекою веб-фреймворку, тому краще знати про цей тип атак і не піддаватися їм.