# Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 2)

Из последнего абзаца предыдущего поста видно, что когда `XSLeaks` комбинируется с поиском, и это может создать больший эффект - этот метод атаки называется `XS-Search`.

Причина, по которой он имеет больший эффект, заключается в том, что результаты поиска обычно считаются конфиденциальными. Как только результаты поиска могут быть выведены определенными способами, злоумышленники могут получить чувствительную информацию через функцию поиска. Итак, какие средства могут быть использованы?

Помимо методов, упомянутых в предыдущем посте, в этом посте будет обсужден более распространенный метод: `Cache probing`.

### Cache Probing

Кэш-механизмы повсеместно распространены в мире компьютерных наук. В плане отправки запросов вы можете реализовать свой собственный кэш на JavaScript, где повторные запросы не отправляются снова. Браузеры также реализуют кэш-механизмы на основе HTTP-спецификации, а DNS также имеет кэширование при отправке запросов!

Браузеры имеют кэширование DNS, операционные системы имеют свои собственные кэши, а DNS-серверы также имеют свои собственные кэши. Кэширование повсюду, и даже у процессоров есть кэш-механизмы, такие как L1 и L2, которые обменивают пространство на время для ускорения выполнения.

Если вы помните, уязвимости процессора Spectre и Meltdown, упомянутые ранее, связаны с кэшированием. На самом деле, они похожи на технику `Cache probing`, обсуждаемую в этом посте.

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

Например, предположим, что веб-сайт отображает приветственную страницу, если пользователь вошел в систему, с изображением `welcome.png` на ней. Если пользователь не вошел в систему, его перенаправят на страницу входа. Как только изображение отображается, оно будет сохранено в кэше браузера.

Поскольку изображения в кэше загружаются быстрее, мы можем определить, находится ли изображение в кэше, измеряя время, необходимое для загрузки `welcome.png`. Это позволяет нам определить, вошел ли пользователь в систему.

