Мир SQL-инъекций огромен. В реальной работе сам факт наличия уязвимости — это недостаточное условие для радости, ведь надо еще суметь ей воспользоваться. Инъекции бывают разными, но самые неприятные — это те, которые не возвращают логического результата. Слепые инъекции, особенно в SQLite, — печальная штука. В этом выпуске я расскажу о техниках эксплуатации SQLi в выше упомянутом ПО.

Что же, для тех, кто хочет освежить терминологическую базу по SQLi, напомню основные классы ситуаций. В общем, это применимо ко всем SQL-образным СУБД.

 

WARNING

Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи

 

Простая SQLi.

Банальная SQL-инъекция без ухищрений — какой запрос приходит в БД, такой ответ мы и получаем прямым выводом (например, в ответ веб-сервера). Такой тип эксплуатируется легче всего, не нужно ничего придумывать. Пример:

news.php?id=1

Выводит текст из СУБД, допустим с какой-то новостью. Если выбрать несуществующую новость, то вернет NULL:

news.php?id=-1

Соответственно, чтобы выбрать что-либо «секретное» из БД, достаточно применить операцию UNION SELECT — NULL объединится с новой выборкой, в результате чего именно результат второго селекта и будет возвращен:

news.php?id=-1 union select ‘text’

Соответственно, на экране будет строка «text».

Обнаруживать такие уязвимости так же просто:

news.php?id=1 and 1=1
news.php?id=1 and 1=0

В общем, с этим делом все ясно.

 

Слепая SQLi.

Бывают другие ситуации, когда вывод строго ограничен. Такие инъекции по обнаружению ничем не отличаются от простых, зато по выводу данных отличаются. Дело вот в чем: результат того, что скрипт возвращает, не содержит вывода из СУБД (точнее из того запроса, на который мы воздействуем). Тогда UNION SELECT нам не поможет. Но и методы борьбы с такими вещами тоже известны — изменение логики (истинности или ложности запроса) влияет на контент. Например, такой запрос возвращает текст новости:

news.php?id=1

Очевидно, что и такой запрос вернет новость:

news.php?id=1 and 1=1

Тогда как такой запрос вернет ошибку или еще что:

news.php?id=1 and 1=0

Ну и для выдергивания данных можно действовать по аналогии, не буду тратить время, все это уже и так знают:

news.php?id=1 and (select password from users)like’pass%’

Если пароль действительно начинается с «pass», то вернется оригинальный текст новости, в противном случае — ошибка. Второй вариант эксплуатации — если есть вывод об ошибке, в случае неправильного SQL-запроса. Тогда вывод мы будем оценивать не по истинности или ложности запроса, а по правильности. Кроме того, стоит отметить, что иногда в тексте ошибки можно встраивать данные из СУБД, тем самым делая ее «не слепой» (это если подфартит и вывод ошибок не отключен). Ну и конечно, есть еще вариант с временными задержками при обработке SQL-запроса… Более подробно обо всем этом уже писал Дима Евтеев в далеком 2009-м: «Хакер» № 12 (132). Так что бросим затянувшееся введение и перейдем к делу…

Error-based метод
Error-based метод
 

Абсолютная слепота в SQLite

Так случилось, что столкнулись мы с очень слепой инъекцией в SQLite. Слепота заключалась в том, что логика запроса никак не влияла на вывод. Так что, понятно, объединение запросов не помогало, игра с логикой запроса тоже. Все, что мы знали, — что инъекция есть:

script.php?id=1

Результат: страница
----------------------------
script.php?id=11212

Результат: страница
----------------------------
script.php?id=-99

Результат: страница
----------------------------
script.php?id=-99’

Результат: ошибка без деталей
Вывод: возможно, инъекция
----------------------------
script.php?id=1 and 1=1

Результат: страница
----------------------------
script.php?id=1 and 1=0

Результат: страница
----------------------------
script.php?id=1 andXXX 1=0

Результат: ошибка без деталей
Вывод: точно инъекция

