При загрузке скриптов, таблиц стилей, шрифтов или изображений из сторонних сетей доставки контента или внешних источников проверяйте их криптографическую целостность с помощью Subresource Integrity (integrity в HTML и integrity() в CSS url()), настраивайте обработку учетных данных из разных источников (crossorigin и cross-origin()) и ограничивайте исходящие заголовки Referer (referrerpolicy и referrer-policy()). Чтобы узнать больше о Content Security Policy на уровне документа и белых списках исходящих подключений, ознакомьтесь со статьями Веб-безопасность и Как ограничить исходящие сетевые подключения с помощью белых списков подключений.
1. Создавайте криптографические дайджесты целостности для неизменяемых объектов.
Целостность субресурсов (SRI) гарантирует, что браузер выполняет или отображает внешний объект, только если его байты соответствуют криптографическому хешу, объявленному вашим приложением.
- Вычисляйте дайджесты во время сборки или выпуска, а не на основе ответов, полученных в реальном времени и не заслуживающих доверия. Генерируйте хеши целостности на этапе сборки или выпуска на основе байтов локальных объектов, которые точно не содержат ошибок (или скопируйте официальный дайджест, опубликованный автором пакета), и задайте их в коде HTML, CSS или JavaScript. Никогда не извлекайте сторонний URL в браузере во время выполнения, чтобы вычислить его собственный хеш
integrity. Это приведет к тому, что будут приняты любые байты, возвращенные удаленным сервером, и нарушит SRI. - Использовать
sha384-по умолчанию. Метаданные целостности форматируются как<algorithm>-<base64-digest>с помощьюsha384-(рекомендуется по умолчанию),sha512-илиsha256-. - Привязывайте внешние подресурсы к неизменяемым URL с указанием версии. Всегда ссылайтесь на сторонние ресурсы, используя неизменяемые URL с указанием версии или адреса контента (например,
https://cdn.example.com/ui@2.4.0/app.min.js), а не изменяемые плавающие URL (latest/app.min.js), которые делают ваш сайт уязвимым для незаметных изменений в цепочке поставок и нарушают SRI, как только удаленный файл обновляется. Если вам нужно загрузить изменяемый сторонний скрипт, который обновляется на месте (например, тег аналитики поставщика), не указывайтеintegrityв URL и ограничьте его с помощью Content Security Policy (strict-dynamic/nonce) или изолированной среды iframe. - Если конечная точка может передавать несколько допустимых полезных нагрузок, укажите несколько хешей, разделенных пробелами. Все браузеры, поддерживающие SRI, реализуют
sha256-,sha384-иsha512-. Если URL может возвращать один из нескольких допустимых пакетов данных (например, во время A/B-тестирования, поэтапного развертывания CDN или сборки с согласованием контента), укажите несколько хешей одного алгоритма, разделенных пробелом (например,integrity="sha384-variantA... sha384-variantB..."). Браузер примет ответ, если он соответствует любому из указанных хешей самого надежного алгоритма. - Используйте заголовки CORS для передачи дополнительных ресурсов из разных источников. Браузеры могут проверять целостность ответов из разных источников, только если они доступны для чтения CORS (
Access-Control-Allow-Origin: *для анонимных запросов или точный источник запроса сAccess-Control-Allow-Credentials: trueдля запросов с учетными данными).
# Generate a sha384- SRI hash at build time from a known-good local file
printf "sha384-%s\n" "$(openssl dgst -sha384 -binary app.min.js | openssl base64 -A)"
// Build-time / tooling helper (Node.js 19+, Deno, Bun) to compute an SRI string from known-good local bytes
export async function computeIntegrityMetadata(bytes) {
const rawBytes =
typeof bytes === 'string' ? new TextEncoder().encode(bytes) : bytes;
const digestBuffer = await crypto.subtle.digest('SHA-384', rawBytes);
const binary = Array.from(new Uint8Array(digestBuffer), (b) =>
String.fromCharCode(b),
).join('');
return `sha384-${btoa(binary)}`;
}
2. Защита HTML-скриптов, таблиц стилей и предварительных загрузок
Установите заголовок HTTP-ответа Referrer-Policy: strict-origin-when-cross-origin на уровне документа (или включите <meta name="referrer" content="strict-origin-when-cross-origin"> в <head>) в качестве базового значения по умолчанию, чтобы любой подресурс или CSS url(), который опускает модификатор URL перехода для каждого запроса, по-прежнему удалял конфиденциальный путь и параметры запроса в запросах из разных источников.
Затем для внешних элементов <script>, <link rel="stylesheet">, <link rel="preload"> и <link rel="modulepreload"> всегда задавайте integrity, crossorigin и referrerpolicy вместе:
integrity="sha384-..."– блокирует выполнение или применение ресурса, если байты ответа не соответствуют ожидаемому дайджесту (для заблокированного<link rel="stylesheet">linkElement.sheetостаетсяnullи запускается событиеerror).crossorigin="anonymous"(рекомендуемое значение по умолчанию для общедоступных объектов CDN) илиcrossorigin="use-credentials". Требуется, если для ресурса из разных источников задан заголовокintegrity. Если не указатьcrossorigin, браузер получит ресурс в режимеno-cors(что приведет к непрозрачному ответу, который нельзя проверить с помощью хеша) и заблокирует его.referrerpolicy="no-referrer"(рекомендуется по умолчанию для сторонних CDN) илиreferrerpolicy="strict-origin-when-cross-origin". Переопределяет правила в отношении URL перехода, чтобы предотвратить утечку информации об источнике или пути в заголовке запросаRefererна сторонний хост.
<!-- Document-level baseline referrer policy in <head> (or via Referrer-Policy HTTP header) -->
<meta name="referrer" content="strict-origin-when-cross-origin" />
<!-- External stylesheet with SRI, anonymous CORS, and no referrer -->
<link
rel="stylesheet"
href="https://cdn.example.com/ui@2.4.0/styles.css"
integrity="sha384-bs0EksOYBbU/g0IAHMXbSyH1Mgw83GKn+EZlSI+VE+zVchXmSbLwhVLrPzsOWnc5"
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<!-- External script with SRI, anonymous CORS, and no referrer -->
<script
src="https://cdn.example.com/ui@2.4.0/bundle.min.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
При программном получении внешних подресурсов с помощью fetch() передайте соответствующие параметры integrity, mode, credentials и referrerPolicy (несоответствие дайджеста отклоняет обещание с помощью TypeError):
const response = await fetch('https://cdn.example.com/ui@2.4.0/manifest.json', {
integrity: 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC',
mode: 'cors',
credentials: 'omit',
referrerPolicy: 'no-referrer',
});
3. Защитите изображения, шрифты и импортированные таблицы стилей CSS с помощью модификаторов url().
Модификаторы запросов CSS url() (cross-origin(), referrer-policy() и integrity()) управляют режимом получения, заголовком реферера и проверкой целостности подресурсов для ресурсов, загружаемых с помощью CSS, включая правила background-image, content, mask-image, border-image-source, filter, @font-face src и @import, непосредственно из CSS:
| Функция-модификатор | Синтаксис аргументов и допустимые значения | Цель |
|---|---|---|
cross-origin(<ident>) |
Ключевое слово без кавычек: anonymous (рекомендуется по умолчанию) или use-credentials. |
Получает ресурс CSS с помощью CORS и управляет отправкой учетных данных. Всегда указывайте вместе с integrity() в ресурсах CSS из разных источников. |
referrer-policy(<ident>) |
Ключевое слово без кавычек: no-referrer (рекомендуемое значение по умолчанию для сторонних объектов), strict-origin-when-cross-origin, origin, same-origin, strict-origin, origin-when-cross-origin, no-referrer-when-downgrade или unsafe-url. |
Переопределяет правила в отношении источника запроса для этого конкретного запроса CSS. |
integrity("<string>") |
Строка в кавычках: "sha384-<base64>" (или хеши, разделенные пробелами "sha384-... sha384-...") |
Перед отрисовкой или применением полученного ресурса CSS проверяет его на соответствие ожидаемому криптографическому дайджесту. |
При создании модификаторов url() следуйте приведенным ниже правилам синтаксиса и безопасности CSS.
- Заключите строку URL в кавычки внутри
url("..."). Модификаторы запросов действительны, только если URL внутриurl(...)заключен в кавычки<string>(URL без кавычек не могут использовать модификаторы). - Указывайте каждый тип модификатора не более одного раза в элементе
url(). Вы можете комбинировать элементыcross-origin(),referrer-policy()иintegrity()в любом порядке внутри элементаurl("..." ...), но повторять один и тот же тип модификатора дважды (например, два модификатораintegrity()) нельзя. Если вы хотите указать несколько хешей, разделите их пробелами в одной строкеintegrity("sha384-... sha384-..."). - Всегда используйте
integrity()вместе сcross-origin(anonymous). Хотя@font-face,srcиmask-imageпо умолчанию получают данные в анонимном режимеcors,background-image,content,border-image-sourceи@importпо умолчанию используют режимno-cors(создавая непрозрачный ответ, который нельзя проверить с помощью хеша), а внешний SVGfilterпо умолчанию использует режимsame-origin(блокируя перекрестные URL). Добавлениеcross-origin(anonymous)(илиcross-origin(use-credentials)) обеспечивает согласованную проверку CORS для каждого свойства CSS. - Помещайте модификаторы внутри
url("...")в правилах@importи@font-face. Оставляйтеcross-origin(),referrer-policy()иintegrity()внутриurl("..." ...), а дескрипторы шрифтов, такие какformat("woff2")иtech(...), – за пределамиurl(...).
@import url(
"https://cdn.example.com/theme@1.2.0/tokens.css"
cross-origin(anonymous)
referrer-policy(no-referrer)
integrity("sha384-bs0EksOYBbU/g0IAHMXbSyH1Mgw83GKn+EZlSI+VE+zVchXmSbLwhVLrPzsOWnc5")
);
@font-face {
font-family: "BrandSans";
src: url(
"https://cdn.example.com/fonts@1.0.0/brand-sans.woff2"
cross-origin(anonymous)
referrer-policy(no-referrer)
integrity("sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC")
) format("woff2");
font-display: swap;
}
.verified-graphic {
background-image: url(
"https://cdn.example.com/media@1.0.0/badge.svg"
cross-origin(anonymous)
referrer-policy(no-referrer)
integrity("sha384-Iw/0p59dP96nGTzHvf2XIfqcBwBW7RM6JfOij8uZxyg5azfNaOOUW8SQy6H/hU91")
);
}
Резервные стратегии
Если целевая версия Baseline пока не поддерживает модификаторы запросов CSS url() (cross-origin(), referrer-policy() или integrity()), объедините правила на уровне документа с каскадными объявлениями CSS, чтобы подресурсы оставались функциональными и защищенными:
- Если требуется кроссбраузерная проверка целостности субресурсов, используйте HTML-тег
<link rel="stylesheet">, а не CSS-правило@import. Поскольку атрибутыsubresource-integrityиreferrer-policyтега<link rel="stylesheet">поддерживаются всеми браузерами, загружайте внешние таблицы стилей с помощью<link rel="stylesheet" href="..." integrity="sha384-..." crossorigin="anonymous" referrerpolicy="no-referrer">, а не@import, если проверку целостности нужно выполнять во всех браузерах. - Положитесь на заголовок
Referrer-Policyили элемент<meta name="referrer">на уровне документа, добавленные на шаге 2. В браузерах, которые не поддерживаютreferrer-policy()в CSS-правилеurl(), заголовокReferrer-Policy: strict-origin-when-cross-originили элемент<meta name="referrer" content="strict-origin-when-cross-origin">на уровне документа автоматически удаляет конфиденциальные параметры пути и параметры запроса из перекрестных CSS-запросов. Используйте каскадные отдельные объявления CSS для прогрессивного улучшения. Браузеры, которые не распознают модификатор внутри
url("..." ...), отбрасывают все объявление при синтаксическом анализе CSS. Сначала объявите обычныйurl("..."), затемcross-origin()иreferrer-policy()(для браузеров, которые поддерживают эти два модификатора, но не поддерживаютintegrity()), а затем полное объявление, включаяintegrity("sha384-..."):- Никогда не указывайте непроверенный
url()в том же списке@font-facesrc, разделенном запятыми. Если вы укажетеsrc: url("font.woff2" cross-origin(anonymous) integrity("sha384-...")), url("font.woff2");в одном дескриптореsrc, то во время выполнения будет обойдена проверка целостности субресурсов. Если CDN предоставит измененный шрифт, который не пройдет проверкуintegrity(), браузер расценит это как ошибку загрузки и автоматически вернется ко второмуurl("font.woff2"), разделенному запятыми. Использование отдельных объявленийsrc:гарантирует, что поддерживающие браузеры перезапишут непроверенное объявление во время синтаксического анализа CSS и заблокируют поддельные шрифты во время выполнения.
@font-face { font-family: "BrandSans"; /* 1. Parse-time fallback for browsers without url() modifier support */ src: url("https://cdn.example.com/fonts@1.0.0/brand-sans.woff2") format("woff2"); /* 2. Parse-time fallback for browsers supporting cross-origin() and referrer-policy() */ src: url( "https://cdn.example.com/fonts@1.0.0/brand-sans.woff2" cross-origin(anonymous) referrer-policy(no-referrer) ) format("woff2"); /* 3. Full protection in browsers supporting cross-origin(), referrer-policy(), and integrity() */ src: url( "https://cdn.example.com/fonts@1.0.0/brand-sans.woff2" cross-origin(anonymous) referrer-policy(no-referrer) integrity("sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC") ) format("woff2"); font-display: swap; } .verified-graphic { background-image: url("https://cdn.example.com/media@1.0.0/badge.svg"); background-image: url( "https://cdn.example.com/media@1.0.0/badge.svg" cross-origin(anonymous) referrer-policy(no-referrer) ); background-image: url( "https://cdn.example.com/media@1.0.0/badge.svg" cross-origin(anonymous) referrer-policy(no-referrer) integrity("sha384-Iw/0p59dP96nGTzHvf2XIfqcBwBW7RM6JfOij8uZxyg5azfNaOOUW8SQy6H/hU91") ); }- Никогда не указывайте непроверенный
Определите поддержку модификатора
url()в CSS с помощьюCSS.supports()в JavaScript:
export function getCssUrlModifierSupport() {
const supportsCrossOriginAndReferrer =
typeof CSS !== 'undefined' &&
typeof CSS.supports === 'function' &&
CSS.supports(
'background-image',
'url("https://example.com/a.svg" cross-origin(anonymous) referrer-policy(no-referrer))',
);
const supportsUrlIntegrity =
typeof CSS !== 'undefined' &&
typeof CSS.supports === 'function' &&
CSS.supports(
'background-image',
'url("https://example.com/a.svg" cross-origin(anonymous) referrer-policy(no-referrer) integrity("sha384-Iw/0p59dP96nGTzHvf2XIfqcBwBW7RM6JfOij8uZxyg5azfNaOOUW8SQy6H/hU91"))',
);
return { supportsCrossOriginAndReferrer, supportsUrlIntegrity };
}