2 lines
14 KiB
JSON
2 lines
14 KiB
JSON
{"index":3,"slug":"editorial-2027-12-practice-author-manifesto","title":"Security headers: CSP и HSTS без иллюзии защиты","excerpt":"Как CSP и HSTS ограничивают браузер, почему nonce и HTTPS не заменяют исправление XSS и как вводить политики с проверяемым откатом.","contentHtml":"<p>Проблема становится видимой после XSS или downgrade-атаки: приложение отдаёт страницу по HTTPS, но разрешает произвольный inline-скрипт или продолжает открываться по HTTP. Симптомы можно увидеть в DevTools: CSP фиксирует нарушение или отсутствует вовсе, а HTTP-запрос сначала доходит до редиректа. Цена ошибки — выполнение чужого кода в контексте origin, утечка токена и потеря данных. Один security header не закрывает весь риск.</p><p>Ошибка начинается с копирования длинной строки заголовка без карты ресурсов и момента включения политики. CSP управляет тем, откуда страница может загружать и выполнять ресурсы. HSTS говорит браузеру обращаться к домену по HTTPS после получения политики через доверенный HTTPS-ответ. Ни один заголовок не исправляет серверный XSS, плохой сертификат, mixed content или секрет, который уже попал в JavaScript.</p><h2>Тезис: заголовок — это граница, а не доказательство безопасности</h2><p>Политику нужно проектировать от конкретной страницы. Сначала назовите нужные скрипты, стили, изображения, API и фреймы. Затем разрешите только эти источники и отдельно проверьте отрицательные сценарии. Так CSP становится исполняемым контрактом браузера, а не декоративной строкой в конфигурации.</p><p><code>default-src</code> задаёт запасное правило для типов ресурсов, но не заменяет явные директивы. <code>script-src</code> управляет JavaScript. <code>object-src 'none'</code> закрывает загрузку plugin/object, если она не нужна. <code>base-uri 'self'</code> ограничивает изменение базового URL. Такая политика уменьшает поверхность атаки, но не превращает небезопасный sink в безопасный.</p><h2>Механизм CSP</h2><p>Браузер получает Content-Security-Policy вместе с ответом и сопоставляет каждую попытку загрузки или выполнения с директивой. При несовпадении ресурс блокируется в режиме enforce. В режиме Report-Only браузер отправляет нарушение для анализа, но не блокирует действие. Поэтому отчёт помогает собрать карту, но сам по себе не является исправлением.</p><p>Nonce нужен для конкретного inline-скрипта, который пока нельзя вынести в файл. Сервер генерирует непредсказуемое значение заново для каждого ответа, вставляет его в CSP и в атрибут нужного <code>script</code>. Статическая строка не даёт защиты: тот, кто её узнает, получает разрешение. Полный nonce не стоит писать в диагностические логи.</p><table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Inline-скрипт заблокирован</td><td>Нет nonce или разрешение слишком узкое</td><td>Сверить директиву <code>script-src</code> и атрибут <code>nonce</code></td><td>Вынести код в файл или выдать свежий nonce только этому скрипту</td></tr><tr><td>В отчёте много нарушений</td><td>Политика собрана без карты ресурсов</td><td>Сгруппировать отчёты по директиве, URL и типу ресурса</td><td>Удалить лишние источники и обосновать каждый оставшийся</td></tr><tr><td>Сайт всё ещё доступен по HTTP</td><td>Браузер ещё не получил HSTS или первый переход был HTTP</td><td>Проверить заголовок в HTTPS-ответе и цепочку переходов</td><td>Исправить HTTPS и redirect; отдельно оценить требования preload</td></tr><tr><td>Поддомен перестал открываться</td><td><code>includeSubDomains</code> включили до проверки всех имён</td><td>Проверить сертификаты, HTTPS и endpoint каждого поддомена</td><td>Расширять область и <code>max-age</code> только после контролируемой проверки</td></tr><tr><td>После CSP остаётся XSS</td><td>Небезопасный HTML или sink не исправлен</td><td>Проверить контекстное экранирование и места вроде <code>innerHTML</code></td><td>Исправить источник XSS; CSP оставить дополнительным барьером</td></tr></tbody></table><figure><img src=\"/assets/editorial/2027/author-manifesto-2027-editorial-decision-graph.svg\" alt=\"Граф security headers: карта ресурсов формирует CSP, HTTPS-ответ включает HSTS, а неизвестный скрипт или неподготовленный поддомен останавливает расширение политики\" loading=\"lazy\" /><figcaption>Два независимых слоя защиты: CSP управляет ресурсами страницы, HSTS — схемой соединения. Один заголовок не заменяет другой.</figcaption></figure><h2>Минимальный рабочий пример</h2><p>Ниже — учебная функция, которая собирает заголовки из nonce и срока HSTS. Она не устанавливает заголовки в серверном ответе и не проверяет шаблонизатор. Проверяемый результат показывает только форму политики: nonce присутствует в CSP, а HSTS содержит числовой срок и <code>includeSubDomains</code>.</p><pre><code>import { buildSecurityHeaders } from './security-headers.mjs'; const result = buildSecurityHeaders({ nonce: '7c2f1b8e9a4d6f0c', hstsMaxAge: 31536000 }); if (!result.ok) throw new Error(result.reason); response.setHeader('Content-Security-Policy', result.headers['Content-Security-Policy']); response.setHeader('Strict-Transport-Security', result.headers['Strict-Transport-Security']);</code></pre><p>В реальном шаблоне nonce должен попасть только в разрешённый inline-скрипт:</p><pre><code><script nonce=\"7c2f1b8e9a4d6f0c\">startApplication();</script></code></pre><p>Пример не доказывает, что приложение безопасно. Он проверяет сборку значения и показывает границу между генерацией заголовка и его применением. Тесты должны отдельно проверить отсутствие случайного <code>unsafe-inline</code>, соответствие nonce в двух местах и отправку заголовков только по нужному маршруту.</p><h2>Механизм HSTS</h2><p>HSTS начинает действовать после того, как браузер получил <code>Strict-Transport-Security</code> через доверенный HTTPS-ответ. Большой <code>max-age</code> не защищает самый первый HTTP-переход, если домен ещё неизвестен браузеру. Он также не исправляет сертификат или mixed content. Для защиты первого перехода существует preload с отдельными требованиями и риском: сначала нужно проверить готовность домена.</p><p><code>includeSubDomains</code> распространяет правило на поддомены. Включайте его только после проверки всех имён, которые ещё нужны пользователям, API и административным инструментам. Если один поддомен не умеет HTTPS, браузер перестанет подключаться к нему по HTTP на весь срок политики. Возможность отката — часть безопасности, а не последняя строка runbook.</p><h2>Порядок внедрения</h2><ol><li>Соберите карту ресурсов страницы: scripts, inline-код, eval, стили, изображения, CDN, API, iframe и object. Зафиксируйте нужные источники до написания политики.</li><li>Включите CSP в Report-Only. Соберите нарушения по директиве, URL и типу ресурса. Не называйте этот режим блокировкой.</li><li>Удалите лишние источники. Inline-код вынесите в файл или замените на nonce. Оставшиеся исключения объясните рядом с конфигурацией.</li><li>Переведите CSP в enforce на одной проверяемой странице. Сравните ошибки загрузки с разрешённым списком и проверьте отрицательные сценарии.</li><li>Проверьте HTTPS для основного домена и поддоменов. Только после этого добавляйте HSTS, затем осторожно расширяйте <code>max-age</code> и область.</li><li>Добавьте версионируемый откат и автоматическую проверку заголовков. Изменение не должно зависеть от ручной правки одного proxy.</li></ol><h2>Ограничения и критерий готовности</h2><p>Nonce не санитизирует пользовательский HTML и не исправляет небезопасный sink. Если приложение передаёт строку в <code>innerHTML</code>, разрешённый bootstrap может помочь атакующему выполнить уже загруженный код. Нужны контекстное экранирование и безопасные API. CSP снижает последствия и обнаруживает часть нарушений, но не заменяет исправление причины.</p><p>Эта схема не проверяет совместимость целевых браузеров, CDN, service worker, iframe-политику, сертификаты, preload и настройки API-клиента. CSP Level 3 остаётся Working Draft, поэтому директивы и поддержку следует сверять с целевыми браузерами. RFC 6797 описывает HSTS, но не даёт защиты до первого безопасного ответа.</p><p>Готовность наступает, когда для конкретной страницы есть карта ресурсов, Report-Only нарушения разобраны, enforce проверен, HSTS подтверждён на каждом нужном домене, а откат воспроизводится. Дополнительный признак — автоматическая проверка заголовков ловит возврат широкого источника или исчезновение обязательной политики. Без этих проверок строка конфигурации остаётся предположением.</p><h2>Проверяемые источники</h2><ul><li><a href=\"https://www.w3.org/TR/CSP3/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Content Security Policy Level 3</a> — опубликованная редакция спецификации, Working Draft. Описывает директивы источников, nonce и режимы Report-Only/Enforce. Working Draft может изменяться; поддержку браузеров и собственные inline-скрипты нужно проверять отдельно.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc6797.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 6797 — HTTP Strict Transport Security</a> — IETF, Standards Track. Определяет Strict-Transport-Security и поведение браузера после получения политики по HTTPS. Не исправляет mixed content, сертификат, redirect до первого безопасного ответа и настройки API-клиента.</li></ul>"}
|