Перебирая все варианты функций, поняли, что sleep(), delay(), @@version, version() система не поддерживает (то есть запросы с этими вызовами возвращают всю ту же ошибку), но, когда попробовали sqlite_version(), ошибки не было! Но вот незадача — как получить что-то из БД? Ну например, тот же номер версии… вот тут-то и зарылась собака. Как уже было сказано, временных задержек в SQLite нету, так что time-based нам не светит. Пытаясь понять, что можно сделать, я вспомнил про возможность загрузки библиотеки через функцию load_extension(), но, к сожалению, использование STATEFUL-фильтрации срезало все исходящие соединения, и потому протроянить цель было невозможно. ATTACH DATABASE для заливки PHP отказался работать (ни перевод строк, ни символ «;» не проходили в запросе). Казалось бы, все. Печаль. Провал. Но мой коллега не захотел сдаваться и продолжил играться с функцией load_extension() — и понял, как можно ее использовать для организации error-based эксплуатации. Так что мы выкрутились так:

news.php?id=1 union select (CASE WHEN sqlite_version() like '3.%' THEN 'here' ELSE load_extension('blah') END) --

Результат: Если это третья версия SQLite, то возвращается оригинальная страница, если нет, то ошибка.

Как видите, идея проста и основана на том, что если срабатывает load_extension(), пытаясь открыть несуществующий файл, то получается ошибка. Таким образом, уже используя логику и влияя на ошибку, можно получать результаты. Соответственно упростив идею можно вообще обойтись без функции Сережи:

news.php?id=1 union select (CASE WHEN sqlite_version() like '3.%' THEN 'here' ELSE (select ‘aaa’ from not_a_table) END) --

Результат: Если это третья версия SQLite, то возвращается оригинальная страница, если нет, то ошибка, так как нет такой таблицы — not_a_table

Уже потом @BlackFan поделился своими векторами работы с SQLite. Идея та же, только еще и вывод в текст ошибки помогает встраивать, и если будет вывод ошибки в браузер, то это станет крайне полезно и эффективно:

CREATE VIRTUAL TABLE t1 USING fts3(x);
SELECT * FROM t1 WHERE t1 MATCH '"'||sqlite_version();

malformed MATCH expression: ["3.6.23.1]
Примечание: Только для таблиц fts3/fts4
------------------------------------------------
select case when 1=2 then 1 else 1 like 1 escape 11 end;
Error: ESCAPE expression must be a single character

Естественно, бывают еще случаи с супер-пупер-двойными-слепыми инъекциями. Это тогда, когда даже в случае ошибочного запроса вывод тот же. Владимир Воронцов рекомендует использовать аналогичную схему, только вместо генерации ошибочного запроса (который тут окажется бесполезен) предлагает использовать любую операцию, которая повлияет на время отклика. Ребята из RDot (ага, тот же @BlackFan) уже пытали эту технику и достигли успеха. Так, наши друзья советуют использовать функцию randomblob(). Ну и в качестве полноты картины смотри пример с временной задержкой:

news.php?id=1 union select (CASE WHEN sqlite_version() like '3.%' THEN 'here' ELSE randomblob(50000000) END)--

Результат: Если это третья версия SQLite, то возвращается оригинальная страница и быстро, если нет, то возвращаться будет долго. Играя с параметром функции, можно установить приемлемое для брутфорса время. (Только имейте в виду, что для эффективной работы ложные ответы должны быть быстрыми, а правильные — тормозить, так как брутфорс — это все-таки большее количество неверных запросов…)

Time-based метод — тормоз-запрос (неверный). Для брутфорса лучше наоборот :)
Time-based метод — тормоз-запрос (неверный). Для брутфорса лучше наоборот 🙂
Time-based метод — быстрый запрос (верный)
Time-based метод — быстрый запрос (верный)

Как видите, логика эксплуатации error-based и time-based инъекций одинакова для всех SQL-образных СУБД. Даже для такой полуобрезанной и маленькой, как SQLite. Не сдавайтесь и верьте в себя!

1 комментарий

  1. dmi01

    20.07.2017 at 22:21

    Можно ли переводить символ который мы вытаскиваем инъекцией в двоичную систему (в SQLite есть ASCII() и MOD() функции) и соответственно возвращать побитово (ноль есть ошибка один нет если мы говорим про error based) и тоже самое с задержками если мы говорим про абсолютный блайнд. Если мы брутфорсим то нам надо делать по тридцать запросов на символ если только прописные буквы, а реально порядка семидесяти. А так должно получиться семь (врядли понадобится вторая половина ascii) Имеет ли эта идея какойто смысл и стоит ли пробовать ее реализовывать

Оставить мнение

Check Also

Скрытая сила пробела. Эксплуатируем критическую уязвимость в Apache Tomcat

В этой статье мы поговорим о баге в Apache Tomcat, популярнейшем веб-сервере для сайтов на…