Обход Same Origin Policy
Moody
Приветствую.
Производительность веб-сайтов и приложений можно значительно повысить за счет повторного использования ранее собранных ресурсов. Веб-кэши сокращают задержку и снижают сетевой траффик, уменьшая тем самым время, необходимое для отображения ресурсов. Используя HTTP-кеширование, сайты становятся более отзывчивыми.
Однако, некорректно настроенное кэширование может привести к появлению различных уязвимостей на вашем ресурсе. Так, например, злоумышленники могут использовать неправильно настроенные промежуточные серверы (реверсные прокси серверы, балансировщики нагрузки или кэширующие прокси) для получения доступа к конфиденциальным данным. Или ещё один метод - атаки с использованием отравленного кэша.
Кэш браузера может выглядеть как очень безопасное место для временного хранения личной информации. Основной риск заключается лишь в том, что злоумышленник может получить к нему доступ через файловую систему. Однако, принято считать, что такая уязвимость является уязвимостью с низким уровнем опасности. Однако в некоторых случаях неправильно сконфигурированные заголовки, связанные с кэшем, могут вызвать более серьезные проблемы безопасности.
Основные риски кросс-доменных запросов
Некоторые веб-сайты имеют множество поддоменов, которые должны обмениваться информацией друг с другом. Камнем преткновения в реализации такой задачи является политика одинакового источника (SOP), согласно которой обмениваться информацией могут лишь источники, у которых совпадают три признака: домен, порт и протокол. Однако, существует несколько методов, которые позволяют обойти такое ограничение, например, использование JSONP (JSON with Padding). Разработчики, использующие подобные методы, должны реализовать хорошую защиту от утечки данных на другие сайты.
Приведу пример. Например, мы имеем сайт с двумя поддоменами: blog.example.com и account.example.com. На сайте account.example.com есть конечная точка JSONP, которая возвращает конфиденциальные данные пользователя на основе пользовательских файлов Cookie. Чтобы предотвратить утечки важной информации, эта конечная точка проверяет содержимое заголовка Referer с записями из белого списка, который включает в себя строку blog.example.com.
С такой настройкой, если пользователь зайдет на вредоносный сайт, злоумышленник не сможет напрямую украсть его конфиденциальные данные. Однако, если конечная точка JSONP устанавливает заголовки, связанные с кэшем, злоумышленник сможет получить доступ к личной информации из кэша браузера.
Управление кэшированием
Разные браузеры имеют немного отличающиеся друг от друга реализации кеша, однако их основные аспекты работы cхожи. Прежде всего, нужно знать, что обычно они кэшируют только ответы, полученные методом GET, а другие отклоняют. Когда браузер получает ответ на свой GET-запрос, он проверяет его на наличие следующих заголовков:
- Если ответ содержит заголовок Cache-Control: private или Cache-Control: public, ответ кэшируется либо в приватный кэш, либо в общий, соответственно.
- Если в Cache-Control не указано, какое время должен храниться кэш (время устаревания), браузер проверяет наличие заголовка Expires и, если находит, ответ кэшируется в соответствии с его значением. По сути, это Cache-Control, но с меньшим приоритетом.
- Если нету вышеназванных заголовков, браузеры проверяют заголовок Last-Modified и кэшируют ответ на время, равное значению заголовка Date минус значение заголовка Last-modified разделить на 10.
- Если заголовки, связанные с кэшем, вообще отсутствуют, браузер все-равно может кэшировать ответ и повторно перепроверять его перед использованием.
Проблемы могут возникнуть из-за того, что для всех веб-сайтов существует только один кеш, и для идентификации используется лишь один ключ: полноценный (абсолютный) URI (scheme://host:port/path?query). Это означает, что в кеше браузера нет дополнительной информации о запросе, инициировавшем конкретный ответ (например, сайт-источник, с которого он поступил, функция JavaScript или тег, инициировавший его). Любой сайт получает кэшированный ответ от account.example.com, если он инициирует GET-запрос к тому же URI.
Реализация атаки
Пошаговое объяснение возможного сценария для атаки:
- Пользователь заходит на blog.example.com.
- Скрипт на blog.example.com запрашивает информацию о пользователе.
- Браузер пользователя отправляет запрос конечной точке JSONP по адресу account.example.com.
- Ответ от конечной точки JSONP на account.example.com содержит заголовки, связанные с кэшированием.
- Браузер пользователя кеширует содержимое ответа.
- Пользователя заманивают на вредоносный сайт.
- Вредоносный сайт содержит скрипт, который указывает на конечную точку JSONP, на account.example.com.
- Браузер возвращает кэшированный ответ скрипту на вредоносном сайте.
В этой ситуации заголовок Referer никогда не проверяется, поскольку ответ поступает из кэша. Таким образом, злоумышленник получает доступ к кешированной личной информации пользователя.
Похожие уязвимости
Аналогичный подход может быть применен для реализации межсайтового скриптинга (например, XSSI). Помимо этого, можно обходить и другие проверки на стороне сервера, например, заголовок Origin, атрибут Cookie или новомодный SameSite.
Предположим, что account.example.com использует совместное использование ресурсов (CORS) вместо конечной точки JSONP. Он возвращает заголовок Access-Control-Allow-Origin: *, при обращении к нему, а также подхватывает специальный токен из пользовательского заголовка для аутентификации пользователя.
Если ответы кэшируются, злоумышленник может украсть личную информацию, отправив запрос на тот же URI. Защита CORS отсутствует (из-за наличия Access-Control-Allow-Origin: *), и браузер пользователя будет возвращать кэшированные данные без проверки маркера пользовательского заголовка.
Вы можете увидеть, как эти уязвимости работают на практике, проанализировав результаты работы консоли браузера на тестовом сайте.
Как защититься от обхода SOP
Как я уже упомянул, такая уязвимость возникает при неправильной конфигурации. И решается это очень просто - отключение кэширования при взаимодействии различных источников. Если вы используете фреймворки, то большинство из них либо не устанавливают заголовки, связанные с кэшем, либо устанавливают их правильно по умолчанию (Cache-Control: no-store). Тем не менее, вы должны всегда проверять наличие таких заголовков, чтобы быть в безопасности.