Subresource Integrity (SRI) — это механизм, который разработчики используют, чтобы защититься от подмены внешних ресурсов, например JavaScript-библиотек, загружаемых с CDN. В идеале, если злоумышленник попытается изменить файл, браузер обнаружит несоответствие хэша и просто не выполнит код.
На практике же всё не так однозначно. Существует сценарий, известный как CDN race, при котором злоумышленник может обойти проверку SRI за счёт особенностей работы CDN и параллельной загрузки ресурсов браузером. Эта техника не нова, но остаётся актуальной, особенно в больших проектах с высокой нагрузкой и сложными цепочками кеширования.
Что такое SRI и зачем он нужен
SRI ( Subresource Integrity ) — это атрибут integrity
, который добавляют к тегу <script>
или <link>
. Он хранит хэш содержимого файла (обычно SHA-256, SHA-384 или SHA-512). Когда браузер загружает ресурс, он вычисляет его хэш и сверяет с указанным в атрибуте. Если совпадения нет, файл отбрасывается.
Пример использования:
<script src="https://cdn.example.com/lib.js"
integrity="sha384-abc123..."
crossorigin="anonymous"></script>
Это полезно, если вы не полностью доверяете стороннему хостингу. Даже если CDN будет скомпрометирован, браузер защитит пользователя, отказавшись исполнять модифицированный код.
Как работает CDN race
CDN race — это ситуация, при которой разные серверы CDN могут одновременно отдавать разные версии одного и того же файла в зависимости от точки присутствия (PoP) и состояния кеша. Из-за распределённой природы CDN и особенностей обновления контента могут возникнуть "окна" времени, когда часть узлов уже содержит обновлённый (или вредоносный) файл, а другая часть — нет.
Ключевой момент — браузер может параллельно загружать один и тот же ресурс в разных контекстах, например:
- Один и тот же скрипт подключён несколько раз с разными параметрами.
- Скрипт загружается как модуль (
type="module"
) и как обычный скрипт. - Есть прелоад (
<link rel="preload">
) и фактическая загрузка.
Если один из запросов получит "чистую" версию и пройдет SRI-проверку, а другой — вредоносную, может возникнуть гонка за использование кеша и исполнение кода до срабатывания проверки.
Почему это вообще возможно
В идеальном мире SRI всегда проверяет конкретные байты, которые будут исполнены. Но в реальных браузерах есть нюанс: если ресурс уже есть в кеше и он прошёл проверку в одном контексте, этот же кеш может быть повторно использован в другом — даже если запрос пришёл с другой точки CDN и вернул бы другой ответ.
Это открывает пространство для атак:
- Злоумышленник модифицирует файл на части PoP CDN, но не везде.
- Жертва отправляет параллельные запросы к ресурсу.
- Браузер кеширует "чистую" версию после одной проверки.
- Другой контекст или механизм загрузки выполняет подменённый код, минуя повторную проверку.
Пример сценария атаки
Рассмотрим упрощённую модель:
- На сайте подключен
main.js
с SRI и crossorigin. - CDN имеет PoP в Лондоне и Нью-Йорке.
- Атакующий получает доступ к CDN в Нью-Йорке и загружает вредоносную версию.
- Пользователь в Европе загружает страницу, браузер через DNS балансировку отправляет один запрос в Лондон, другой — в Нью-Йорк.
- Лондонский PoP отдаёт легитимную версию, которая проходит SRI-проверку и кешируется.
- Нью-Йоркский PoP отдаёт вредоносный файл, но браузер использует уже кешированную версию или выполняет код до проверки.
В результате защита, казалось бы, есть, но она не спасает от реальной угрозы.
Почему это опасно для больших проектов
Эта техника особенно актуальна для проектов, которые:
- Используют CDN без строгого контроля кеша.
- Загружают один и тот же ресурс из нескольких мест (например, с параметрами
?v=
). - Активно используют прелоад или prefetch.
- Имеют сложные цепочки редиректов и балансировщиков.
Чем сложнее инфраструктура, тем больше вероятность, что где-то можно создать условия для гонки загрузки.
Как защититься от CDN race
Полностью исключить этот риск сложно, но можно минимизировать:
- Использовать fetch API или загрузку ресурсов с ручной валидацией.
- Отключить или минимизировать параллельные загрузки одного ресурса в разных контекстах.
- Жёстко контролировать кеш CDN, включая принудительную синхронизацию PoP перед деплоем.
- Использовать версионирование файлов и загружать уникальные имена при каждом обновлении.
- По возможности хранить критические скрипты на собственных серверах.
Инструменты для тестирования
Если вы хотите проверить свой проект на устойчивость к подобным атакам, можно использовать:
- SecurityHeaders — для проверки SRI и других заголовков.
- Собственные тестовые CDN с эмулированием задержек и разных версий файлов.
- Инструменты вроде mitmproxy для подмены ответов на лету.
Выводы
SRI — полезный механизм, но не панацея. Он защищает от подмены содержимого в большинстве случаев, но в реальной инфраструктуре с CDN остаются уязвимости. CDN race — один из примеров того, как архитектурные особенности могут превратить мощную защиту в дырявый щит.
Если ваш проект использует внешние ресурсы, особенно через глобальные CDN, стоит учитывать такие сценарии. А лучше — проектировать систему так, чтобы критические файлы были под вашим полным контролем.