Files
progcode/editorial/agent-rewrites/003.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

2 lines
14 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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>&lt;script nonce=\"7c2f1b8e9a4d6f0c\"&gt;startApplication();&lt;/script&gt;</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>"}