5.5. Экслуатация SQLi через вызов ошибки (Error-based payload)

Очень часто содержимое базы данных не отображается в бразуере после отправки запроса на сервер. Примером такого поведения является авторизация пользователя на сервере. При успешной авторизации на страницу обычно добавляется дополнительное меню, ссылки которого обычно ведут на персональную страницу пользователя. Другим примером является отправка на сервер отзыва или комментария. В данном случае обычно появляется уведомление, что собщение  было успешно отправлено на сервер.

То есть во всех примерах содержимое базы данных не отображается, а потому не всегда эксплуатация с помощью метода UNION оказывается возможной. В таких случаях мы можем вызвать ошибку исполнения SQL запроса, которая может быть отображена в браузере. Заметьте, что нам нужно вызвать не синстаксическую ошибку, когда SQL выражение содержит ошибку, а именно ошибку в исполнении.

Поясним сказанное на примере приложения bWAPP. Входим на сайт и выбираем опцию SQL Injection (Login Form):

Выбор теста в bWAPP

Здесь предлагается обойти авторизацию, не зная ни логина ни пароля. Мы не будем обходить авторизацию, а просто вызовем ошибку исполнения кода и сервер ее отобразит.

Так как в приложении bWAPP в качестве СУБД выступает MySQL, то наиболее подходящими функциями являются ExtractValue() и UpdateXML(). Обе функции работают с XML. ExtractValue() принимает 2 аргумента: фрагмент XML и выражение XPath.

XPath или XML Path Language – это язык запросов для обращения к XML элементам. Нам не нужно знать XPath, наоброт, необходимо указать неправильное выражение, которое вспоследствии вызовет ошибку.

Допустим, мы хотим узнать текущую версию СУБД. Для это используем выражение extractvalue(‘’, concat(‘<’, version())). В качестве первого аргумента используется пустая строка, во втором аргументе используется функция version() для определения текущей версии базы данных. Результат данной функции объединяется с символом “<”, то есть он просто добавляется в начало.

Для чего мы используем символ “<” и функцию concat()?

Дело в том, что в выражениях XPath используются числа, а также символы “/” (косая черта), “.” (точка), “@” (собачка). Результат работы функции version() как раз и содержит некоторые из перечисленных символов в самом начале, а значит интерпретатор СУБД может их использовать для выполнения запроса. Чтобы этого избежать необходимо в самое начало добавить специальный символ, который XPath не понимает. Хорошими кандидатами являются символы “<” и “>”.

Теперь попробуем применить данное выражение: 1' extractvalue('', concat('<', version())) #

Получение версии базы данных

Результат налицо. Попробуем извлечь более существенную информацию, например, список таблиц в текущей базе данных.

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

SELECT table_name, FROM information_schema.tables WHERE table_schema=’название_БД’

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

Для этого воспользуемся выражением 1' and extractvalue('', concat('>', database())) #

Извлечение имени базы данных

Теперь составляем выражение, чтобы получить список всех таблиц:

1' and extractvalue('', concat('>', (select table_name from information_schema.tables where table_schema='bwapp'))) #

Ошибка SQL

Однако из БД ожидается только одна строка, поэтому добавим дополнительный оператор LIMIT, который ограничит вывод одной строкой и позволит вывести только требуемую строку из всего списка.

В MySQL оператор LIMIT может использоваться с двумя аргументами: LIMIT номер_строки, количество_строк.

Первый аргумент означает номер строки, с которой нужно выводить данные. Нумерация начинается с нуля, то есть, если нужно получить первую строку, то в качестве значения указывается 0, если вторую строку – то 1 и так далее.

Второй аргумент означает количество строк. Нумерация начинается с единицы.

Чтобы получить список всех таблиц мы должно поочередно выполнить запросы LIMIT 0,1, затем LIMIT 1,1, LIMIT 2,1 и так далее. Наш запрос (пейлоад) будет выглядеть так:

1' and extractvalue('', concat('>', (select table_name from information_schema.tables where table_schema='bwapp' limit 0,1))) # - для первой строки

1' and extractvalue('', concat('>', (select table_name from information_schema.tables where table_schema='bwapp' limit 1,1))) # - для второй строки

1' and extractvalue('', concat('>', (select table_name from information_schema.tables where table_schema='bwapp' limit 2,1))) # - для третьей строки

Попробуем получить первую строку:

Извлечено первое имя таблицы

Последущие запросы можно выполнить вручную либо воспользоваться Burp Suite. Откройте встроенный браузер Burp Suite и в нем откройте приложение bWAPP с соответсвующим тестом. Затем в Burp Suite во вкладке Proxy --> Intercept активируйте перехват запросов (Intercept is on):

Активация перехвата в Burp Suite

В браузере введите наш SQL запрос и отправьте на сервер. Запрос будет перехвачен Burp Suite, после чего вы можете отправить его в Intruder для последущего тестирования:

Перехваченный запрос с SQL

В Intruder добавьте маркер к первому аргументу оператора LIMIT:

Установка маркера в Burp Suite Intruder

Затем переключитесь во вкладку Payloads и в качестве нагрузки установите последовательные числа от 0 до 10 с шагом 1. Число 10 было выбрано случайно. Если в процессе теста выясниться, что в БД имеется больше таблиц, то число 10 заменим на большее чило:

Настройки payloads и Burp Suite

Жмем на кнопку Start attack и наблюдаем за процессом в новом окне:

Результат успешной инъекции SQL

Можно ли применить подобную технику и в других СУБД?

Да. Ниже представлены опробованные методы:

СУБД

SQL запрос

Пояснения

MySQL

ExtractValue(‘’, concat(‘>’, version()))

Неверный сиснтаксис в запросе XPath

UpdateXML(null,concat(‘>’,(version())),null)

MS SQL

Cast(@@version as int)

Преобразование в несовместимый или неверный тип переменной

Convert(int,(SELECT @@version))

PostgreSQL

Cast(version() as int)

Oracle DB

to_char(dbms_xmlgen.getxml('select " '|| (select substr(banner,0,30) from v$version where rownum=1)||'" from sys.dual'))

Преобразование в XML вид

utl_inaddr.get_host_name((select banner from v$version where rownum=1))

Получение имя хоста и его IP адреса