HTTP-заголовки безпеки, CSP і клікджекінг
Зміст
(HTTP security response headers) — дешевий шар захисту: сервер додає кілька рядків у відповідь, і браузер вмикає механізми проти міжсайтового скриптингу (cross-site scripting, XSS), й розкриття інформації. Впровадження коштує мало, перевірка автоматизується тривіально — тому це типовий вміст релізного чекліста.
Підступність у тому, що перевірка «заголовок присутній» дешева, але тупа. Найдорожчі дефекти виглядають як правильна конфігурація: ALLOW-FROM, який браузер ігнорує цілком; default-src 'none', який не закриває фреймінг; , який нічого не блокує; прибраний , який діє до кінця max-age. Тому нижче не перелік «що поставити», а межі кожного механізму. Механіка самої CSP, CORS, (same-origin policy) і TLS тут не переказується — їхній дім у главах HTTPS, TLS і безпека та CORS і політика одного походження.
Клікджекінг: клік, який іде не туди
Клікджекінг (clickjacking) — , у якій змушує користувача взаємодіяти з цільовим сайтом не так, як той намірявся. Друга офіційна назва — UI redress attack: прозорі чи непрозорі накладки підмінюють те, куди насправді йде клік. класифікує його як клієнтську проблему й підмножину UI redressing; термін увели Джеремая Ґроссман і Роберт Гансен у 2008 році.
Сайт-приманка вбудовує ціль у <iframe>, ховає фрейм стилями й підганяє власні підставні елементи рівно під ті, що виконують чутливі дії. — автентифікована жертва: клік іде на невидиму кнопку цільового сайту, і оскільки користувач залогінений, запит несе його справжні облікові дані й проходить. Канонічний приклад OWASP — «click here for a free iPod» поверх кнопки «delete all messages» із фрейму пошти жертви; тим самим прийомом перехоплюють і натискання клавіш.
Чому це не CSRF: дії йдуть зі справжньої цільової сторінки, тож частину анти-CSRF захистів атака обходить. Показовий сценарій WSTG — звʼязка CSRF плюс клікджекінг, якою обходять анти- і змушують жертву власноруч підтвердити переказ коштів; сама підробка запиту — у главі CSRF і SameSite.
Захистів OWASP називає три незалежні. Перший — заборона фреймінгу заголовками (X-Frame-Options або frame-ancestors). Другий — SameSite на сесійних cookie: значення Strict/Lax не потрапляють у запити зі сторінки всередині чужого <iframe>, але якщо атака автентифікації не потребує, атрибут не дає захисту взагалі. Третій — , клієнтський скрипт у самій сторінці, який перевіряє, що поточний фрейм і є верхнім вікном; його призначення — застарілі браузери, а наївний однорядковий варіант захистом не є: OWASP ставить його в розділ «Insecure Non-Working Scripts DO NOT USE».
Процедура WSTG-CLNT-09 має два кроки: зрозуміти, який захист стоїть, і оцінити, чи він обходиться. Базова техніка — власна сторінка з <iframe> на цільову. Критерій провалу однозначний: сторінка завантажилася у фрейм — захисту немає. Зворотний висновок значно слабший: не завантажилася — захист «якийсь» є, але це ще не імунітет. Три деталі, які ловлять на практиці: клієнтський frame busting відпадає разом із вимкненим JavaScript; мобільна версія часто захищена гірше; веб- відомі тим, що знімають заголовки. Звідси головне правило глави: вердикт знімається з реальної відповіді клієнту, а не з конфігурації сервера.
X-Frame-Options: старий заборонний заголовок
X-Frame-Options каже браузеру, чи можна рендерити документ у <frame>, <iframe>, <embed> чи <object>. Дефолт небезпечний, і саме це робить заголовок предметом перевірки: якщо його не надіслано і сайт не має іншого механізму обмеження, браузер дозволить іншим сайтам вбудовувати цей документ.
| Значення | Що означає |
|---|---|
DENY | Жоден фрейм, незалежно від походження — включно з вбудовуванням самим сайтом |
SAMEORIGIN | Дозволено, лише якщо всі фрейми-предки того самого походження |
ALLOW-FROM uri | Застаріла директива: сучасні браузери ігнорують заголовок повністю |
DENY OWASP і WSTG називають рекомендованим значенням, поки не доведено потребу у фреймінгу. У SAMEORIGIN є нюанс, який пропускають: перевіряється не самий лише безпосередній батько — чужий фрейм десь вище ламає умову. А ALLOW-FROM — найгірший стан: конфігурація виглядає захищеною, а захисту немає, бо браузер без підтримки відкидає весь заголовок.
Далі — межі, і кожна породжує або дефект, або хибну знахідку:
- Метатег не працює:
<meta http-equiv="X-Frame-Options">у розмітці — дефект конфігурації, а не захист. OWASP виносить це в «Common Defense Mistakes» і поширює те саме правило наframe-ancestors. - Політика посторінкова, звідси вимога ставити заголовок на всі відповіді з HTML-вмістом.
- Мультидоменні сайти не обслуговуються: списку доменів задати не можна, браузер шанує рівно один заголовок і одне значення.
- Межа застосовності: на редиректі або JSON-відповіді API заголовок не дає жодної безпеки, і вимагати його там означає плодити хибні знахідки.
Статус заголовка джерела формулюють із різною силою: шпаргалки OWASP кажуть, що frame-ancestors obsoletes X-Frame-Options; MDN називає директиву заміною, але радить ставити старий заголовок на додачу, а позначку «obsolete» на сторінці самого заголовка несе лише ALLOW-FROM. Висновок збігається попри різні слова: ставлять обидва — директиву як чинний механізм, заголовок як «graceful degradation and older browser compatibility».
frame-ancestors: чинний механізм і три пастки
frame-ancestors — директива CSP, яка перелічує дозволених батьків, що можуть вбудувати сторінку через <frame>, <iframe>, <object> або <embed>. Специфікація формулює її через : директива обмежує URL, які можуть вбудувати ресурс, і дозволяє уникнути багатьох атак класу UI redressing. За класифікацією CSP це navigation-директива — клас, що керує не завантаженням ресурсів, а тим, хто кого вбудовує.
frame-ancestors 'none' забороняє всім, і одинарні лапки обовʼязкові — це ключове слово, а не рядок; OWASP називає це значення рекомендованим, поки не виявлено потреби у фреймінгу. Кілька дозволених батьків задають списком джерельних виразів через пробіл — головна перевага над старим заголовком (frame-ancestors 'none' ≈ X-Frame-Options: deny).
Три пастки, на яких ловляться навіть автори політик:
frame-ancestors— неframe-src. Перша каже, хто вбудовує нас, друга — звідки вантажаться фрейми в нас. Політика, що обмежує власні фрейми сторінки, від клікджекінгу не захищає ніяк.- Директива не відкочується на
default-src. Політика зdefault-src 'none'усе одно дозволяє вбудовувати ресурс будь-кому, і специфікація фіксує це окремою нотаткою. Найсуворіший на виглядdefault-srcзахистом від клікджекінгу не є — це типовий хибний висновок у ревʼю політик. - У
<meta>директива не діє: специфікація робить це нормативним — директивуMUSTігнорувати, якщо політику оголошено метатегом.
Ще деталь для складних інтеграцій: директива перевіряє кожного предка, і якщо будь-який не збігся, завантаження скасовується — вкладені фрейми поводяться інакше, ніж перевірка з одним фреймом.
Пріоритет нормативний: за наявності frame-ancestors у режимі «enforce» X-Frame-Options MUST ігноруватися; застереження шпаргалки OWASP важливе для звітів — старі версії браузерів (Chrome 40, Firefox 35) цю вимогу порушували. А аргумент «ставимо тільки X-Frame-Options, бо сумісність» датою підтримки не підтверджується: MDN позначає директиву як well established і доступну в браузерах із січня 2018 року.
Із чого складається політика CSP
Що таке CSP і якими каналами вона доставляється — тема глави HTTPS, TLS і безпека. Тут — склад політики і чому «CSP є» не дорівнює «CSP захищає».
Політика — впорядкований набір директив, кожна з яких керує однією поведінкою; директива є парою «імʼя / значення», у заголовку директиви розділені крапкою з комою. Специфікація ділить їх на чотири класи: fetch (звідки вантажити ресурси певного типу — script-src, font-src), document (властивості документа: base-uri обмежує URL для <base>), navigation (form-action, frame-ancestors) і reporting (куди слати звіти). Читати політику треба разом із запасними значеннями: default-src є запасною для всіх fetch-директив, script-src — для script-src-attr і script-src-elem, child-src — для frame-src і worker-src. Тому відсутність script-src не означає «дозволено все» — і дзеркально, frame-ancestors на default-src не відкочується.
Слабку політику видно просто текстом заголовка. За наявності default-src або script-src inline-JavaScript за замовчуванням не виконується, і заборона поширюється й на inline-обробники подій — 'unsafe-inline' її знімає. 'unsafe-eval' дозволяє eval(), але істотно послаблює політику; 'unsafe-hashes' небезпечне за означенням — воно вмикає атаку, у якій вміст inline-обробника інʼєктується в документ як inline-елемент <script>. А host-джерела приймають підстановку * для піддоменів — звідси класика надто широкого allowlist.
Сучасна альтернатива спискам — nonce і хеші. Nonce — випадкове значення, яке сервер генерує на кожну відповідь; браузер вантажить ресурс, лише коли nonce у директиві збігається з nonce в атрибуті елемента, а впорснутий скрипт не знає, який nonce використає сервер. Три деталі для ревʼю: nonce «гасить» 'unsafe-inline'; мідлвар, що проставляє nonce усім тегам <script>, роздасть його й скриптам атакувальника, тож nonce вставляють шаблонізатором; хеш крихкий — навіть пробіл від автоформатера ламає збіг. 'strict-dynamic' передає довіру від скрипта з nonce до скриптів, які той вантажить динамічно, і ціна подвійна: разом із ним перестають діяти host-джерела, схеми, 'self' і 'unsafe-inline', а якщо довірені скрипти створюють <script> із потенційних джерел XSS, CSP їх уже не прикриє.
Звідси головний вододіл: початковий підхід — списки дозволених джерел, чинна провідна практика — «строгий» CSP на nonce або хешах, бо його простіше впровадити й важче обійти. Allowlist програє тому, що такі політики ненавмисно впускають небезпечні домени й розростаються до некерованих; попередження джерела пряме — не-строга політика, надто дрібна або надто дозвільна, веде до обходів і втрати захисту. Але щось краще за нічого: навіть default-src https: вимикає небезпечний inline та eval().
Три речі, які легко пропустити: не успадковує політику документа (її ставлять заголовком на відповідь зі скриптом воркера); кілька політик не послаблюють одна одну; upgrade-insecure-requests не заміняє HSTS, бо не піднімає зовнішні посилання на сайт. Директива require-trusted-types-for 'script' вимагає передавати в injection sinks (eval(), innerHTML, document.write()) лише довірені типи — механізм розгорнуто в главі XSS: міжсайтовий скриптинг.
І дві межі для чекліста: заголовок ставлять на всі HTTP-відповіді, а не лише на головну сторінку — це типова діра, яку видно саме в тестуванні; але CSP може бути беззмістовним у відповіді REST API, яку ніхто не рендерить, тож «у API немає CSP» — не знахідка.
Report-режим: як політику обкатують
Кожна політика має дію (disposition) — або «enforce», або «report». Заголовок Content-Security-Policy-Report-Only віддає політику, яка не примушується: вона нічого не блокує, але порушення не мовчазні — вони пишуться в і летять на , якщо задано report-to чи report-uri. Сенс режиму MDN формулює одним рядком: побачити, які порушення сталися б за цієї політики. Це «fail open» перед переходом у блокувальний «fail closed». Причому обидва заголовки працюють одночасно — звідси робочий патерн: сувора політика в report-режимі збирає порушення, мʼякша в enforce не ламає сайт.
Для QA це дві протилежні речі. Report-only — готовий інструмент , а не «недовключений захист»: прогін набору тестів проти report-політики показує рівно те, що зламалося б після вмикання. І навпаки: знайдений у проді лише Content-Security-Policy-Report-Only політикою не є.
- Рекомендований механізм звітності — Reporting API: ендпоінти оголошують заголовком
Reporting-Endpoints, ціль для CSP вибирають директивоюreport-to. - Формат: браузер шле звіт як JSON-обʼєкт, методом POST, із
Content-Type: application/reports+json— тобто звітність перевіряється мережею. report-uriзастаріла: специфікація каже вживатиreport-toй ігнорувати стару директиву за наявності нової; практична поправка джерел — оголошують обидві, бо старі браузери відкотяться наreport-uri.'report-sample'додає у звіт перші 40 символів заблокованого коду. Без нього видно, що щось заблоковано, і не видно що.
І межа доставки: заголовок Content-Security-Policy-Report-Only у метаелементі не підтримується — як і директиви report-uri, frame-ancestors і sandbox. Політика в <meta> не звітує й не боронить від фреймінгу.
HSTS: примус до HTTPS і його незворотні наслідки
HSTS (HTTP Strict Transport Security) повідомляє браузеру, що до хоста можна звертатися лише через HTTPS і що майбутні HTTP-спроби треба автоматично підвищувати. Це opt-in: хост оголошує себе HSTS-хостом сам, видаючи заголовок Strict-Transport-Security по захищеному транспорту; політика складається рівно з двох речей — «тільки захищений транспорт» і «як довго це памʼятати». Канонічна специфікація — RFC 6797, опублікований IETF наприкінці 2012 року. Другий ефект не менш важливий за перший: на HSTS-хості браузер не дає користувачеві обійти помилку захищеного зʼєднання, зокрема невалідний сертифікат.
Директив три: max-age — обовʼязкова, скільки секунд від отримання заголовка хост уважається відомим HSTS-хостом; includeSubDomains — необовʼязкова директива без значення, поширює політику на всі піддомени; preload вимагає max-age щонайменше 31536000 (рік) і обовʼязкову присутність includeSubDomains.
Нормативні межі RFC 6797, з яких ростуть майже всі перевірки:
- Заголовок по HTTP заборонено: хост
MUST NOTвкладати його у відповіді незахищеним транспортом, а браузер такий заголовок ігнорує — знайдений у HTTP-відповіді, він нічого не доводить. - Схема переписується до мережевого запиту: браузер
MUSTпри збігу з відомим HSTS-хостом замінити схему наhttps, а явний порт80— на443; політика перекриває навіть ручний ввід в адресному рядку. Ключем зберігання є доменне імʼя, тож IP-адреса HSTS-хостом бути не може. - Будь-яка помилка транспорту рве зʼєднання — «whether "warning" or "fatal" or any other error level»; можливість «клікнути далі» специфікація називає рецептом MITM-атаки.
<meta http-equiv>браузериMUST NOTслухати. - Зникнення заголовка не скасовує політику: хост лишається відомим до кінця
max-age; знімає її лишеmax-age=0, і лише по HTTPS.
Піддомени — окрема пастка. includeSubDomains іде вниз по дереву імен, а не вгору: політика для secure.example.com покриває login.secure.example.com, але не example.com; кожен піддомен має віддавати заголовок сам. Специфікація прямо каже, навіщо директива потрібна: без неї HSTS не захищає доменні cookie з прапорцем Secure — ними можна маніпулювати з піддомену.
Найслабше місце — найперший візит: доки браузер не отримав заголовок по HTTPS, початковий HTTP-запит вразливий (RFC називає це bootstrap MITM vulnerability). Очікувана реакція на HTTP-запит — постійний редирект: хост SHOULD відповісти статусом на кшталт 301 з https-адресою в Location, і в цій редирект-відповіді заголовка HSTS бути не повинно. Проблему першого візиту закриває preload-список: його веде Google й використовують усі браузери, але частиною специфікації він не є, незворотний («PERMANENT CONSEQUENCES»), і сам він домен у список не додає — домен подають окремо. Тому довгий max-age без includeSubDomains OWASP називає небезпечним варіантом, а на час розкочування радить свідомо короткий: ціна помилки асиметрична — неправильний HSTS плюс проблема з сертифікатом, і легітимні користувачі втрачають доступ.
Для стенду з цього три наслідки: самопідписаний сертифікат на HSTS-хості ламає сценарій остаточно, жорстко зашитий http:// у конфізі до сервера не дійде, а регресію «HSTS зник» ловлять на заголовку, бо стара політика ще діє.
Поруч у чеклісті транспорту стоїть (mixed content) — його дім теж у главі HTTPS, TLS і безпека, а специфікація HSTS сама зшиває ці теми поняттям «mixed security context». Поведінка браузера коротка: зображення, відео й аудіо автоматично підвищуються з HTTP на HTTPS, решта типів блокується, а (mixed download) типово блокуються з можливістю вибору. Дві поправки до «зображення підвищуються»: це працює для джерела в src, а не в srcset чи <picture>, і підвищення не станеться, якщо хост в URL — IP-адреса, а не доменне імʼя. І пастка стенду: локальні ресурси вважаються захищеними — file:-URL і петльові адреси на кшталт http://localhost/, тож локально блокувань ви просто не побачите.
Referrer-Policy і Permissions-Policy
Referrer-Policy керує тим, скільки даних про джерело переходу потрапляє в запити. Перше, на чому спотикаються: заголовок запиту — Referer, з однією r (історична одруківка), а заголовок політики — Referrer-Policy. Це не лише про приватність: Referer виносить назовні URL, а в URL може лежати ідентифікатор сесії або capability URL — посилання, яке саме по собі дає доступ (скидання пароля, запрошення).
| Значення | Що їде в Referer |
|---|---|
no-referrer | Нічого, заголовок не надсилається взагалі |
same-origin | Повний URL усередині свого походження, назовні — нічого |
strict-origin | Лише походження; нічого при пониженні HTTPS → HTTP |
strict-origin-when-cross-origin | Повний URL усередині походження, лише походження назовні, нічого при пониженні |
unsafe-url | Усе, включно зі шляхами: «The policy's name doesn't lie; it is unsafe» |
Дефолтну політику треба знати: за MDN це strict-origin-when-cross-origin, і вона ж діє, якщо надане значення невалідне — тобто одруківка не «вимикає» політику, а повертає дефолт. Течуть по незахищеному транспорту origin, origin-when-cross-origin і unsafe-url.
Чому перевірка «заголовок стоїть» не описує поведінку конкретного запиту: політика надходить пʼятьма шляхами — заголовок, <meta name="referrer">, атрибут referrerpolicy на елементі (<a>, <img>, <iframe>, <script> та інших), rel=noreferrer і успадкування. Механіка пріоритету нормативна: невідомі значення ігноруються, а коли політику задає кілька джерел, перемагає останнє — на цьому тримається трюк із запасною політикою (список через кому, бажана останньою), який працює лише в HTTP-заголовку. Для тесту звідси три правила: OWASP радить не покладатися на дефолт браузера, а слати strict-origin-when-cross-origin на всіх відповідях; unsafe-url — знахідка, а не смак; відсутність Referer у трафіку помилки сервера не доводить, бо браузер MAY дозволяти користувачеві прибрати заголовок. І найцінніший тест тут не про заголовок, а про URL: токен скидання пароля в адресі робить витік реальним.
Permissions-Policy дозволяє й забороняє можливості браузера в документі та в будь-яких його <iframe> — керує тим, які походження якими можливостями користуються. Це захід зменшення наслідків: навіть за успішної камеру чи мікрофон увімкнути не вдасться. Заголовок задає allowlist для директив — перелік походжень у дужках через пробіл: порожній список () вимикає можливість і зверху, і у вкладених контекстах (типовий hardening — microphone=(), geolocation=()), self лишає її своєму походженню, а маска піддоменів не покриває сам домен.
Дві поведінки ламають очікування найчастіше. Відсутність директиви не дорівнює забороні: у кожної є власний типовий allowlist — *, self або none. І політика успадковується вниз, але навігація її скидає: щоб можливість працювала у фреймі, його походження має бути й у allowlist батьківської сторінки, а після переходу фрейма на інше походження політика за замовчуванням на нього вже не поширюється. Порушення фіксується в момент спроби скористатися можливістю й збирається через Reporting API, тож перевірка формулюється як сценарій, а не як наявність заголовка. Остання межа: MDN позначає механізм як «not Baseline», бо він не працює в частині найпоширеніших браузерів, — єдиним барʼєром бути не може.
Ще кілька заголовків, у яких є перевірна вага
Перелік свідомо не вичерпний — тут те, що регулярно спливає в чеклістах і має підтверджену вагу.
X-Content-Type-Options: nosniff оголошує, що MIME-типи виставлено свідомо, і забороняє браузеру їх «уточнювати»; так закривається вгадування типу, через яке невиконуваний тип стає виконуваним. Механізмів насправді два: для призначень script і style браузер блокує відповідь при невідповідності типу, а для решти бере заявлений тип як є — і навіть за відсутнього Content-Type відповідь не стане HTML «за замовчуванням». Практичний сценарій: файл, завантажений користувачем, віддано як text/plain, а всередині HTML — із nosniff браузер не витлумачить його як HTML. Єдине значення заголовка — nosniff, і перевіряти його треба в парі з Content-Type. Дзеркально: неправильний Content-Type сам по собі дає XSS — але лише коли вміст призначений для рендерингу клієнтом і він недовірений.
Два заголовки перевіряються «навпаки». X-XSS-Protection більше не вимагають: він міг захистити користувачів старих браузерів без CSP, але в окремих випадках сам створює XSS-вразливості в інакше безпечних сайтах — тож його не ставлять або явно вимикають (X-XSS-Protection: 0). А Access-Control-Allow-Origin — не захисний заголовок узагалі: без нього сайт типово захищений політикою одного походження, а сам заголовок її послаблює.
Cross-Origin-Resource-Policy наказує браузеру блокувати і крос-сайт запити режиму no-cors до ресурсу, відсікаючи відповідь ще до потрапляння в процес атакувальника; політику задає власник ресурсу, а значень рівно три: same-site, same-origin, cross-origin.
Cache-Control потрапляє сюди через класичну пастку: no-cache не забороняє , він лише вимагає ревалідації. Для чутливих даних потрібен no-store, тому no-cache у відповіді з персональними даними — знахідка, а не галочка (механіка директив — у главі Кешування). Server і X-Powered-By — витік версій стека; їх прибирають, але це гігієна, а не захід захисту, а Public-Key-Pins (HPKP) просто мертвий: його прибрали з Chromium у 2018 році. Окремий сценарій — віддача завантажених користувачем файлів: потрібні заголовки, які не дають браузеру виконати вміст, — Content-Disposition: attachment, Content-Type: application/octet-stream і nosniff.
Неправильний CORS як вектор
Механіка CORS і політики одного походження — глава CORS і політика одного походження. Тут — той кут, який робить із конфігурації .
Почати варто з походження як домену захисту: RFC 6454 групує URI в origins — protection domains (схема, хост, порт), і ключова асиметрія там така — надсилати дані в чуже походження дозволено, заборонене саме читання. Звідси й міжсайтова підробка запиту як окремий клас, і CORS як механізм добровільної від заборони на читання.
Access-Control-Allow-Origin нічого не захищає, він послаблює. Причому обмеження накладає клієнт: за специфікацією саме він визначає й застосовує обмеження доступу до даних відповіді. Тобто CORS не є серверним контролем доступу, і будувати авторизацію на ньому не можна — тим паче що Origin не можна змінити з JavaScript, але поза браузером його можна підробити.
Дефекти, які шукає тестувальник:
*уAccess-Control-Allow-Origin— дозволені всі домени; межа прийнятності названа прямо: виправдано лише для публічного API, доступного всім.- Віддзеркалення
Originбез додаткових перевірок. Підтримка кількох походжень реалізується кодом, який має прочитатиOrigin, звірити зі списком дозволених і лише за збігу віддати значення назад; пропущений крок звірки і є вразливістю. Access-Control-Allow-Origin: null— окремий дефект: так серіалізується походження sandbox-документів і схемdata:/file:, багато браузерів дають таким документам доступ до відповіді, а створити ворожий документ із походженнямnullможе будь-хто.- Відсутній
Vary: Originпри віддзеркаленні робить помилку масовою: кеш віддасть чужому походженню відповідь, призначену іншому.
Чому механізм узагалі opt-in, сказано в самій Fetch: інакше течуть дані з-за фаєрвола, а разом з обліковими даними — ще й чутливі; поєднання «ділитися відповідями плюс дозволити облікові дані» специфікація називає доволі небезпечним і вказує клас ризику — confused deputy.
Процедура WSTG-CLNT-07 формулюється як «знайти ендпоінти з CORS і переконатися, що конфігурація безпечна або нешкідлива»: перехопити заголовки проксі-інструментом, подивитися Origin і дозволені домени — плюс ручний огляд JavaScript. Останнє не для галочки: друга тут не заголовки, а код — URL, що потрапляє в XMLHttpRequest без валідації, і неекранована відповідь, яку підставляють у сторінку.
Автоматична перевірка наявності заголовків
Відсутність заголовків — не «недопрацювання», а прояв категорії хибної конфігурації в мапі ризиків OWASP (сама категорія розбирається в главі про security misconfiguration і розкриття даних). Ознака вразливості названа прямо: сервер не надсилає заголовків або директив безпеки, або вони не встановлені в безпечні значення. Пріоритет підпирають два числа: категорія піднялася з пʼятого місця попередньої редакції, і «100% протестованих застосунків» мали ту чи іншу форму хибної конфігурації. Джерело вимагає автоматизованого процесу перевірки конфігурацій в усіх середовищах, а якщо перевірки не автоматизовані — щонайменше ручної щороку.
Швидкий зовнішній зріз — , інструмент Mozilla, який робить поглиблену оцінку HTTP-заголовків сайту та інших ключових налаштувань безпеки. Результат — не «пройдено/не пройдено», а конкретний перелік того, що виправляти. Механіка бала: старт зі 100, далі штрафи й бонуси у два проходи — перший прохід тільки штрафи, а бонуси нараховуються лише тим, хто після штрафів має 90 (A) або більше; діапазон підсумкового бала — від 0 до 145.
Найцінніше тут — те, що інструмент каже про себе сам. Межа обʼєктивності проговорена першим абзацом: важко призначити обʼєктивне значення субʼєктивному питанню на кшталт «наскільки погано не впровадити HSTS», і те, що зайве одному сайту, закриває важливі ризики іншому. Оцінки «designed to alert developers» — це сигнал, а не вирок; межі літерних оцінок «essentially arbitrary», а модифікатори змінюються з часом, тож порівнювати бали різних дат наосліп не можна. Звідси й правило: баг заводиться на конкретний заголовок і ризик, а не на літеру.
Другий канал — читати заголовки просто з відповіді у власному тесті, і тут є пастка. Обʼєкт відповіді Playwright віддає всі заголовки обʼєктом, але обʼєкт не показує повторів: у доці підписано, що в масивній формі імена не приводяться до нижнього регістру, а заголовки з кількома входженнями (як Set-Cookie) зʼявляються в масиві кілька разів.
const response = await request.get('https://example.com/');
// Обʼєктна форма: одне значення на імʼя — повтори не видно
const csp = response.headers()['content-security-policy'];
// Масивна форма: усі входження, імена в оригінальному регістрі
const all = response.headersArray()
.filter((h) => h.name.toLowerCase() === 'content-security-policy');
Ще деталь того самого обʼєкта: TLS-дані лежать в окремому полі, для не-HTTPS воно повертає null, а для редиректу віддає дані останнього запиту ланцюга. Куди такі перевірки ставити в й коли знахідка має валити — предмет глави про автоматизацію security-перевірок; тут важливе інше: заголовок читають із самої відповіді, бо проксі на шляху здатен його додати або зрізати, а сканер оцінює те, що віддає публічна адреса.
Типові помилки
«Заголовок є в конфізі сервера — захист стоїть». Виглядає як доказ, а насправді доводить лише те, що написано в конфізі: веб-проксі відомі тим, що додають і знімають заголовки, і якщо проксі зріже X-Frame-Options, сайт втратить захист від фреймінгу.
«default-src 'none' — суворіше не буває, фреймінг закрито». Виглядає як максимальна суворість, а насправді frame-ancestors на default-src не відкочується: таку сторінку може вбудувати будь-хто.
«Сторінка не вбудувалася в мій <iframe> — усе гаразд». Виглядає як зелений тест, а насправді доводить лише те, що вона не вбудувалася в цьому браузері на цьому URL.
«Поставили X-Frame-Options: ALLOW-FROM … — дозволили партнеру». Виглядає як тонке налаштування, а насправді сучасний браузер ігнорує весь заголовок, і захисту не лишається зовсім.
«В API немає CSP і X-Frame-Options — заводимо баг». Виглядає як прогалина, а насправді на JSON-відповіді, яку ніхто не рендерить, ці заголовки нічого не захищають.
«У відповіді є Content-Security-Policy-Report-Only — CSP присутній». Виглядає як увімкнений захист, а насправді ця політика не примушується за означенням.
«Прибрали заголовок HSTS — політику знято». Виглядає як відкат, а насправді хост лишається відомим до кінця max-age; знімає політику лише max-age=0 і тільки по HTTPS.
«У Permissions-Policy немає директиви camera — камеру вимкнено». Виглядає як , а насправді діє власний типовий allowlist директиви, і він може бути *.
Підсумок
- Дефолт майже скрізь небезпечний. Немає
X-Frame-Optionsіframe-ancestors— вбудовувати сторінку дозволено будь-кому; немає директиви вPermissions-Policy— діє її власний дефолт. Перевіряється не «чи немає помилки», а «чи є явне рішення». - Джерело вердикту — сама відповідь, а не її замінники. Метатег для
X-Frame-Options,frame-ancestors, report-режиму й HSTS не працює взагалі, а сканер оцінює те, що віддає публічна адреса. - «Механізм є» ≠ «механізм захищає».
ALLOW-FROMігнорується цілком,default-src 'none'не закриває фреймінг, не- веде до обходів, report-only не блокує нічого, аAccess-Control-Allow-Originполітику одного походження послаблює, а не підсилює. - Межі механізму — така сама частина перевірки, як його наявність.
X-Frame-Optionsбезпредметний на редиректі й JSON, CSP може бути беззмістовним у відповіді REST API,nosniffне рятує без правильногоContent-Type. - Стан браузера переживає конфігурацію. HSTS діє до кінця
max-ageнавіть після того, як заголовок зник із відповіді, аpreloadнезворотний — тобто поведінка браузера деякий час нічого не скаже про поточний стан заголовка.
Можливі питання
«Що таке клікджекінг і чим він відрізняється від CSRF?» Дивляться, чи розумієте ви, звідки йде дія: у клікджекінгу запит іде зі справжньої цільової сторінки, тож частину анти-CSRF захистів атака обходить. Сильна відповідь називає передумову — автентифікована жертва — і додає, що SameSite тут помʼякшення, а не рішення.
«X-Frame-Options чи frame-ancestors?» Очікують не вибору, а розуміння пріоритету: за наявності директиви в режимі enforce браузер MUST ігнорувати старий заголовок, тож ставлять обидва — директиву як чинний механізм, заголовок як сумісність.
«Політика default-src 'none' — від чого вона не захищає?» Питання-фільтр на читання CSP. Правильна відповідь одна: від вбудовування сторінки в чужий фрейм. Хто цього не знає, зазвичай і frame-src із frame-ancestors плутає.
«Навіщо nonce, якщо є список дозволених доменів?» Слухають, чи бачите ви, чому allowlist програє: такі політики ненавмисно впускають небезпечні домени й розростаються до некерованих, а nonce скрипт із XSS-інʼєкції просто не знає. Деталь, що виказує досвід: nonce має бути новий на кожну відповідь.
«Що робить Content-Security-Policy-Report-Only і коли він шкідливий?» Хочуть дві половини: як інструмент це обкатка політики й готова регресія («що зламалося б»), як стан проду — фальшива галочка. І що обидва заголовки можуть стояти одночасно, і тоді шануються обидві політики.
«Прибрали HSTS — коли це помітить браузер?» Перевіряють, чи розумієте ви, що політика живе в браузері, а не у відповіді: до кінця max-age, тож регресія ловиться на заголовку. Хороша відповідь додає, що зняти політику можна лише max-age=0 по HTTPS.
«Чому Access-Control-Allow-Origin: * — це не захист?» Дивляться, чи не переплутали ви напрям дії: без заголовка сайт уже захищений політикою одного походження, а заголовок її послаблює. Тому перевіряють широту, а не наявність, і окремо шукають віддзеркалення Origin без звірки зі списком та значення null.
Джерела
Клікджекінг: клік, який іде не туди
- MDN — Clickjacking — означення атаки, механіка прихованого фрейму, роль автентифікованої жертви,
SameSiteяк помʼякшення. - OWASP www-community — Clickjacking — назва UI redress attack, перехоплення натискань клавіш, приклад з iPod.
- OWASP WSTG — 4.11.9 Testing for Clickjacking (WSTG-CLNT-09) — класифікація й рік появи терміна, звʼязка з CSRF, процедура тесту, вимкнений JS, мобільна версія, проксі.
- OWASP Cheat Sheet — Clickjacking Defense — три механізми захисту, межа
SameSite, призначення frame busting і заборонений однорядковий варіант.
X-Frame-Options: старий заборонний заголовок
- MDN — X-Frame-Options — призначення заголовка, небезпечний дефолт,
DENY/SAMEORIGIN, застарілеALLOW-FROM, недієвість метатега. - OWASP Cheat Sheet — Clickjacking Defense —
DENYяк рекомендація, посторінковість, відсутність списку доменів, одне значення, «Common Defense Mistakes». - OWASP — HTTP Security Response Headers Cheat Sheet — витіснення директивою
frame-ancestors, відсутність сенсу на редиректах і JSON-відповідях API. - OWASP — Content Security Policy Cheat Sheet — формулювання «obsoleted by the frame-ancestors CSP directive».
- MDN — Clickjacking — директива як заміна заголовка й порада ставити обидва.
- OWASP www-community — Clickjacking — старий заголовок як засіб сумісності зі старими браузерами.
- OWASP WSTG — 4.11.9 Testing for Clickjacking (WSTG-CLNT-09) —
DENYяк очікуване значення.
frame-ancestors: чинний механізм і три пастки
- MDN — CSP: frame-ancestors — призначення директиви, обовʼязкові лапки, список джерел, відсутність відкату на
default-src, недієвість у<meta>, перевірка кожного предка, дата підтримки. - W3C — Content Security Policy Level 3 — обмеження вбудовування як захід проти UI redressing, нормативна нотатка про
default-src, вимога ігнорувати директиву в метатезі. - MDN — Content-Security-Policy (заголовок відповіді) — клас navigation-директив.
- OWASP Cheat Sheet — Clickjacking Defense —
'none'як рекомендоване значення, правила лапок, цитата специфікації про пріоритет і застереження про старі браузери. - MDN — Clickjacking — браузер із підтримкою директиви ігнорує старий заголовок.
- MDN — Content Security Policy (CSP) — директива як гнучкіша заміна
X-Frame-Optionsпроти клікджекінгу.
Із чого складається політика CSP
- W3C — Content Security Policy Level 3 — політика як набір директив, директива як пара «імʼя/значення», чотири класи,
base-uriіform-action, відсутність відкатуframe-ancestors. - MDN — Content-Security-Policy (заголовок відповіді) — синтаксис, ланцюг запасних значень, host-джерела,
'unsafe-inline'і'unsafe-hashes', nonce і його пріоритет надunsafe-inline,'strict-dynamic'та його ціна, воркери й накопичення політик. - MDN — Content Security Policy (CSP) — чому nonce непередбачуваний, вставка шаблонізатором, ризик
'strict-dynamic'для XSS-джерел,require-trusted-types-for,upgrade-insecure-requestsне заміняє HSTS. - MDN — Practical implementation guides: CSP —
'unsafe-eval', вибір nonce проти хешів, строга політика проти location-based,default-src https:як мінімум. - OWASP — Content Security Policy Cheat Sheet — заборона inline-обробників, самообман із nonce у мідлварі, крихкість хешів, allowlist проти строгого CSP, попередження про обходи, заголовок на всіх відповідях.
- OWASP — HTTP Security Response Headers Cheat Sheet — беззмістовність CSP у відповіді REST API.
Report-режим: як політику обкатують
- W3C — Content Security Policy Level 3 — дія політики «enforce»/«report», призначення report-only, застарілість
report-uri, недоступність режиму й трьох директив у<meta>. - MDN — Content Security Policy (CSP) — одночасна дія обох заголовків,
Reporting-Endpointsіreport-to, формат звітуapplication/reports+json. - MDN — Content-Security-Policy (заголовок відповіді) — сумісне оголошення
report-toіreport-uri,'report-sample'і 40 символів. - MDN — Practical implementation guides: CSP — обкатка перед бойовим увімкненням, «які порушення сталися б».
- OWASP — Content Security Policy Cheat Sheet — «fail open» перед «fail closed», патерн «сувора в report плюс мʼяка в enforce», обмеження доставки метатегом.
HSTS: примус до HTTPS і його незворотні наслідки
- RFC 6797 — HTTP Strict Transport Security (HSTS) — оголошення HSTS-хоста, дві складові політики,
max-ageіincludeSubDomains, заборона заголовка по HTTP, переписування схеми й порту, розрив зʼєднання при будь-якій помилці, заборона метатега, збереження політики йmax-age=0, доменне імʼя як ключ,Secure-cookie, bootstrap MITM, редирект 301, «mixed security context». - MDN — Strict-Transport-Security — призначення заголовка, заборона обходу помилки сертифіката, вимоги
preload, напрям діїincludeSubDomains, окремий заголовок на кожному піддомені, відсутність HSTS у редирект-відповіді, статус preload-списку, IP-адреса як хост. - OWASP — HTTP Strict Transport Security Cheat Sheet — opt-in і RFC 6797, ризик cookie з піддомену, незворотність preload і потреба подати домен, короткий
max-ageна розкочуванні. - OWASP — HTTP Security Response Headers Cheat Sheet — асиметрична ціна помилки конфігурації.
- MDN — Content Security Policy (CSP) —
upgrade-insecure-requestsне піднімає зовнішні посилання, тож HSTS не заміняє. - MDN — Mixed content — підвищення медіа й блокування решти, межі підвищення зображень (
srcпротиsrcset, IP замість домену), змішане завантаження, локальні адреси як захищені походження.
Referrer-Policy і Permissions-Policy
- MDN — Referrer-Policy — призначення заголовка, пастка написання
Referer, значення політики, чинний дефолт і поведінка при невалідному значенні, елементи з атрибутомreferrerpolicy, запасна політика списком. - W3C — Referrer Policy (Candidate Recommendation, 26 January 2017) — ризик витоку session id і capability URL, небезпечні набори значень, пʼять шляхів надходження політики, ігнорування невідомих значень і перемога останнього, право користувача прибрати заголовок.
- MDN — Permissions-Policy — синтаксис allowlist,
()іself, маска піддоменів і апекс, типовий allowlist кожної директиви, успадкування й скидання при навігації, момент порушення й Reporting API, статус «not Baseline». - OWASP — HTTP Security Response Headers Cheat Sheet — рекомендація слати
strict-origin-when-cross-originна всіх відповідях; керування можливостями за походженнями як зменшення наслідків інʼєкції.
Ще кілька заголовків, у яких є перевірна вага
- OWASP — HTTP Security Response Headers Cheat Sheet — заголовки як дешевий шар захисту, MIME Confusion,
X-XSS-Protectionі рекомендація його вимкнути,Access-Control-Allow-Originяк послаблення, проти класу Spectre, пасткаno-cache,Server/X-Powered-By, мертвий HPKP, трійка для віддачі файлів, неправильнийContent-Typeяк XSS. - MDN — X-Content-Type-Options — два механізми
nosniff, поведінка при відсутньомуContent-Type, сценарій із завантаженим файлом, єдине значення заголовка, вимога перевіряти в парі зContent-Type. - MDN — Cross-Origin-Resource-Policy (CORP) — блокування
no-corsзапитів, політика на боці власника ресурсу, три значення.
- RFC 6454 — The Web Origin Concept — походження як protection domain і трійка схема/хост/порт, дозвіл на надсилання й заборона на читання, CORS як добровільна відмова від заборони.
- OWASP — HTTP Security Response Headers Cheat Sheet — заголовок як послаблення політики одного походження й вимога перелічувати конкретні походження замість
*. - OWASP WSTG — 4.11.7 Testing Cross Origin Resource Sharing — обмеження застосовує клієнт, підробний
Originпоза браузером,*і віддзеркалення як два дефекти, процедура перевірки, інʼєкція черезXMLHttpRequest. - MDN — Access-Control-Allow-Origin — реалізація списку дозволених походжень на сервері, дефект
nullі вимогаVary: Origin. - WHATWG Fetch Standard — чому механізм opt-in, витік інтранет-даних і клас ризику confused deputy.
- MDN — Cross-Origin Resource Sharing (CORS) —
Access-Control-Allow-Originяк назва одного дозволеного походження або*.
Автоматична перевірка наявності заголовків
- MDN — HTTP Observatory — призначення інструмента, глибина оцінки й характер виводу.
- MDN HTTP Observatory — Tests and scoring — старт зі 100 і два проходи, поріг 90 для бонусів, діапазон 0–145, межа обʼєктивності, призначення оцінки як сигналу, умовність літерних меж і зміна модифікаторів у часі.
- OWASP — HTTP Security Response Headers Cheat Sheet — Observatory як онлайн-інструмент перевірки стану заголовків.
- OWASP Top 10:2025 — A02 Security Misconfiguration — відсутні або небезпечно виставлені заголовки як ознака категорії, підйом із пʼятого місця, «100% протестованих застосунків», вимога автоматизованої перевірки конфігурацій.
- Playwright — APIResponse class — обʼєкт заголовків проти масивної форми з повторами й регістром, окреме поле TLS-даних та його поведінка на не-HTTPS і на редиректі.
Що таке клікджекінг і завдяки чому він спрацьовує?
(clickjacking), він же UI redress attack, — це підміна цілі кліка: людина натискає те, що бачить, а натискання дістається зовсім іншій сторінці. Схема стандартна: сторінка-приманка вантажить цільовий сайт у <iframe>, ховає той фрейм стилями — прозорість, зсув, нульовий розмір — і розкладає власні привабливі елементи рівно над чутливими кнопками цілі. нічого не ламає на сервері, тому OWASP і відносить її до клієнтських: вона краде намір користувача, а не обходить перевірку. Спрацьовує вона за однієї умови — жертва в цю мить залогінена на цільовому сайті, бо саме її браузер підставить у запит справжню сесію, і для сервера дія виглядатиме легітимною. Назву запропонували 2008 року дослідники Джеремая Ґроссман і Роберт Гансен, а хрестоматійний приклад OWASP — принада на кшталт розіграшу пристрою, натиснута поверх невидимої кнопки видалення всіх листів у пошті жертви. Тим самим прийомом перехоплюють і введення з клавіатури.
Чим клікджекінг відрізняється від CSRF?
Джерелом дії. При міжсайтовій підробці запиту (cross-site request forgery, CSRF) запит формує чужа сторінка, і ловлять його на тому, що в ньому немає непередбачуваного секрету — анти-. При клікджекінгу запит народжується на самій цільовій сторінці, у її власному контексті: токен там правильний, правильне, натиснула справжня людина. Тому частина анти-CSRF заходів атаку просто не бачить, і описує звʼязку, у якій клікджекінгом обходять токен, змушуючи жертву власноруч підтвердити переказ коштів. SameSite тут допомагає, але не рятує: Strict і Lax не потрапляють у запити зі сторінки, відкритої всередині чужого <iframe>, проте якщо дія взагалі не потребує автентифікації, від атрибута користі нуль. Третій захист, , — клієнтський скрипт, і його призначення суто сумісність зі старими браузерами; наївний однорядковий варіант OWASP тримає в розділі «не використовувати». Практичний висновок: закривають фреймінг заголовком, а не токеном.
Що робить X-Frame-Options і які значення в нього бувають?
Заголовок каже браузеру, чи можна рендерити цей документ усередині <frame>, <iframe>, <embed> або <object>. Робочих значень два: DENY забороняє вбудовування взагалі, включно з власним сайтом, а SAMEORIGIN дозволяє його лише тоді, коли всі фрейми-предки того самого походження — не тільки безпосередній батько, тож чужий фрейм на будь-якому рівні вище ламає умову. Третє значення, ALLOW-FROM uri, застаріле, і поводиться воно найгірше з можливого: браузер не звужує дозвіл до вказаного партнера, а відкидає заголовок цілком — конфігурація виглядає тонко налаштованою, а вбудувати сторінку може будь-хто. Перевіряти заголовок треба ще й тому, що дефолт небезпечний: якщо його не надіслали й іншого механізму немає, вбудовування дозволене. І дві межі, щоб не плодити хибних знахідок: політика діє посторінково, тобто заголовок потрібен на всіх відповідях із HTML, а на редиректі чи JSON-відповіді API він не захищає нічого.
Що ставити сьогодні — X-Frame-Options чи frame-ancestors?
Обидва, і питання насправді про пріоритет. frame-ancestors — , яка витіснила старий заголовок: якщо політика в режимі enforce її містить, браузер зобовʼязаний старий заголовок проігнорувати, і долю фрейму вирішує саме директива. Тому суперечність між значеннями небезпечна лише на вигляд — фактично працюватиме CSP. Старий заголовок лишають запасним варіантом для клієнтів, які директиви не розуміють: MDN прямо радить надсилати обидва, OWASP формулює жорсткіше — директива робить заголовок застарілим, — але висновок в обох однаковий. Аргумент «нам вистачить X-Frame-Options, бо CSP підтримують не всюди» датами не підтверджується: директива доступна в основних браузерах із січня 2018 року. У звіті варто памʼятати ще одну деталь: дуже старі версії (Chrome 40, Firefox 35) вимогу про ігнорування порушували, тож пара «директива плюс заголовок» — не забобон.
Чим frame-ancestors відрізняється від frame-src?
Напрямом. frame-ancestors відповідає на питання «хто має право вбудувати нас», frame-src — «які фрейми ми маємо право завантажити в себе». Це різні : перша директива про клікджекінг, друга — про контроль вмісту власної сторінки. Політика з ретельно виписаним frame-src від вбудовування в чужий сайт не захищає ніяк, і на рев'ю цю пару плутають регулярно. Технічно різниця теж є: frame-ancestors належить до navigation-директив і звіряє кожного предка в ланцюжку — досить одному не збігтися зі списком, і завантаження скасовується, тому проба з одним фреймом і проба з вкладеними можуть дати різний результат. І практична перевага над старим заголовком: дозволених батьків можна перелічити списком через пробіл, а frame-ancestors 'none' дає той самий ефект, що X-Frame-Options: DENY.
Чому політика default-src 'none' не захищає від клікджекінгу?
Бо frame-ancestors не входить у ланцюг запасних значень default-src — специфікація фіксує це окремою нотаткою. Політика, суворіша за яку, здається, не буває, при цьому не забороняє нікому вбудувати сторінку у свій <iframe>. Механіка проста: default-src підстраховує лише fetch-директиви, тобто керує завантаженням ресурсів, а frame-ancestors — navigation-директива, яка керує вбудовуванням, і запасного значення в неї немає взагалі. На співбесіді це питання-фільтр: хто читає CSP уважно, той шукає окрему директиву, а не заспокоюється на 'none' у дефолті. Наслідок для тесту прямий: немає frame-ancestors і не надіслано X-Frame-Options — сторінка вбудовується, і перевіряється це за хвилину.
Як перевірити захист від фреймінгу і чому висновок «не вбудувалося» слабший, ніж здається?
Процедура WSTG-CLNT-09 має два кроки: зʼясувати, який механізм узагалі стоїть, і оцінити, чи він обходиться. Технічно це власна сторінка з <iframe> на цільову адресу, відкрита в браузері. Критерій провалу однозначний: сторінка відрендерилася у фреймі — захисту немає, можна фіксувати дефект. Зворотний висновок значно слабший: не відрендерилася — отже, щось спрацювало саме в цьому браузері, на цьому URL і за цих умов. Далі йдуть речі, які й ловлять на практиці: клієнтський frame busting відмирає разом із вимкненим JavaScript, мобільна версія сайту часто захищена гірше за десктопну, а веб- відомі тим, що заголовки і додають, і знімають. Звідси головне правило: перевіряють відповідь, яку реально отримав клієнт на потрібному шляху доставки, а не рядок у конфізі сервера.
З чого складається політика CSP і що таке ланцюг запасних директив?
Політика — це впорядкований набір директив, розділених у заголовку крапкою з комою, де кожна директива є парою «імʼя — значення» й керує однією поведінкою. Класів у специфікації чотири: fetch керує завантаженням ресурсів певного типу (script-src, font-src), document — властивостями самого документа (наприклад, base-uri), navigation відповідає за form-action і frame-ancestors, reporting — за адресу звітів. Читати політику без запасних значень не можна: default-src підстраховує всі fetch-директиви, script-src — свої -elem і -attr варіанти, child-src — frame-src та worker-src. Тобто відсутній script-src ще не означає «скрипти звідусіль»: якщо в політиці є default-src, працює він; а дзеркальний бік того самого правила — frame-ancestors не відкочується ні на що. Дві дрібниці, які видно тільки в тестуванні: політику документа не успадковує, їй потрібен власний заголовок на відповіді зі скриптом воркера, а кілька політик в одній відповіді одна одну не послаблюють — виконуються всі.
Чому список дозволених доменів вважають слабшим за nonce і хеші?
Історично CSP будували саме списками джерел, але провідною практикою зараз є «строгий» CSP на nonce або хешах: його простіше впровадити й значно важче обійти. Проблема allowlist у тому, що він розростається до некерованого й ненавмисно впускає небезпечні домени — досить одного хостингу, з якого можна віддати довільний скрипт, і політика тихо перестає бути перепоною, лишаючись на вигляд детальною. Джерела формулюють це прямо: політика, яка не є строгою — надміру подрібнена або надміру дозвільна, — зрештою обходиться, і захист втрачається. Nonce знімає проблему інакше: довіра привʼязана не до домену, а до значення, якого впорснутий скрипт не знає. При цьому навіть слабка політика краща за її відсутність: default-src https: уже прибирає inline-скрипти та eval(), а це найдешевші вектори. І окремо варто памʼятати про * у host-джерелах: маска піддоменів — класичний спосіб зробити список ширшим, ніж автор задумував.
Як працює nonce у CSP і які помилки його знецінюють?
Сервер генерує випадкове значення на кожну відповідь, кладе його в директиву й у атрибут кожного дозволеного тега <script>; браузер виконує лише ті скрипти, де значення збіглося. Захист тримається саме на непередбачуваності: код, що потрапив через XSS-, потрібного значення не знає, бо воно змінюється щовідповіді. Три речі ламають схему. Перша — мідлвар, який проставляє nonce усім тегам <script> підряд: він так само щедро видасть його й тегу , тому значення підставляє шаблонізатор при рендерингу. Друга — «випадкове» значення, згенероване один раз і зашите в конфіг. Третя стосується альтернативи: хеш прив'язаний до байтів скрипта, тож зайвий пробіл від автоформатера ламає збіг, і скрипт мовчки перестає виконуватися. Корисний побічний ефект теж треба знати: наявність nonce нейтралізує 'unsafe-inline' у тій самій директиві.
Що робить 'strict-dynamic' і чим за нього платять?
Ключове слово передає довіру: скрипт, дозволений через nonce або хеш, може динамічно створювати інші скрипти, і ті виконаються, хоча їхніх адрес у політиці немає. Це і є спосіб жити зі складними бандлами й завантажувачами без величезного allowlist. Ціна подвійна. По-перше, разом зі 'strict-dynamic' перестають діяти host-джерела, схеми, 'self' і 'unsafe-inline' — політика фактично зводиться до nonce і хешів, і частина звичного вмісту може відпасти. По-друге, довіра передається наосліп: якщо довірений скрипт створює теги <script> із даних, які контролює зловмисник, CSP уже не втрутиться. Для тестувальника це означає конкретний обсяг після вмикання: віджети, аналітика, ліниво завантажувані модулі, усе, що підвантажує код у рантаймі.
Навіщо потрібен режим report-only в CSP і коли він стає фальшивою галочкою?
У кожної політики є дія: enforce або report. Другий заголовок доставляє політику, яка нічого не блокує, але про кожне порушення пише в і, якщо задано ціль звітності, надсилає звіт — POST-ом, JSON-обʼєктом із типом application/reports+json, тобто саму звітність можна перевірити мережею. Сенс режиму — побачити наперед, що зламалося б після вмикання: «fail open» перед переходом у «fail closed». Для QA це готовий регресійний інструмент: прогін набору тестів проти report-політики дає точний список того, що доведеться лагодити ще до бойового вмикання. Зворотний бік — так само точна помилка в звіті: якщо на проді знайдено лише report-only, захисту там немає, хай яка гарна сама політика. І деталь, яка виказує досвід: обидва заголовки можуть стояти одночасно, тож типовий робочий стан — сувора політика в режимі звітності поверх мʼякшої, яка вже примушується.
Що не працює, якщо політику доставити метатегом?
Метатег — легальний канал доставки CSP, але неповний, і саме тут народжуються фальшиві галочки. frame-ancestors у метатезі браузер зобовʼязаний ігнорувати — специфікація робить це нормативним; так само не працюють report-uri і sandbox, а режим report-only метатегом не доставляється взагалі. Тобто політика в розмітці не боронить від фреймінгу й не звітує про порушення. Те саме правило поширюється на сусідні заголовки: <meta http-equiv="X-Frame-Options"> OWASP відносить до типових помилок захисту, а у метатезі браузер слухати не повинен за специфікацією. Практичний висновок один: усе, що стосується фреймінгу, звітності й транспорту, живе виключно у HTTP-заголовках відповіді — і перевіряти це треба саме там.
Що таке HSTS і чому прибраний заголовок не знімає політику?
HSTS (HTTP Strict Transport Security, RFC 6797) — оголошення хоста про те, що звертатися до нього можна лише через HTTPS. Механізм opt-in: сервер видає Strict-Transport-Security по захищеному зʼєднанню, а браузер запамʼятовує хост на вказаний у max-age час; уся політика зводиться до двох тверджень — ходити лише захищеним транспортом і скільки часу це памʼятати. Далі вона працює на боці клієнта: схема переписується на https ще до виходу в мережу, явний порт 80 замінюється на 443, і навіть ручний ввід http:// в адресному рядку не допоможе. Другий ефект не менш важливий: на такому хості користувач не може «клікнути далі» через помилку сертифіката — будь-яка проблема транспорту рве зʼєднання, бо можливість продовжити специфікація називає готовим рецептом MITM. Звідси й відповідь про відкат: заголовок зник із відповідей, а браузери, які його вже бачили, вважають хост HSTS-хостом до кінця max-age; скасувати політику можна лише надіславши max-age=0, і теж по HTTPS. Для регресії висновок практичний: зникнення HSTS ловлять на заголовок, бо поведінка браузера ще довго виглядатиме правильною.
Куди діє includeSubDomains і навіщо ця директива потрібна?
Униз по дереву імен і тільки вниз: політика, оголошена для secure.example.com, накриє login.secure.example.com, але до example.com не підніметься. Без неї кожен домен, який має бути захищений, зобовʼязаний віддавати заголовок самостійно. Потрібна вона не для повноти охоплення заради краси, а через кукі: специфікація прямо каже, що без цієї директиви HSTS не захищає доменні кукі з прапорцем Secure, бо ними можна маніпулювати з незахищеного піддомену. Друга причина — preload: щоб потрапити до списку попереднього завантаження, потрібні max-age щонайменше на рік і обовʼязкова присутність includeSubDomains. Тут же й головне застереження: preload незворотний, домен у список подають окремо, а комбінація «довгий max-age плюс проблема з сертифікатом на якомусь піддомені» коштує доступу для реальних користувачів — саме тому на розкочуванні свідомо беруть короткий термін.
Що таке Referrer-Policy і чому наявність заголовка не описує поведінку конкретного запиту?
Заголовок керує тим, скільки даних про сторінку-джерело поїде в наступний запит. Перша пастка орфографічна: у запиті їде Referer з однією «r» — історична одруківка, — а політику задає Referrer-Policy з двома. Друга: це не лише приватність, бо в URL можуть лежати ідентифікатор сесії або capability URL, тобто посилання, саме володіння яким відкриває доступ, — лист про скидання пароля чи запрошення в команду. А поведінку конкретного запиту заголовок не визначає, бо політика надходить пʼятьма шляхами: HTTP-заголовок, <meta name="referrer">, атрибут referrerpolicy на конкретному елементі, rel="noreferrer" у посиланні та успадкування. Невідомі значення ігноруються, а серед відомих перемагає останнє — на цьому й тримається трюк із запасною політикою, записаною списком через кому, який працює лише в HTTP-заголовку. Дефолт браузерів — strict-origin-when-cross-origin, і він же вмикається при невалідному значенні: одрук не «вимикає політику», а повертає стандартну. У звіті unsafe-url — знахідка, а не смак команди; але найцінніша перевірка стосується не заголовка, а самої адреси: якщо в ній лежить токен скидання пароля, витік перестає бути теоретичним.
Чому відсутність директиви в Permissions-Policy не означає заборону?
Бо в кожної можливості є власний типовий allowlist — *, self або none, — і саме він діє, поки директиви немає. Тому «у політиці немає camera, отже камеру вимкнено» — хибний висновок: вимикає її явний порожній список, camera=(), який відрізає можливість і на самій сторінці, і у вкладених контекстах. Синтаксис тут свій: у дужках через пробіл перелічують походження, self лишає можливість своєму походженню, а маска піддоменів сам домен не покриває. Друга поведінка, що ламає очікування, — успадкування: політика йде вниз у <iframe>, тож аби можливість була доступна у фреймі, батьківська сторінка мусить мати його походження у своєму allowlist; а щойно фрейм перейде на інше походження, типово він з-під політики випадає. Перевіряти це наявністю заголовка марно: порушення фіксується тоді, коли код намагається скористатися можливістю, і потрапляє у звіти Reporting API, тому перевірка формулюється як сценарій. І ще одне обмеження: у MDN механізм має статус «not Baseline», бо частина поширених браузерів його не підтримує, — єдиним барʼєром він бути не може.
Що дає X-Content-Type-Options: nosniff і чому його перевіряють у парі з Content-Type?
Заголовок оголошує, що типи вмісту виставлені свідомо, і забороняє браузеру «уточнювати» їх за самим вмістом. Усередині два різні механізми. Коли призначення — script або style, невідповідний тип змушує браузер відхилити відповідь; для решти призначень він просто довіряє заявленому типу — причому коли заголовка типу немає взагалі, HTML браузер відповіді не припише. Класичний сценарій — файл, завантажений користувачем: усередині HTML, віддається як text/plain, і з nosniff браузер не витлумачить його як сторінку. Значення в заголовка одне, тож перевірка «є / немає» тривіальна — і саме тому сама по собі малоцінна: якщо Content-Type виставлено неправильно, nosniff слухняно закріпить помилку. Тому асерт роблять на пару, а для віддачі користувацьких файлів додають ще Content-Disposition: attachment і Content-Type: application/octet-stream. Дзеркальне уточнення для звітів: неправильний Content-Type перетворюється на XSS лише тоді, коли вміст призначений для рендерингу клієнтом і при цьому недовірений.
Чому Access-Control-Allow-Origin — не захисний заголовок і що в CORS шукає тестувальник?
Тому що напрям дії протилежний: без цього заголовка сторінка вже захищена , а заголовок цю заборону послаблює, добровільно відкриваючи читання відповіді чужому походженню. Корисно памʼятати й де стоїть контроль: обмеження застосовує клієнт, тому будувати авторизацію на CORS не можна — поза браузером Origin підробляється тривіально. Звідси перелік знахідок. Значення * означає «читати можуть усі» й виправдане лише для справді публічного API. Віддзеркалення Origin без звірки зі списком дозволених — та сама «підтримка кількох доменів», у якій забули крок перевірки. Значення null — окремий дефект: так серіалізуються sandbox-документи й схеми на кшталт data: та file:; чимало браузерів пускають такі документи до тіла відповіді, а зібрати документ із подібним походженням здатен будь-хто. І відсутній Vary: Origin при віддзеркаленні, через який кеш віддасть чужому походженню відповідь, підготовлену для іншого. WSTG додає до перевірки заголовків ще й ручний огляд JavaScript: адреса, що потрапляє в запит без валідації, і неекранована відповідь, вставлена в сторінку, — це друга .
Як автоматизувати перевірку заголовків і де межа такої автоматизації?
Відсутні або небезпечно виставлені заголовки — це прояв категорії хибної конфігурації, яка в редакції 2025 року піднялася з пʼятого місця на друге; там же вимога мати автоматизований процес перевірки конфігурацій в усіх середовищах, а не лише в проді, і щорічну ручну перевірку як мінімум, якщо автоматизації немає. Швидкий зовнішній зріз дає від Mozilla: бал стартує зі 100, штрафи й бонуси нараховуються у два проходи (бонуси — лише тим, хто після штрафів утримав 90 і вище), підсумок лежить у діапазоні від 0 до 145. Читати цей бал треба як сигнал: інструмент сам попереджає, що межі літерних оцінок умовні, модифікатори змінюються з часом, а «наскільки погано не мати HSTS» — питання субʼєктивне, бо зайве одному сайту закриває важливі іншому. Тому тікет заводять під конкретний механізм і його ризик, а не під літеру оцінки. Другий канал — власні тести, які читають заголовки прямо з відповіді; у Playwright тут є пастка: обʼєктна форма тримає одне значення на імʼя й повторів не показує, тож для заголовків із кількома входженнями беруть масивну форму. І головна межа обох підходів: сканер перевіряє публічну адресу, тоді як перед вашим стендом заголовок міг зникнути на проксі — вердикт знімають із відповіді, яку реально отримав клієнт.
Три кейси, у яких вердикт про заголовки не знімається з конфіга: розбір реальної шапки відповіді (де тут захист, а де його імітація), сторінка-пастка з <iframe> плюс таблиця вердиктів, і Playwright-перевірки, які ловлять різні провали — від фальшивої галочки report-only до зниклого . Скрізь — що дивитися і чому.
Кейс 1. Шапка відповіді: де тут захист, а де його імітація
Прийшов тікет «перевірити заголовки безпеки перед релізом». Заголовки читають у вкладці Network (Response Headers) або будь-яким HTTP-клієнтом — важливо лише, щоб запит ішов тим самим шляхом, що й у користувача: через той самий , той самий CDN, ту саму адресу. Ось шапка сторінки налаштувань акаунта:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Security-Policy-Report-Only: default-src 'none'; script-src 'self' *.cdn.example; report-uri /csp-report
X-Frame-Options: ALLOW-FROM https://partner.example
Strict-Transport-Security: max-age=300
Referrer-Policy: unsafe-url
X-XSS-Protection: 1; mode=block
Server: nginx/1.24.0
Що дивитися і чому:
- Захисту від фреймінгу немає взагалі — і це два незалежні промахи, а не один.
ALLOW-FROMбраузер відкидає разом з усім заголовком, тобто «дозвіл партнеру» насправді означає «дозвіл усім». А політика CSP, навіть якби вона примушувалася, фреймінг не закриває:default-src 'none'виглядає максимально суворим, алеframe-ancestorsна нього не відкочується. Це найважча знахідка в шапці, і вона перевіряється за хвилину — кейсом 2. Content-Security-Policy-Report-Onlyза CSP не зараховуємо. Політика в цьому режимі не примушується за означенням: порушення летять у і на , блокувань немає. Як інструмент вона корисна (саме так політику й обкатують), як стан проду — фальшива галочка. Плюс два зауваження до самої політики:report-uriзастаріла, поруч має бутиreport-toз ендпоінтом, оголошеним уReporting-Endpoints; а*.cdn.example— типова маска, через яку в allowlist потрапляє більше, ніж планували.max-age=300— це стан розкочування, а не робоча політика. Пʼять хвилин свідомо беруть на початку впровадження, щоб ціна помилки була низькою, і це правильно. Питання до команди інше: чи це справді розкочування, чи так лишили назавжди. Тут же перевіряютьincludeSubDomains— без неї доменні кукі зSecureHSTS не захищає.Referrer-Policy: unsafe-url— знахідка, а не смак. Повні URL зі шляхами поїдуть на чужі домени. Якщо в застосунку є посилання зі скидання пароля або запрошення, витікає не «аналітика про переходи», а сам доступ. Очікуване значення на всіх відповідях —strict-origin-when-cross-origin.X-XSS-Protection: 1; mode=blockперевіряється навпаки. Сьогодні його або немає, або він явно вимкнений значенням0: користувачам старих браузерів без CSP він колись допомагав, але в окремих випадках сам створює XSS там, де її не було.Server: nginx/1.24.0— гігієна, а не захист. Витік версії стека варто прибрати, але це низький пріоритет; плутати його з відсутнім захистом від фреймінгу в одному тікеті не варто — пріоритети зіллються.- Чого в шапці не видно, теж частина розбору. Немає
X-Content-Type-Options: nosniff, а сторінка віддає HTML і, найімовірніше, десь віддає користувацькі файли. Але сам по собі він мало що дає — дивитися треба на пару зContent-Type: неправильний типnosniffне виправляє, а закріплює.
Кейс 2. Сторінка-пастка: перевірка фреймінгу за процедурою WSTG
Вердикт із кейса 1 треба підтвердити поведінкою браузера. Мінімальна проба — власний HTML-файл, відкритий локально:
<!-- frame-probe.html -->
<style>iframe { width: 900px; height: 600px; border: 2px solid red; }</style>
<p>Якщо у рамці нижче видно сторінку цілі — заборони фреймінгу немає.</p>
<iframe src="https://app.example.com/account/settings"></iframe>
Далі — читання результату. Консоль браузера тут важливіша за саму рамку: саме вона каже, який механізм спрацював.
| Що видно у пробі | Що це означає | Наступний крок |
|---|---|---|
| Сторінка відрендерилась у рамці | Захисту немає — дефект підтверджено | Зафіксувати шапку відповіді й завести баг на конкретний URL |
Рамка порожня, у консолі згадка frame-ancestors | Працює CSP | Перевірити директиву на всіх HTML-сторінках, а не лише на головній |
Рамка порожня, у консолі згадка X-Frame-Options | Працює старий заголовок | Подивитися значення: SAMEORIGIN перевірити ще й вкладеним фреймом |
| Сторінка блимнула й «вистрибнула» з рамки | Клієнтський frame busting | Повторити з вимкненим JavaScript — заголовок має спрацювати без скриптів |
| Десктопна адреса порожня, мобільна рендериться | Політика не покриває мобільний хост чи шаблон | Окремий баг на мобільну адресу |
Що дивитися і чому:
- Позитивний результат сильний, негативний слабкий. Рендер у фреймі — доказ. Порожня рамка доводить лише те, що в цьому браузері й на цьому URL щось спрацювало; який саме механізм і чи покриває він решту сторінок, проба не каже.
- Одна сторінка — не вибірка. Заборона фреймінгу діє посторінково, тож обходять хоча б головну, форму входу, налаштування акаунта й будь-який екран із незворотними діями. Класична діра — старий підрозділ або службова сторінка, які не проходять через сучасний шар конфігурації.
SAMEORIGINперевіряють вкладеністю. Значення вимагає одного від усіх предків, тому цільову сторінку кладуть у свій фрейм, а той — у чужий: якщо на цьому кроці нічого не змінилося, у конфізі щось інше, ніж ви думаєте.- Проба читає відповідь, а не конфіг. Веб-проксі відомі тим, що додають і знімають заголовки, тож той самий URL варто перевірити і крізь проксі, і напряму до застосунку. Що саме поставило заголовок, з відповіді не видно — об'єкт відповіді джерела не розрізняє.
Кейс 3. Playwright: заголовки як контракт
Ручна проба відповідає на питання «зараз захист є?», автотест — на питання «він ще є після вчорашнього деплою?». Три перевірки закривають різні провали: підміну захисту report-режимом, зникнення HSTS і надмірні вимоги там, де заголовок безпредметний.
import { test, expect, type APIResponse } from '@playwright/test';
const BASE = 'https://app.example.com';
const PAGE = `${BASE}/account/settings`;
// headers() тримає одне значення на імʼя — повторів у ньому не видно
function headerOf(res: APIResponse, name: string): string | undefined {
const hits = res.headersArray().filter((h) => h.name.toLowerCase() === name);
return hits.length ? hits.map((h) => h.value).join(', ') : undefined;
}
test('фреймінг заборонено, і саме примусовим механізмом', async ({ request }) => {
const res = await request.get(PAGE);
expect(res.status()).toBe(200);
const csp = headerOf(res, 'content-security-policy') ?? '';
const xfo = headerOf(res, 'x-frame-options')?.trim().toUpperCase();
const blocked = /frame-ancestors\s+'none'/.test(csp) || xfo === 'DENY' || xfo === 'SAMEORIGIN';
expect(blocked, `фреймінг відкритий: CSP="${csp}", XFO="${xfo}"`).toBe(true);
// report-only за захист не зараховуємо: він нічого не блокує
const reportOnly = headerOf(res, 'content-security-policy-report-only');
expect(Boolean(reportOnly) && csp === '', 'у відповіді лише report-only').toBe(false);
});
HSTS перевіряють окремо, і саме на заголовку: браузер ще довго поводитиметься правильно за старою політикою, тож поведінкою не спіймати.
test('HSTS: політика на місці, а редирект з HTTP її не несе', async ({ request }) => {
const res = await request.get(PAGE);
const hsts = res.headers()['strict-transport-security'] ?? '';
const maxAge = Number(/max-age=(\d+)/.exec(hsts)?.[1] ?? 0);
expect(maxAge, 'HSTS зник або обнулений — політику знято').toBeGreaterThan(0);
expect(hsts).toContain('includeSubDomains');
// API-клієнт не тримає HSTS-стану браузера, тому редирект видно як є
const plain = await request.get('http://app.example.com/', { maxRedirects: 0 });
expect(plain.status()).toBe(301);
expect(plain.headers()['location']).toMatch(/^https:\/\//);
expect(plain.headers()['strict-transport-security']).toBeUndefined();
});
Останнє — набір поверхонь із різними очікуваннями, щоб перевірка не перетворилася на генератор хибних знахідок:
const surface = [
{ url: `${BASE}/`, html: true },
{ url: `${BASE}/account/settings`, html: true },
{ url: `${BASE}/api/v1/profile`, html: false },
];
for (const { url, html } of surface) {
test(`заголовки: ${url}`, async ({ request }) => {
const res = await request.get(url);
expect(res.headers()['x-content-type-options']).toBe('nosniff');
expect(res.headers()['content-type']).toBeTruthy();
if (html) {
expect(headerOf(res, 'content-security-policy'), 'CSP на HTML обовʼязкова').toBeTruthy();
expect(res.headers()['referrer-policy']).toBe('strict-origin-when-cross-origin');
}
// на JSON-відповіді CSP і X-Frame-Options не захищають нічого — не вимагаємо
});
}
Що дивитися і чому:
headers()протиheadersArray(). Обʼєктна форма зручна, поки заголовок один; для тих, що трапляються кілька разів, потрібна масивна — інакше частина значень тихо зникає з перевірки. Масивна форма ще й лишає імена в оригінальному регістрі, тому порівнювати їх треба черезtoLowerCase().- на «наявність CSP» без перевірки режиму нічого не вартий. Саме тому в тесті окремо відсіюється ситуація «є лише report-only»: інакше зелений тест підтверджуватиме політику, яка за означенням не блокує.
- HSTS ловлять на заголовку, а не на поведінці. Політика живе в браузері до кінця
max-age, тож після зникнення заголовка сценарії ще довго проходитимуть. Заодно перевіряють редирект з HTTP: він має бути постійним, вести наhttpsі не нести HSTS у самій редирект-відповіді. - Різні поверхні — різні очікування. Вимагати
X-Frame-Optionsчи CSP від JSON-відповіді API означає плодити тікети, які закриють як «not a bug». А отnosniffдоречний скрізь, де щось віддається клієнту, і перевіряти його треба разом ізContent-Type. - Вердикт прив'язаний до адреси, на якій його зняли. Прогін проти внутрішньої адреси в обхід проксі перевіряє інший шлях: саме проксі здатен зрізати заголовок, і тоді сайт втрачає захист, якого тест не побачив. Сканер тут не заміна — він оцінює публічну адресу, а не закритий стенд.
Клікджекінг
- Можу за 30 секунд описати механіку: ціль у прихованому
<iframe>, підставні елементи поверх чутливих кнопок, клік дістається цілі — і назвати , без якої марна: жертва залогінена. - Розрізняю і CSRF за джерелом дії: тут запит іде зі справжньої цільової сторінки з правильним токеном, тож частину анти-CSRF заходів атака обходить, а
SameSiteлишається помʼякшенням, не рішенням. - Розумію асиметрію вердикту в пробі з фреймом: «сторінка відрендерилася» доводить відсутність захисту, «не відрендерилася» — лише те, що щось спрацювало в цьому браузері на цьому URL; поправки — вимкнений JavaScript, мобільна версія, .
Заборона фреймінгу
- Знаю значення
X-Frame-Optionsі те, щоSAMEORIGINвимагає одного від усіх предків, аALLOW-FROMне звужує дозвіл, а змушує браузер відкинути заголовок цілком. - Можу пояснити, що робить
frame-ancestors: список дозволених батьків через пробіл,'none'в обовʼязкових лапках як ключове слово, звірка кожного предка в ланцюжку. - Не плутаю
frame-ancestors(хто вбудовує нас) ізframe-src(що вбудовуємо ми) і памʼятаю, щоframe-ancestorsне відкочується наdefault-src— томуdefault-src 'none'фреймінг не закриває. - Знаю пріоритет: за наявності
frame-ancestorsу режимі enforce старий заголовок ігнорується, тож ставлять обидва — директиву як механізм, заголовок як сумісність; і памʼятаю межі — заголовок потрібен на всіх HTML-відповідях і безпредметний на редиректі та JSON.
CSP: склад, сила й режими
- Можу назвати чотири класи директив (fetch, document, navigation, reporting) і ланцюг запасних значень (
default-srcдля fetch,script-srcдля-elem/-attr,child-srcдляframe-src/worker-src), а також дві межі області дії: політику документа не успадковує, а кілька політик в одній відповіді одна одну не послаблюють. - Знаю, що знімають захист
'unsafe-inline'(повертає inline-скрипти й inline-обробники),'unsafe-eval'та'unsafe-hashes', що*у host-джерелі робить allowlist ширшим, ніж задумано, — і чому список доменів узагалі програє строгій політиці на nonce або хешах, при тому що навітьdefault-src https:кращий за відсутність політики. - Можу пояснити вимоги до nonce (нове значення на кожну відповідь, підставляє шаблонізатор, а не мідлвар усім тегам підряд), крихкість хешів і ціну
'strict-dynamic': host-джерела, схеми,'self'і'unsafe-inline'перестають діяти. - Розрізняю дію політики: enforce блокує, report-only лише звітує (і в проді захистом не є), обидва заголовки можуть діяти одночасно, а прогін тестів проти report-політики — готова «що зламається після вмикання».
- Знаю, чого немає в метатезі:
frame-ancestors,report-uri,sandboxі сам режим report-only; те саме стосуєтьсяX-Frame-Optionsі у<meta>.
HSTS і транспорт
- Можу перелічити директиви (
max-age,includeSubDomains,preload) і вимоги preload: рік, обовʼязковийincludeSubDomains, незворотність прапорця й окрема подача домену. - Розумію, що політику зберігає браузер, а не відповідь сервера: заголовок по HTTP ігнорується, у редирект-відповіді його бути не повинно, прибраний заголовок нічого не скасовує, знімає політику лише
max-age=0по HTTPS. - Знаю напрям
includeSubDomains(униз по дереву імен, до апекса не піднімається) і навіщо вона потрібна — доменні кукі зSecure; памʼятаю й наслідки для стенду: самопідписаний сертифікат на HSTS-хості вбиває сценарій, а зашитийhttp://до сервера не дійде.
Referrer-Policy, Permissions-Policy і сусідні заголовки
- Не плутаю
Referer(запит, одна «r») ізReferrer-Policy, знаю дефолтstrict-origin-when-cross-origin, який діє й при невалідному значенні, памʼятаю пʼять шляхів надходження політики (перемагає останній) і те, щоunsafe-url— знахідка. - Розумію, що відсутність директиви в
Permissions-Policyдорівнює її власному типовому allowlist, вимикає можливість лише порожній список(), а навігація фрейма політику скидає. - Знаю, що
nosniffперевіряють у парі зContent-Type, щоX-XSS-Protectionсьогодні або відсутній, або явно вимкнений, і щоno-cacheна відповіді з персональними даними — знахідка, бо потрібенno-store.
CORS і перевірка
- Памʼятаю, що
Access-Control-Allow-Originне підсилює , а робить у ній виняток, і шукаю чотири дефекти:*на приватному API, віддзеркаленняOriginбез звірки зі списком, значенняnull, відсутнійVary: Origin. - Розумію, що метатег не заміняє заголовка для
X-Frame-Options,frame-ancestors, report-режиму й HSTS, що проксі на шляху здатен заголовок додати або зрізати, а сканер оцінює те, що віддає публічна адреса; у Playwright для повторюваних заголовків беруheadersArray(), боheaders()тримає одне значення на імʼя.
Квіз
Перед стартом
- Питань: 17
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Клікджекінг (clickjacking) — що це за атака?