Диалог с немым SQL. Используем продвинутый метод слепых SQLAms (ax330d@gmail.com) Инъекции очень разнообразны по своей сути. В статье я не буду рассказывать их классификацию, отмечу лишь, что сейчас нас будет интересовать слепая SQL-инъекция — тот случай, когда сервер с неохотой делится с нами какой-либо интересной информацией, но все же по косвенным признакам хакер может определить контент БД. Я предложу тебе еще один прием слепой SQL, который ранее нигде не описывался. Чем же так примечательны эти слепые инъекции? А тем, что они вовсе и не примечательны, а противны и безобразны. Как для меня с тобой, так и для сервера. Если мы говорим о критических случаях, когда сервер не показывает никаких ошибок, то для получения информации из базы данных обычно приходится выбирать данные на условиях ложь/правда. Это занимает очень много времени, сильно пачкает логи, а иногда нагружает SQL-сервер. Все-таки, на один символ - несколько запросов. Но сейчас мы изменим ситуацию к лучшему. Как тебе один символ на один запрос при слепой, совсем-совсем слепой, инъекции? Наверняка тебя интересует, как же можно такого добиться, когда недоступны никакие источники информации, кроме как ответ сервера - true/false. Однако, можно. Мы задействуем еще один параметр - время. Вероятно, ты подумал: «автор статьи двигает в массы старье, про time-based SQL-инъекции все уже знают». А вот и нет! Это будет продвинутая, или «advanced» time-based SQL-инъекция. Мы не будем ничего сравнивать, как это обычно происходит, и суть новинки заключается в определении уникальных временных задержек сервера - по задержке на символ. То есть, мы говорим серверу ничего нам не сообщать в течение определенного времени для какого-то определенного символа. Фактически, получается таблица соотношений временных задержек и символов. Берем сервер за рогаПопробуем реализовать мой метод на практике. Для примера возьмем популярный и многострадальный MySQL 5. Пятая, а точнее 5.0.12 версия, предлагает нам новую (относительно новую) функцию SLEEP().
Она приказывает серверу уснуть на какое-то количество секунд. Я решил воспользоваться этой функцией вместо старого и совсем не доброго BENCHMARK() по следующим причинам:
Вопрос сейчас стоит такой - на сколько секунд при каком символе SQL-серверу стоит засыпать? Можно, конечно, в каждом запросе посылать нечто вроде массива отношений время/символ, но это неэлегантно. Решить проблему можно следующим образом. Допустим, мы посимвольно извлекаем пароль при помощи SUBSTR() или его более короткого аналога - MID(), тогда каждый полученный символ преобразуем в ASCII-значение. И то, что мы получили, передаем функции SLEEP(). код Но значение слишком велико, чтобы так долго ждать. Ведь уже для символа * это целых 42 секунды, а что будет для f? Поэтому немного слукавим и от полученного значения отнимем 40. Все 42 секунды не стоит брать, так как не будет понятно, получилась ли текущая операция - в случае каких-либо ошибок мы всегда будем получать 0. Две секунды будет стартовой точкой. В упрощенном варианте наш запрос к серверу выглядит примерно так: Уловил мысль? На всякий случай поясню действия запроса:
АвтоматизацияИтак, принцип ясен, но вручную этим заниматься — неблагодарное дело. Можно, конечно, на листке записать таблицу соотношений, а в руках держать секундомер и ждать ответ сервера. Оставим это для суровых челябинских парней, мы с тобой знаем про поговорку «время-деньги». Специально для тебя я написал Perl-скрипт, который поможет автоматизировать рутинный процесс. Скрипт довольно-таки прост, но на всякий случай в нем есть небольшой хелп (на английском). В двух словах: сценарий выполняет задачу не полноценного инструмента, а, скорее, показательного примера, так сказать, Proof Of Concept. Хочу обратить твое внимание на один из параметров скрипта «-p» (сокращенное precision, или по-русски, точность). Что он делает, я объясню чуть позже. Принцип работы скрипта таков - сначала он тестирует, применим ли метод, а в случае успеха выбирает пароль для пользователя root (по умолчанию), если такая удача имеет место быть. К сожалению (а может, к счастью), он не идеален, и ты легко его можешь заточить под себя. Ложка дегтя в бочке медаОдин из существенных недостатков метода — он чувствителен к непропорциональному времени отдачи страницы, которое никогда не будет одинаковым: и сервер отвечает с разными задержками, и сама сеть никогда не будет идеально стабильной, ну и много других второстепенных факторов могут мешать. Разберем суть проблемы — к примеру, у нас в пароле текущий символ 2, тогда при идеальных обстоятельствах полученное значение от паузы будет 5 (в секундах). После всех преобразований ASCII-значение будет 50, что и есть 2. Но, допустим, ожидаемый ответ приходит на полторы секунды позже, чем предполагалось. По времени это уже получится 6.5 секунд, ASCII-значение 52, а символ и вовсе - 4. На локальном сервере все работает безупречно, без каких-либо «паразитных» задержек, но нам надо использовать метод в полевых условиях. Решил я проблему, введя специальный коэффициент - точность, или, как в скрипте написано, precision. Это и есть один из параметров скрипта, «-p». Данный коэффициент позволяет расширить диапазон допустимых значений задержек для каждого символа, то есть каждому символу теперь выделен некий интервал времени. Думаю, график выразит мысль лучше, чем слова. Фактически, коэффициент «точность» соответствует количеству секунд, которое отводится дополнительно для каждого символа. Чтобы прояснить ситуацию, вернемся к прошлому примеру. В том случае мы получили 6.5 секунд, что привело к неправильной интерпретации полученных данных. Теперь же, если мы возьмем precision, равный двум, то после расширения интервала допустимых значений для символа 2 диапазон уже будет 11-13 (включительно). Таким образом, добавляя к стартовому значению 11 секунд дополнительные «паразитные» полторы секунды, мы получим 13,5 секунд, но наша кривая чертова дюжина уже среди допустимых значений. Хочу отметить, что при повышении коэффициента точности увеличивается общее время ожидания. При точности, равной 0, время извлечения строки длиной в 41 символ (хеш пароля и астериск) составляет около 400 секунд, дальше на каждую единицу точности время ожидания увеличивается примерно на 318 секунд. Долго, но что делать, когда такие суровые условия? Тем более, я уверен, что и данный метод подлежит оптимизации. Допустим, сократить общее время ожидания можно за счет многопоточности. Еще как вариант я вижу применение группировки символов – создать определенную группу символов (путем SQL-запроса) на некий период времени, а там уже разбираться по алгоритму, что применен в показательном скрипте. Но это уже детали реализации конкретного решения, не относящиеся к теме. Если ты посмотришь в исходник скрипта, то увидишь, что запрос существенно длиннее и отличается от приведенного в статье. Дело в том, что запрос составлен таким образом, чтобы исключить диапазоны ненужных символов, оставив только те, что встречаются в строке, которую мы извлекаем – «0-9, A-Z. *». Для примера я взял хеш пароля пользователя. Думаю, тебе не составит проблем разобраться в запросе, а если что, обращайся к руководству MySQL, ну или ко мне. Второй недостаток метода — сложность реализации. Не обойтись без средств автоматизации, то есть, задействовать такой прием в полевых условиях крайне тяжело, да и не оправдывает средств. Однако заготовка уже есть, да и самому написать свой инструмент, думаю, нетрудно. И это все?Наверное, ты хочешь спросить - «и где же тут продвинутость?». Да, время выполнения не конек метода, но зато мы имеем всего 40 запросов для 40-символьного пароля MySQL при отсутствии вывода каких-либо данных в слепых SQL-инъекциях. Повышая количество запросов, мы можем оптимизировать общее время, ведь даже десяток лишних строк не сильно повысят степень риска быть замеченным. Все вышеописанное относится не только к MySQL, и вполне может быть реализовано на других серверах. Например, в PostreSQL нашу SLEEP() функцию можно заменить на pg_sleep(), ну и так далее.
Это лишь метод, его можно и нужно модифицировать, оптимизировать и расширять. |