Давайте рассмотрим случай из прошлого, чтобы понять это быстрее. В 2020 году Securitum обнаружил уязвимость во время проведения тестирования на проникновение на польском приложении ProteGo Safe: [Leaking COVID risk group via XS-Leaks](https://www.youtube.com/watch?v=Cknka1pN268\&ab_channel=Securitum).

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

На основе анкеты для отчетности о состоянии можно было получить четыре результата:

1. High
2. Medium
3. Low
4. Very low

В зависимости от результата на странице отображались разные изображения (такие как `high.png`, `medium.png` и так далее). Например, самый низкий риск был бы представлен символом, указывающим на безопасность.

Исходя из этого, автор определил состояние здоровья пользователя, измеряя время, необходимое для загрузки изображения. Если загрузка `high.png` была самой быстрой, это означало, что состояние пользователя было `high`. Код, использованный для обнаружения, выглядит следующим образом:

{% code overflow="wrap" %}

```html
<img src="https://example.com/high.png">
<img src="https://example.com/medium.png">
<img src="https://example.com/low.png">
<img src="https://example.com/very_low.png">
```

{% endcode %}

Время загрузки каждого изображения можно получить с помощью `performance.getEntries()`, что позволяет нам определить, какое изображение загружается быстрее.

<figure><img src="https://4101843849-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9aPqL1pJrSP93yQXPt21%2Fuploads%2FUkxL9cmcAhtFRliYCfHd%2F32-01-f43792b55065abb2d39a6f631d2da3a7.png?alt=media&#x26;token=7a41d830-ac66-43c9-83a4-15107d7cd406" alt=""><figcaption></figcaption></figure>

Однако есть небольшая проблема: этот трюк можно использовать только один раз. После первого открытия веб-сайта все четыре изображения будут загружены, и при следующем тесте все четыре будут в кэше и загрузятся быстро.&#x20;

Поэтому нам нужно найти способ очистить кэш для пользователя. Когда браузер получает ответ с кодом состояния ошибки (4xx и 5xx), он очищает кэш. Итак, как мы можем сделать так, чтобы ответ для `https://example.com/high.png` вернул ошибку?

Автор использовал здесь хитрый прием. Поскольку этот веб-сайт размещен на Cloudflare и имеет включенную функцию WAF (Web Application Firewall), он автоматически блокирует определенные полезные нагрузки. Например, URL `https://example.com/high.png?/etc/passwd` будет заблокирован, потому что он содержит подозрительный `/etc/passwd`, что приведет к коду состояния 403.

Поэтому автор добавил `?etc/passwd` к странице и использовал следующий код:

{% code overflow="wrap" %}

```javascript
fetch(url, {  cache: 'reload',  mode: 'no-cors',  referrerPolicy: 'unsafe-url'})
```

{% endcode %}

Таким образом, отправленное изображение будет иметь заголовок `referrer`, содержащий `/etc/passwd`, что приведет к блокировке его сервером и возврату ошибки, тем самым очищая кэш.

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

### Cache Probing с событиями ошибок

Хотя определение того, находится ли ресурс в кэше, на основе времени является эффективным методом, оно иногда может быть затруднено из-за неопределенности сети. Например, если сеть работает очень быстро, может быть трудно определить, какой ресурс закэширован, когда каждый ресурс, кажется, имеет время отклика 1 мс или 2 мс.

Поэтому существует еще одна техника атаки, которая сочетает метод использования `<img>` для определения того, загружено ли изображение, как упоминалось ранее, с `Cache probing`. Вместо того чтобы полагаться на время, она использует события ошибок для определения, находится ли ресурс в кэше.

Предположим, есть страница под названием `https://app.example.com/search?q=abc`, которая отображает разный контент в зависимости от результата поиска. Если что-то найдено, появится изображение `https://app.example.com/found.png`; в противном случае изображение не будет присутствовать.

Сначала, как и в предыдущем шаге, нам нужно очистить кэш. Существует несколько методов на выбор, один из которых похож на метод `Cookie Bomb`, упомянутый ранее, где мы заставляем сервер вернуть ошибку, отправляя большой запрос, тем самым очищая кэш в браузере:

{% code overflow="wrap" %}

```javascript
// код изменен с https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels#cache-and-error-events
let url = 'https://app.example.com/found.png'; 
// это добавляет много символов к URL, чтобы сделать заголовок запроса огромным
history.replaceState(1, 1, Array(16e3)); 
// отправить запрос
await fetch(url, { cache: 'reload', mode: 'no-cors' });

```

{% endcode %}

Второй шаг — загрузить целевой веб-сайт `https://app.example.com/search?q=abc`. В этот момент страница будет отображаться в зависимости от результата поиска. Как упоминалось ранее, если что-то найдено, изображение `https://app.example.com/found.png` появится и будет сохранено в кэше браузера.

Последний шаг — сделать URL очень длинным и затем снова загрузить изображение:

{% code overflow="wrap" %}

```javascript
// код изменен с https://github.com/xsleaks/xsleaks/wiki/Browser-Side-Channels#cache-and-error-events
let url = 'https://app.example.com/found.png'; 
history.replaceState(1, 1, Array(16e3)); 
let img = new Image(); 
img.src = url; 
try {  
    await new Promise((r, e) => { img.onerror = e; img.onload = r; });  
    alert('Ресурс был закэширован'); // В противном случае это вызвало бы ошибку
} catch (e) {  
    alert('Ресурс не был закэширован'); // В противном случае он бы загрузился
}
```

{% endcode %}

Если изображение не в кэше, браузер отправит запрос для его получения. Это приведет к той же ситуации, что и на первом шаге, когда сервер возвращает ошибку из-за слишком длинного заголовка, что вызывает событие onerror.

С другой стороны, если изображение в кэше, браузер напрямую использует закэшированное изображение, не отправляя запрос. После загрузки изображения из кэша будет вызвано событие onload.

Таким образом, мы можем устранить нестабильный фактор времени и использовать кэш вместе с событиями ошибок для выполнения XSLeaks.

### Реальный пример использования Google XS-Search

Давайте рассмотрим реальный случай, когда была применена эта техника.

В 2019 году terjanq обнаружил уязвимость XS-Search в различных продуктах Google и написал статью под названием [Massive XS-Search over multiple Google products](https://terjanq.medium.com/massive-xs-search-over-multiple-google-products-416e50dd2ec6)Технические детали можно найти в статье [Mass XS-Search using Cache Attack](https://terjanq.github.io/Bug-Bounty/Google/cache-attack-06jd2d2mz2r0/index.html). Затронутые продукты включают:

1. My Activity
2. Gmail
3. Google Search
4. Google Books
5. Google Bookmarks
6. Google Keep
7. Google Contacts
8. YouTube

С помощью этих техник атаки `XS-Search` злоумышленник может получить информацию, такую как:

1. Search history
2. Watched videos
3. Email content
4. Private notes
5. Web pages bookmarked

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

Например, в Gmail есть функция "Расширенный поиск", аналогичная Google Search, где вы можете указать условия поиска с помощью фильтров. URL этой функции поиска также можно скопировать и вставить, чтобы напрямую открыть страницу поиска.

Если поиск успешен, появится определенный значок: `https://www.gstatic.com/images/icons/material/system/1x/chevron_left_black_20dp.png`

В этот момент мы можем использовать упомянутую ранее технику, чтобы определить, существует ли конкретное ключевое слово в поиске (скрин из видео пока - [PoC video](https://www.youtube.com/watch?v=H3JTx0JhAng\&ab_channel=terjanq)):

<figure><img src="https://4101843849-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9aPqL1pJrSP93yQXPt21%2Fuploads%2FD4XDjZkS6cDLZuNJczpG%2F32-02-65296f3f4b2bb7c4b8185e65da1f3cd4.png?alt=media&#x26;token=7f93f9a1-ab9c-4335-808c-c55c81d9fbd7" alt=""><figcaption></figcaption></figure>

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

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

1. Your password is 1
2. Your password is 2
3. Your password is 3
4. ...

Таким образом, мы можем раскрыть первый символ пароля. После утечки мы продолжаем поиск:

1. Your password is 11
2. Your password is 12
3. Your password is 13
4. ...

Это может утечь второй символ, и, продолжая пробовать, можно утечь полный пароль.

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

Из эксперимента terjanq видно, что `XSLeaks`, использующие кэш, могут быть выполнены на многих продуктах, и это осуществимо. Кроме Google, должно быть много других веб-сайтов, которые могут выдавать информацию, используя аналогичные методы.

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

### Cache Partitioning

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

Поэтому Chrome ввел новый механизм в 2020 году: разделение кэша. Ранее кэш использовался каждым веб-сайтом, и ключ кэша был URL, что позволяло `XSLeaks` использовать существование кэша для утечки информации.

С введением разделения кэша ключ кэша изменился. Теперь это кортеж, состоящий из следующих трех значений:

1. Top-level site
2. Current-frame site
3. Resource URL

В примере предыдущей атаки, предположим, изображение `https://app.example.com/found.png` загружается с `https://app.example.com/search?q=abc`. Ключ кэша будет:

1. `https://example.com`
2. `https://example.com`
3. `https://app.example.com/found.png`

А если изображение `https://app.example.com/found.png` загружается с другой страницы `https://localhost:5555/exploit.html`, ключ кэша будет:

1. `http://localhost:5555`
2. `http://localhost:5555`
3. `https://app.example.com/found.png`

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

Поскольку используются разные кэши, злоумышленники не могут выполнять атаки `Cache probing` с других страниц, чтобы обнаружить существование кэша.

Эта реализация разделения кэша также имеет некоторое влияние на обычные веб-сайты. Один из примеров — общий CDN. Некоторые веб-сайты, такие как [cdnjs](https://cdnjs.cloudflare.com/), размещают множество библиотек JavaScript бесплатно, что облегчает их загрузку на веб-сайты:

{% code overflow="wrap" %}

```html
<script  src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"  integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg=="  crossorigin="anonymous"></script>
```

{% endcode %}

Одним из основных преимуществ является более быстрая скорость загрузки благодаря кэшированию. Предположим, многие веб-сайты используют сервис `cdn.js`. Если вы загрузили этот файл на сайте A, он не будет загружен снова на сайте B.

Однако с разделением кэша это больше невозможно, потому что сайт A и сайт B будут иметь разные ключи, поэтому файл все равно будет загружен снова.

Наконец, стоит упомянуть, что разделение кэша в первую очередь зависит от `"Site"`, а не от `"Origin"`. Поэтому, если вы находитесь в ситуации `Same-Site`, разделение кэша не имеет значения.

В приведенном ранее примере, что если атака запускается не с <http://localhost:5555>, а с <https://test.example.com>? Ключ кэша будет:

1. <https://example.com>
2. <https://example.com>
3. <https://app.example.com/found.png>

Это то же самое, что и загрузка изображения с <https://app.example.com/search?q=abc>, поэтому атаку `Cache probing` все еще можно выполнить.

Кроме того, в `headless Chrome` по умолчанию не включено разделение кэша. Поэтому, если вы используете Puppeteer в `headless` режиме для доступа к веб-сайтам, они все равно будут делить один и тот же ключ кэша.

### More XSLeaks

Из-за ограничений по объему я представил только несколько методов `XSLeaks`. На самом деле существует много других техник.

Кроме обращения к базе знаний [XS-Leaks Wiki](https://xsleaks.dev/) была опубликована статья в 2021 году под названием "XSinator.com: From a Formal Model to the Automatic Evaluation of Cross-Site Leaks in Web Browsers", которая обнаружила много новых методов `XSLeaks` с использованием автоматизированных техник.

Они также предоставляют веб-сайт, который объясняет, какие версии браузеров подвержены уязвимостям: <https://xsinator.com/>

Существует всего пять категорий данных, которые могут утечь:

1. Status code
2. Redirects
3. API usage
4. Page Content
5. HTTP header

Каждая из этих категорий имеет свои специфические методы для достижения утечек.

Например, в категории перенаправлений есть метод под названием "Max Redirect Leak", который использует максимальное количество разрешенных перенаправлений, чтобы определить, выполняет ли веб-страница серверные перенаправления.

Принцип следующий: в спецификации `fetch` есть ограничение на количество перенаправлений для ответа:

> If request's redirect count is 20, then return a network error.

Поэтому, предположим, что нашей целью для тестирования является `http://target.com/test`, мы сначала создаем API на нашем собственном сервере, который перенаправляет 19 раз, с последним перенаправлением на `http://target.com/test`.

Если ответ от `http://target.com/test` является перенаправлением, это вызовет максимальный лимит в 20 перенаправлений и вызовет ошибку сети. Если это не перенаправление, то ничего не произойдет.

Проверяя, возникает ли ошибка во время выполнения `fetch()`, мы можем определить, перенаправляется ли `http://target.com/test`.

Веб-сайт `xsinator` содержит много интересных техник `XSLeaks`. Если вам интересно, вы можете ознакомиться с ним.

### Заключение

В этой статье мы продолжаем обсуждать `XSLeaks`, о которых упоминали в предыдущей статье, и представляем метод атаки, использующий кэш в качестве оракула утечки. Это распространенная практика в мире атак бокового канала. Кроме того, мы предоставляем некоторые примеры из реальной жизни, чтобы показать влияние применения `XSLeaks` на реальные веб-сайты. Например, пример с Google демонстрирует, что сочетание XSLeaks с функциональностью поиска может создать даже большее воздействие, чем предполагалось.

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

Хотя `XSLeaks`, как косвенный метод атаки, может не иметь такого же уровня воздействия, как прямые атаки `XSS`, он все равно довольно увлекателен.
