Original article: http://shiflett.org/articles/sql-injection

Ласкаво просимо до чергового випуску куточку безпеки. Темою цього місяця є SQL- ін’єкції – напрямок атак, який часто турбує розробників PHP, але у відношенні якого не вистачає хорошої документації.

Більшість веб-додатків взаємодіють із базами даних, і дані, які у них зберігаються в основному надходять з віддалених джерел. Таким чином, при створенні оператора SQL, ви часто використовуєте форму введення даних. Типова атака у вигляді SQL-ін’єкції використовує цей же сценарій, намагаючись відправити фрагменти дійсних SQL-запитів, використовуючи несподівані значення даних GET і POST. Ось чому причиною вразливості до SQL- ін’єкцій часто є помилки з поганою фільтрацією та екрануванням, і на цьому факті неможливо не наголосити.

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

SQL-ін’єкція

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

Малюнок 1:

Щоб отримати більш докладну інформацію про цю форму, ви переглядаєте код ресурсу:

<form action="/login.php" method="POST"> 
<p>Username: <input type="text" name="username" /></p> 
<p>Password: <input type="text" name="password" /></p> 
<p><input type="submit" value="Log In" /></p> 
</form> 

Ви вже можете зробити дуже обґрунтоване припущення щодо типу оператора SQL, цей додаток можна використовувати для перевірки облікових даних доступу. Це буде, швидше за все, оператор SELECT. Ви також можете зробити припущення про правила присвоєння назв в таблиці бази даних, тому що вони, ймовірно, відповідають назвам, які використовуються в HTML формі. (Також можливо, що ви можете призвести до помилки, яка покажете вам цю інформацію.) Оскільки ця форма призначена для аутентифікації, у ній, ймовірно, є умова WHERE , яка використовує $_POST['username'] і $_POST['password'].

З усього цього, можна передбачити наступне:

<?php 
  
$sql = "SELECT count(*) 
        FROM   users 
        WHERE  username = '{$_POST['username']}' 
        AND    password = '...'"; 
  
?> 

Якщо це припущення вірне, що можна зробити, щоб керувати цим запитом? Уявіть собі, що вводите наступне ім’я користувача:

chris' /* 

Оператор SQL набуває такого вигляду:

SELECT count(*) 
FROM   users 
WHERE  username = 'chris' /*' 
AND    password = '...'"; 

У цьому прикладі /* використовується, щоб почати багаторядковий коментар, ефективно зупинивши запит в цій точці. Це було успішно протестовано з mysqli. Стандартний коментар в SQL починається з –, і дуже просто спробувати обидва варіанти.

Цей запит передбачає успішну спробу аутентифікації, якщо існує обліковий запис chris , незалежно від його пароля. Саме цей вид атак часто використовується для крадіжки облікових записів. Звичайно, можна використати будь-яке ім’я користувача (admin – це популярний випадок). Таким чином, пославши неправильне ім’я користувача, можна ввійти в систему, не маючи дійсного облікового запису.

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

 

Злом умови WHERE

Умова WHERE використовується для обмеження записів, які підходять окремому запиту. Для оператора SELECT він визначає записи, які повертаються. Для оператора UPDATE він визначає записи, які змінюються. Для оператора DELETE він визначає, які записи будуть видалені. Якщо користувач може використовувати умову WHERE, є багато можливостей зробити кардинальні зміни – відбір, оновлення і видалення довільних записів в базі даних.

Уявіть собі оператор SELECT, який має на меті отримання номерів всіх кредитних карт поточного користувача:

<?php 
  
$sql = "SELECT card_num, card_name, card_expiry 
        FROM   credit_cards 
        WHERE  username = '{$_GET['username']}'"; 
  
?> 

В даному випадку, додаток може навіть не спитати ім’я користувача, але надасть його в посиланні:

<a href="/account.php?username=shiflett"> 
Credit Card Information 
</a> 

Якщо користувач має кілька карт, додаток може зробити цикл по знаходженню результатів запиту до бази даних, відображаючи для кожної карти номер карти, ім’я власника та термін її дії.

Уявіть собі користувача, який відвідує такий ресурс:

/account.php?username=shiflett%27+OR+username+%3D+%27lerdorf 

Тут присвоюється наступне значення імені користувача:

shiflett' OR username = 'lerdorf 

При використанні в попередньому запиті SQL, $sql має наступне значення:

SELECT card_num, card_name, card_expiry 
FROM   credit_cards 
WHERE  username = 'shiflett' OR username = 'lerdorf' 

Тепер користувач бачить список всіх кредитних карт, що належать shiflett або lerdorf. Це досить серйозна вразливість системи безпеки. Звичайно, більша вразливість існує в даному прикладі, оскільки користувач може довільно вписати будь-яке ім’я користувача в URL. Крім того, ім’я користувача, яке змушує умову WHERE знаходити відповідності в усіх записах, потенційно може зробити видимими всі записи:

shiflett' OR username = username 

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

Фільтрація вхідних даних

Дана стаття передбачає, що magic_quotes_gpc відключено. Якщо дана функція включена, то ви можете її вимкнути або використовувати функцію fix_magic_quotes (), щоб відновити вхід.

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

<form action="/receive.php" method="POST"> 
<select name="color"> 
    <option value="red">red</option> 
    <option value="green">green</option> 
    <option value="blue">blue</option> 
</select> 
<input type="submit"> 
</form> 

Очевидно, що очікуваними значеннями будуть red, green, і blue (червоний, зелений і синій). Таким чином, вхідному фільтру необхідно перевірити наступне:

<?php 
  
$clean = array(); 
  
switch ($_POST['color']) { 
    case 'red': 
    case 'green': 
    case 'blue': 
        $clean['color'] = $_POST['color']; 
        break; 
    default: 
        /* Error */ 
        break; 
} 
  
?> 

Цей код використовує окремий масив ($clean) для збереження відфільтрованих даних. Гарною ідеєю є вибір правила присвоєння назв, яке допоможе вам визначити потенційно зіпсовані дані. У даному прикладі можна довіряти, що функція $clean['color'] містить коректний колір, тому що вона запускається першою, і лише потім присвоюється значення $_POST['color'], якщо це значення проходить відбір.

Два найважливіших моменти при фільтрації введених даних:

Екранування вихідних даних
За допомогою правильно відфільтрованих вхідних даних ви вже досить добре захищені від шкідливих атак. Єдиний крок, який залишилось зробити, це екранувати їх так, щоб формат введених даних випадково не пересікався з форматом оператора SQL. Якщо ви використовуєте mysqli, вам просто необхідно перед використанням передавати всі дані через mysqli_real_escape_string ():

<?php 
  
$mysqli = array(); 
  
$mysqli['color'] = mysqli_real_escape_string($clean['color']); 
  
$sql = "SELECT username 
        FROM   users 
        WHERE  favorite_color = '{$mysqli['color']}'"; 
  
?> 

В цьому випадку, припускаючи, що $clean['color'] створений в попередньому прикладі, ви можете бути впевнені, що колір містить тільки алфавітні символи. (Тобто red, green, або blue). Таким чином, екранування може здатися зайвим, і воно таким і є. Тим не менш, як і раніше, краще завжди екранувати дані. Ця практика допоможе вам не забути про цей важливий крок, а він дотримується принципу глибокого захисту, який підкреслює важливість надмірних заходів безпеки.

До наступної зустрічі …

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

NYPHP має корисний ресурс, який пояснює екранування вихідних даних SQL.

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