CSRF і SameSite
Зміст
Сесія тримається на одному рядку, який браузер підставляє сам: отримавши кукі від сайту, він додає її до будь-якого наступного запиту на цей сайт — байдуже, хто цей запит ініціював. Саме ця зручність робить логін одноразовим, і саме вона відкриває міжсайтову підробку запиту (cross-site request forgery, CSRF): чужа сторінка змушує браузер жертви виконати дію на довіреному сайті, а сервер бачить цілком валідну сесію.
Для QA тема незручна тим, що обидва звичні вердикти тут хибні: «токен у формі лежить — захищено» і «стоїть SameSite=Lax — CSRF закрито». Перше доводить лише те, що значення передали, друге — залежить від того, чи є в застосунку мутація, досяжна . Тому й розмова про CSRF швидко зʼїжджає з назв на механізм.
Означення самої та її місце у вебі — глава Автентифікація та авторизація; й прапорці кукі — Сесії та токени. Тут — захисний бік: за чим відбирають , як улаштований токен, що насправді дає SameSite, чим закривають ендпоінти без форм і як відтворити атаку так, щоб вердикт спирався не на враження.
Три умови, за яких ендпоінт вразливий
CSRF починається не з експлойта, а з фільтра: більшість ендпоінтів до цієї атаки байдужі. MDN формулює фільтр трьома умовами, які діють разом. Сайт: уживає HTTP-запити для зміни якогось стану на сервері; уживає лише кукі, щоб підтвердити, що запит прийшов від автентифікованого користувача; уживає в запиті лише параметри, які може передбачити.
Друга умова вирішує суперечку про API. Питання не в тому, JSON там чи форма, а в тому, що саме сервер приймає як доказ особи в цьому запиті. Гайд тестування OWASP ставить його як аудит до будь-якого відтворення: якщо керування сесіями спирається лише на клієнтські значення — інформацію, доступну браузеру, — застосунок вразливий. Механізм один: браузер автоматично надсилає інформацію, що ідентифікує сесію, і, отримавши кукі від сайту, шле її з усіма подальшими запитами до нього. Кукі не єдиний носій — те саме стосується застосунків на самих лише механізмах HTTP-автентифікації. Звідси й вибір заходу: підхід обирають за клієнтом і методом автентифікації, а для тих, хто покладається на кукі, лишаються необхідними.
І окремо — на чому стоїть вердикт. перелічує чотири CSRF: поведінка браузера щодо повʼязаної із сесією інформації; знання атакувальником валідних URL і функціональності; керування сесіями, що спирається лише на відоме браузеру; існування HTML-тегів, чия присутність спричиняє негайне звернення до ресурсу (img). Перші три суттєві для наявності вразливості, четвертий лише полегшує експлуатацію, тож «робочого експлойта немає» вердикту не скасовує.
Анти-CSRF токен: що саме він ламає
CSRF-токен (CSRF token) — непередбачуване значення, яке сервер вбудовує у видану сторінку; легітимна сторінка додає його до стано-змінного запиту, а сервер виконує запит лише при збігу. Чому це працює, видно з третьої умови вище: токен ламає саме передбачуваність параметрів — без нього атакувальник не може створити валідний запит до бекенду.
Один із найпопулярніших і рекомендованих заходів — патерн синхронізованого токена (synchronizer token pattern), і нормативна частина в ньому не «вкласти токен», а звірити: серверний компонент мусить перевірити наявність і валідність токена в запиті та порівняти його з токеном, знайденим у сесії користувача. Вимоги до значення:
- унікальне для сесії користувача, таємне, непередбачуване — велике випадкове значення від надійного генератора;
- генерується на боці сервера — раз на сесію або на кожен запит;
- не передається через кукі в синхронізованому патерні;
- не витікає в серверні логи чи в URL.
Останній пункт для QA найдешевший: токен у рядку запиту — знахідка сама по собі, без експлойта. Вибір «на сесію чи на запит» джерело подає як розмін: токени на запит безпечніші, бо вікно експлуатації вкраденого значення мінімальне, зате псують зручність — класична жертва тут навігація «назад».
Захід добирають за архітектурою: застосунок зі станом — синхронізований токен; без стану — double submit cookies; сайт, керований API, який не може вживати теги <form>, — кастомні заголовки запиту. Перший крок — не писати своє, а перевірити, чи фреймворк уже дає CSRF-захист.
Double submit улаштований так: сайт ставить псевдовипадкове значення кукі й вимагає те саме значення полем форми; запит валідний лише при збігу. Стоїть це на політиці одного — чужий сайт кукі не прочитає й не змінить. Але наївна форма має названий обхід: атакувальник, здатний писати кукі на цільовий домен (уразливий піддомен, захоплення DNS, кукі відкритим HTTP на кукі без префікса __Host-), підставить обидві половини сам. Робочий варіант — підписаний double submit, привʼязаний до автентифікованої сесії; підпису без привʼязки замало.
помʼякшують пре-сесіями й токеном у самій формі логіна: перевірка «чи є токен» не закінчується на екранах під автентифікацією.
І дві межі, за якими токен не рятує. XSS перемагає всі техніки помʼякшення CSRF, і каталог слабкостей ставить свободу від XSS передумовою захисту (дім теми — XSS: міжсайтовий скриптинг). (client-side CSRF), у якому запит формує власний JavaScript застосунку, здатний обійти і токени, і SameSite-кукі: «токен у розмітці є» ще не означає «токен перевірено».
SameSite: три значення й межа «сайту»
Атрибут SameSite обмежує область кукі так, що вона додається до запиту лише тоді, коли запит є same-site. Задум названо в чернетці rfc6265bis прямо: значення Lax уведено як механізм «з коробки», що помʼякшує CSRF-атак, роблячи виняток для частини крос-сайтових запитів.
Strict— кукі надсилається лише з same-site запитами; браузер не додасть її до жодного крос-сайтового, зокрема й до верхньорівневої навігації з крос-сайтового контексту документа.Lax— same-site плюс крос-сайтові верхньорівневі навігації: у крос-сайтовому запиті кукі проходить тоді й тільки тоді, коли виконано обидві умови: запит є навігацією верхньорівневого контексту перегляду І запит ужив безпечний метод.GETбезпечний,POST— ні.None— кукі йде і з same-site, і з крос-сайтовими запитами; безSecureбраузер таку кукі просто не прийме.
Далі два місця, де тест дає несподіваний результат.
Кукі без атрибута — не те саме, що явний Lax. Кукі без SameSite трактується як SameSite=Lax, але типова поведінка Chrome дозвільніша: вона пропускає частину кукі на верхньорівневих POST. Чернетка називає режим поіменно — Lax-allowing-unsafe не є значенням атрибута, це режим, який user agent MAY застосувати лише до кукі без явного SameSite; його обмежують віком кукі (розумною межею названо дві хвилини) і прямо оголошують тимчасовим перехідним заходом, що дає менший захист. Звідси й порада виставляти атрибут явно. А друкарська помилка у значенні атрибут не «вимикає»: значення поза трьома відомими ключовими словами підпадає під дефолтний режим, еквівалентний Lax.
Межа атрибута — сайт, а не походження. https://foo.example.org і https://bar.example.org — різні походження, але той самий сайт: кукі, поставлена на app.example.com із будь-яким значенням SameSite, лишається same-site для запитів із будь-чого в .example.com. «Сайт» тут — (registrable domain): плюс мітка ліворуч від нього, у щоденній мові eTLD+1. Що є суфіксом, визначає список публічних суфіксів, і в ньому не лише домени верхнього рівня — є й сервіси на кшталт github.io, тому your-project.github.io і my-project.github.io рахуються різними сайтами. Схема входить у межу навмисно, щоб HTTP не був слабким каналом. Наслідок важливий: уразливий піддомен знімає цей захист цілком.
Наскільки міцний це рубіж
Тут доводиться зробити те, чого в навчальному тексті зазвичай не роблять, — навести чотири канонічні формулювання поруч. Вони не про різні факти, а про різну силу заяви, і зведення їх до одного речення бреше.
- Чернетка
rfc6265bisпроStrict: кукіSameSiteдають надійний захист від CSRF, коли розгорнуті в суворому режимі й підтримані клієнтом, — і одразу ж радить не робити цю позначку межею всього захисту сайту, бо same-site навігації можна виконати вкупі з іншими векторами (XSS, зловживання редиректами). ПроLaxта сама чернетка каже слабше: розумна проти атак на небезпечних методах, але не надійний захист від CSRF як категорії загалом. - OWASP: контроль глибини захисту, який у більшості розгортань не замінює повноцінного CSRF-захисту й має поєднуватися з токеном або double submit.
- MDN: дає певний захист, це не повний захист, краще як доповнення до одного з інших.
- web.dev: ані
Strict, аніLaxне є повним розвʼязанням безпеки сайту.
Переказу «SameSite не захищає» не робить жодне з цих джерел; беззастережного «SameSite закриває CSRF» — теж жодне.
Чому «замало» — видно з конкретних щілин:
- Стано-змінна операція, доступна через
GET.Laxблокує лише небезпечні методи, а на верхньорівневих навігаціях безпечними (GET,HEAD,OPTIONS,TRACE) кукі проходить. OWASP називає це найпоширенішим способом, у який захист на базіSameSiteпровалюється на практиці; чеклист MDN відповідає окремим пунктом — уникатиGETдля стано-змінних запитів. - Верхньорівневу навігацію можна влаштувати. Атакувальник відкриє нове вікно або запустить навігацію, щоб створити «same-site» запит; чернетка називає атрибут тут лише «лежачим поліцейським» на дорозі до експлуатації.
- Підресурси вже same-site. Крос-сайтовий верхньорівневий запит на чутливу сторінку кукі
SameSite=Strictне отримає — а запити за підресурсами цієї сторінки отримають. - Перезавантаження тягне все. Запити, видані для перезавантажень, ініційованих не користувачем, додають усі
SameSite-кукі.
Чеклист MDN ставить пріоритет: Strict, якщо можеш, Lax, якщо мусиш. Ціна Strict — перший запит користувача, який прийшов за чужим посиланням, піде без кукі; канонічний вихід тут — дві кукі, де окрема Strict-кукі «на запис» своєю відсутністю запускає повторну автентифікацію перед неідемпотентною дією.
І межа інструмента, важлива для AQA: читання атрибута з тесту дока Selenium обіцяє в Chrome 80+, Firefox 79+ і Selenium 4 — на старішому стенді «перевірка пройшла» означає «перевірки не було».
Ендпоінт без форми: кастомний заголовок, CORS і Sec-Fetch-Site
Багато сучасних вебзастосунків не вживають тегів <form> узагалі. Для AJAX- і API-ендпоінтів OWASP дає окремий захід — запиту (custom request header), і токен для нього не потрібен: клієнт просто додає власний заголовок до запитів, що потребують CSRF-захисту.
Тримається це не на секретності заголовка, а на механіці браузера: запити з кастомними заголовками всі сучасні браузери позначають такими, що підлягають CORS-preflight, — спершу летить OPTIONS, який перевіряє відповідність CORS на цільовому сервері. З боку атаки MDN каже те саме: такі запити за замовчуванням не дозволені , тож CSRF-атака не вдалася б. Звідси системне формулювання: сайт, що вживає fetch() або XMLHttpRequest, захищається тим, що його стано-змінні запити ніколи не є «простими запитами».
Тепер важлива частина. Обмеження, на якому все це стоїть, послаблює сам сервер: протокол CORS (дім теми — CORS і політика одного походження) для того й існує. Умова вразливості названа поіменно й читається по заголовках відповіді: сайт буде вразливим до CSRF-атаки з певного походження, якщо відповідь на стано-змінний запит містить Access-Control-Allow-Origin із переліченим у ньому походженням відправника і заголовок Access-Control-Allow-Credentials.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://partner.example
Access-Control-Allow-Credentials: true
Кукі в крос-оріджин запитах за замовчуванням не встановлюються — саме Access-Control-Allow-Credentials: true їх і вмикає. Названий і типовий дефект конфігурації: менш безпечно дозволяти CORS з усіх піддоменів , бо, захопивши піддомен (з хмарними сервісами це не рідкість), атакувальник обійде й підробить запит разом із вашим кастомним заголовком.
Тому дві речі тримають окремо: CORS — про читання відповіді, CSRF — про саму дію, яка відбувається й тоді, коли відповідь прочитати неможливо. Конфігурація CORS тут — частина поверхні, яку перевіряють; неправильний CORS як самостійний вектор — глава HTTP-заголовки безпеки, CSP і клікджекінг.
Друга щілина API — content-type: JSON-API природно потребує CORS, але якщо він приймає text/plain, це робить його вразливим, бо простий content-type відтворюється звичайною формою. Захід — заборонити прості content-type на сервері чи в API.
А найшвидший спосіб побачити, як браузер класифікував запит, — заголовок Sec-Fetch-Site: він каже серверу, чи запит same-origin, same-site, cross-site, чи ініційований безпосередньо користувачем. Значенню можна обґрунтовано довіряти: заголовки з префіксом Sec- не змінити з JavaScript, і браузер ставить їх завжди.
POST /api/transfer HTTP/1.1
Host: bank.example
Origin: https://evil.example
Sec-Fetch-Site: cross-site
Content-Type: text/plain;charset=UTF-8
Як відтворити і що вважати доказом
Мета перевірки сформульована в WSTG як інваріант: зʼясувати, чи можливо ініціювати запити від імені користувача, які не ініційовані самим користувачем.
Стано-змінний GET форми не потребує взагалі: коли користувач завантажує сторінку, браузер намагається дістати ресурс-картинку, який насправді є запитом . Каталог слабкостей перелічує носії ширше — URL, завантаження картинки, XMLHttpRequest тощо, — і всі вони будуть трактовані як автентичний запит клієнта.
Для POST рецепт трикроковий: створити HTML-сторінку з відповідною формою, розмістити її на сторонньому сайті, надіслати посилання жертві й спонукати клікнути. Те, що ендпоінт приймає лише POST, захистом не є: такі запити автоматизуються через JavaScript. Для JSON-ендпоінта обхід описаний окремо: у форматі JSON немає параметрів запиту, обовʼязкових для самонадсильних форм, тому enctype міняють на text/plain, щоб пейлоад доставився як є, а зайве поле-паддинг сервер проігнорує.
І межа, про яку забувають: ціль не мусить бути в інтернеті — достатньо, щоб адреса була досяжна жертві, а не атакувальнику, тож внутрішня адмінка з цього кола не випадає. Обсяг наслідків обмежений привілеями жертви: атакувальник дістає лише те, що відкрито вразливим застосунком і правами цього користувача.
Тепер про доказ. Найтиповіша хиба зафіксована в каталозі слабкостей прикладом коду, який «убезпечує» надсилання форми перевіркою, що користувач має валідну сесію. Вердикт джерела: CSRF-атаку цей контрзахід не спинить, бо атакувальник підробляє запит через браузер користувача, у якому валідна сесія вже існує. Перевірка Referer названа разом зі своєю ціною: вона може зламати легітимну функціональність, бо користувачі або могли вимкнути її надсилання з міркувань приватності. І пряма вимога того ж каталогу — не вживати GET для запитів, що змінюють стан.
Останнє — про межу самої перевірки: гайд тестування заходів запобігання не перелічує, а віддає їх до OWASP CSRF Prevention Cheat Sheet. Тобто перевірка, яка спинилася на відтворенні, ще не сказала, чи достатній захід.
Типові помилки
«У нас POST, чужа форма нам не страшна». Виглядає як рубіж, а насправді такі запити автоматизуються через JavaScript, і саме лише вживання методу наявності вразливості не виправляє.
«Токен у запиті є — захист працює». Виглядає як доказ, а насправді доводить лише те, що значення передали: захистом є звірка з токеном сесії. А перевірка «сесія валідна» контрзаходом проти CSRF не є взагалі — запит іде через браузер, у якому сесія вже жива.
«У нас JSON-API, формою такого не надішлеш». Виглядає як імунітет, а насправді ендпоінт, що приймає text/plain, відтворюється звичайною формою зі зміненим enctype.
«Стоїть SameSite=Lax — CSRF закрито». Виглядає як закритий пункт, а насправді Lax блокує лише небезпечні методи: мутація, досяжна через GET, проходить. І кукі без атрибута поводиться інакше, ніж кукі з явним Lax, на верхньорівневих POST — два схожі тести дадуть різний результат, і обидва будуть правильними.
«У нас налаштований CORS — від CSRF він і захищає». Виглядає як рубіж, а насправді CORS вирішує, чи дозволено прочитати відповідь, тоді як CSRF — про саму дію, яка відбувається й тоді, коли відповідь прочитати неможливо. Гірше: та сама конфігурація буває стороною послаблення — пара Access-Control-Allow-Origin із походженням відправника плюс Access-Control-Allow-Credentials і є названою умовою вразливості.
«Внутрішня адмінка в інтернет не дивиться — їй CSRF не загрожує». Виглядає як ізоляція, а насправді достатньо, щоб адреса була досяжна жертві: браузер співробітника ходить і всередину периметра.
Підсумок
- Відбір важливіший за експлойт. Три умови діють разом: запит змінює стан, упізнання лише за кукі, параметри передбачувані. Вердикт ухвалюють за передумовами, а не за наявністю робочого експлойта.
- Токен ламає передбачуваність — і лише тоді, коли сервер його звіряє з токеном сесії. Не через кукі, не в URL, генерує сервер. Наявність значення в розмітці доказом захисту не є.
SameSiteміряє сайт, а не походження. Уразливий піддомен знімає його цілком, а стано-зміннийGETзнецінюєLax— це названа найчастіша причина провалу на практиці.- Сила заяви про
SameSiteрізна в чотирьох канонічних джерелах — від «надійного захисту в суворому режимі» до «не повного захисту». Ані «не захищає», ані «закриває CSRF» чесним переказом не є. - Для ендпоінтів без форм рубіж — кастомний заголовок, і тримається він на дефолтній забороні браузера. CORS-конфігурація цю заборону послаблює; XSS знімає захід цілком.
Можливі питання
«Що таке CSRF і чому браузер узагалі виконує чужий запит?» Дивляться, чи розводите ви дві речі: кукі доводить, що запит іде від браузера користувача, але не доводить, що його ініціювала сторінка застосунку. Сильна відповідь одразу називає умови, за яких атака складається.
«Чим від цього захищаються і чому саме так?» Очікують механізм, а не список: токен ламає передбачуваність параметрів, і нормативна частина — звірка з токеном сесії. Плюс вимоги: генерує сервер, не через кукі, не в URL і логах.
«У чому різниця між Strict, Lax і None?» Питання-фільтр на точність. Lax пропускає крос-сайтовий запит лише на верхньорівневій навігації безпечним методом; None без Secure браузер не прийме; кукі без атрибута і кукі з явним Lax — різні сценарії.
«Чи достатньо SameSite, щоб закрити CSRF?» Тут перевіряють обережність формулювань. Сильна відповідь називає щілини поіменно — стано-змінний GET, межа по сайту замість походження, підресурси Strict-сторінки — і не зводить різні за силою заяви джерел до одного гасла.
«Як ви перевірите ендпоінт на CSRF?» Хочуть процедуру: відбір за трьома умовами, відтворення (GET картинкою, POST самонадсильною формою, JSON через text/plain) і окремо — підтвердження захисту, бо «сесія перевіряється» контрзаходом не є.
«Коли API вразливе?» Слухають, чи не підміните ви критерій форматом. Питання не в JSON проти форми, а в тому, чим сервер упізнає користувача в цьому запиті й чи послаблена дефолтна заборона браузера: пара Access-Control-Allow-Origin із походженням відправника плюс Access-Control-Allow-Credentials — названа умова вразливості, а не деталь конфігурації.
Джерела
Три умови, за яких ендпоінт вразливий
- MDN — Cross-site request forgery (CSRF) — три умови, за яких атака можлива, і роль передбачуваності параметрів.
- OWASP WSTG — 4.6.5 Testing for Cross Site Request Forgery (WSTG-SESS-05) — аудит керування сесіями, автоматичне надсилання сесійної інформації, чотири передумови й що з них суттєве.
- OWASP — CSRF Prevention Cheat Sheet — вибір заходу за клієнтом і методом автентифікації; токени необхідні застосункам на кукі.
Анти-CSRF токен: що саме він ламає
- OWASP — CSRF Prevention Cheat Sheet — синхронізований токен і звірка з токеном сесії, вимоги до значення, розподіл заходів за архітектурою, підписаний double submit, login CSRF, XSS і клієнтський CSRF як межі.
- MDN — Cross-site request forgery (CSRF) — означення токена й умова передбачуваності параметрів, яку він ламає.
- CWE-352 — Cross-Site Request Forgery (CSRF) — механіка double submit і опора на політику одного походження, XSS як передумова захисту, непередбачуваність значення як окрема слабкість.
SameSite: три значення й межа «сайту»
- IETF Internet-Draft — Cookies: HTTP State Management Mechanism (rfc6265bis-22) — область атрибута й задум
Lax, три значення, режимLax-allowing-unsafeі його межа у дві хвилини, дефолт при невідомому значенні, реєстрований домен. - MDN — Cross-site request forgery (CSRF) — формула
Laxчерез безпечний метод,Strictяк найбільший захист, сайт проти походження як слабша межа. - web.dev — SameSite cookies explained — дефолт дозвільніший за явний
Lax,NoneбезSecureне приймається, список публічних суфіксів і прикладgithub.io. - web.dev — "Same-site" and "same-origin" — same-site як однакова схема плюс однаковий eTLD+1 і навіщо в означення додано схему.
- OWASP — CSRF Prevention Cheat Sheet — область реєстрованого домену: кукі піддомену лишається same-site для всього домену.
- IETF Internet-Draft — Cookies: HTTP State Management Mechanism (rfc6265bis-22) — «robust defense in strict mode» із застереженням і слабша заява про
Lax, обхід через нову верхньорівневу навігацію, підресурси Strict-сторінки, перезавантаження не від користувача, рецепт двох кукі. - OWASP — CSRF Prevention Cheat Sheet — глибина захисту, що не замінює повноцінного CSRF-захисту; стано-змінний
GETяк найчастіша причина провалу. - MDN — Cross-site request forgery (CSRF) — «не повний захист, а доповнення»; пункти чеклиста про
GETі пріоритетStrictнадLax. - web.dev — SameSite cookies explained — ані
Strict, аніLaxне є повним розвʼязанням безпеки сайту. - Selenium — Working with cookies — межа інструментальної перевірки: Chrome 80+, Firefox 79+, Selenium 4.
Ендпоінт без форми: кастомний заголовок, CORS і Sec-Fetch-Site
- OWASP — CSRF Prevention Cheat Sheet — кастомний заголовок як захід для AJAX/API, опора на CORS-preflight,
Access-Control-Allow-Credentialsі кукі, регулярка по піддоменах, прості content-type у JSON-API. - MDN — Cross-site request forgery (CSRF) — заборона крос-оріджин запитів із кастомними заголовками за замовчуванням, правило «ніколи не прості запити», умова вразливості по парі заголовків відповіді,
Sec-Fetch-Siteяк основний захист. - web.dev — "Same-site" and "same-origin" — значення
Sec-Fetch-Siteі чому заголовкам із префіксомSec-можна довіряти. - MDN — Cross-Origin Resource Sharing (CORS) — CORS як механізм доступу до відповіді, а не барʼєр проти самої дії.
Як відтворити і що вважати доказом
- OWASP WSTG — 4.6.5 Testing for Cross Site Request Forgery (WSTG-SESS-05) — мета перевірки, трикроковий рецепт для
POST,POSTяк недостатній захід,enctypeі паддинг для JSON, досяжність цілі за фаєрволом, відсилання по заходи до окремого документа. - CWE-352 — Cross-Site Request Forgery (CSRF) — носії підробленого запиту, «валідна сесія» як хибний контрзахід, ціна перевірки
Referer, заборона стано-змінногоGET, обсяг за привілеями жертви. - MDN — Cross-site request forgery (CSRF) — картинка, яка насправді є запитом транзакції.
- OWASP — CSRF Prevention Cheat Sheet — межа наслідків: тільки можливості, відкриті застосунком, і права користувача.
Що таке CSRF і чому браузер виконує запит, який ініціювала чужа сторінка?
Міжсайтова підробка запиту (cross-site request forgery, CSRF) — це коли сторонній сайт змушує браузер жертви зробити дію на сервісі, де вона залогінена. Механіка тут навіть не хакерська: щойно домен видав браузеру кукі, той чіпляє її до кожного наступного звернення на цей домен і не цікавиться, хто це звернення породив — власна сторінка застосунку чи чужа вкладка. Виходить, кукі підтверджує рівно одне: запит вийшов із браузера цього користувача. Що користувач цієї дії хотів — вона не підтверджує ніяк. Сервер бачить живу сесію, робить переказ або міняє адресу й повертає цілком нормальну відповідь. Сильний кандидат на співбесіді одразу розводить ці два твердження — «запит із браузера користувача» і «запит, ініційований застосунком»: саме на їх злипанні й тримається.
Коли ендпоінт узагалі варто перевіряти на CSRF?
Більшість до цієї атаки байдужі, тому починають не з експлойта, а з відбору за трьома умовами — і виконатися вони мусять одночасно. Перша: запит змінює щось на сервері (створює, редагує, видаляє, переказує). Друга: у цьому запиті сервер упізнає користувача лише за тим, що браузер підставляє автоматично, тобто за кукі — сюди ж потрапляють застосунки на самій HTTP-автентифікації. Третя: усі параметри запиту може вгадати наперед. Не виконалася хоч одна — ендпоінт із кола підозрюваних випадає. Друга умова при цьому вирішує вічну суперечку про API: формат тіла нічого не вирішує, вирішує спосіб упізнання — чи вистачає серверу того, що браузер підставив сам. Такий фільтр економить години роботи: замість перебирати всі ручки поспіль, ви йдете тільки по стано-змінних, «кукі-автентифікованих» і передбачуваних.
Чим CSRF відрізняється від XSS?
Це різні атаки з різним рівнем доступу. При XSS чужий код виконується в контексті самого сайту — атакувальник читає DOM, забирає значення з розмітки, бачить відповіді сервера. При CSRF нічого свого в жертві не виконується: чужа сторінка лише вміє надіслати запит і не бачить, що на нього відповіли, — «наосліп» спрацьовує сама дія. Тому CSRF цілиться в операції, де результат не потрібен: змінити пошту, підтвердити переказ, видалити запис. Практично важлива й ієрархія між ними: XSS перемагає будь-які помʼякшення CSRF, бо звідти можна прочитати токен просто зі сторінки й скласти легітимний запит. Тому відсутність XSS вважають CSRF-захисту, а не сусіднім пунктом чеклиста: заводити тікет «токен слабкий» на сайті з живим XSS — лікувати нежить при відкритому переломі.
На чому стоїть вердикт «вразливо», якщо робочого експлойта у вас немає?
На передумовах, а не на демонстрації. Гайд тестування OWASP перелічує чотири речі, потрібні для атаки: браузер сам возить повʼязану із сесією інформацію; атакувальник знає валідні адреси й що там за функціональність; керування сесіями спирається лише на те, що браузеру й так відоме; існують HTML-теги, які самою присутністю на сторінці тягнуть ресурс (класика — img). Ключове: суттєві для наявності вразливості перші три, а четвертий лише спрощує експлуатацію. Тобто «ми не змогли скласти сторінку-експлойт» вердикту не скасовує — це заява про зручність, а не про безпеку. Для звіту різниця принципова: ви фіксуєте, що керування сесіями спирається виключно на кукі, і це вже знахідка, навіть якщо демонстраційна форма ще не написана.
Що таке анти-CSRF токен і чому він працює?
— непередбачуване значення, яке сервер кладе у видану сторінку, а легітимна сторінка повертає його разом зі стано-змінним запитом; сервер виконує дію лише тоді, коли значення збіглося. Працює це рівно через третю умову вразливості: токен вибиває передбачуваність параметрів. Атакувальник може знати адресу, метод і всі поля форми, але зібрати валідний запит без значення, якого він не бачить, не здатен — не дасть йому прочитати чужу сторінку. Тобто токен не «підсилює автентифікацію», сесія лишається тією самою; він додає до запиту доказ того, що форму справді видав ваш застосунок. І з цього прямо випливає, чому значення мусить бути велике й випадкове: вгадуваний токен просто повертає ситуацію в початкову точку.
Які вимоги висувають до значення CSRF-токена?
Значення має бути унікальне для сесії користувача, таємне й непередбачуване — велике випадкове число від надійного генератора, а не лічильник, час чи хеш від імені користувача. Генерує його сервер: або один раз на сесію, або окремо на кожен запит. У патерні синхронізованого токена його не передають через кукі — це принципово, бо кукі браузер підставить сам, і сенс перевірки зникне. І окремо: токен не має витікати ні в серверні логи, ні в URL. Останній пункт для QA найдешевший — токен у рядку запиту видно неозброєним оком у серверних логах і в історії браузера, і це знахідка сама по собі, без жодного експлойта. Вибір «на сесію чи на кожен запит» — усвідомлений розмін: токени на запит лишають украденому значенню мінімальне вікно, зате псують зручність, і першою страждає навігація кнопкою «назад».
У чому суть патерну синхронізованого токена і що в ньому найчастіше роблять неправильно?
Патерн синхронізованого токена (synchronizer token pattern) — один із основних рекомендованих заходів для застосунків зі станом: сервер тримає токен у сесії, вкладає його копію у видану сторінку й порівнює обидва при кожному стано-змінному запиті. Нормативна частина тут не «вкласти токен», а звірити: серверний компонент мусить перевірити, що токен у запиті присутній, валідний і збігається зі значенням із сесії користувача. Найтиповіша хиба саме тут — команда бачить приховане поле в розмітці й закриває питання. Але наявність значення доводить лише те, що його передали; захистом є порівняння. Перевіряється це за хвилину: повторіть валідний запит із зіпсованим на один символ токеном, потім із порожнім, потім із токеном іншого користувача — якщо сервер усі три рази відповідає успіхом, звірки немає, хоч токен у формі й лежить.
Як улаштований double submit cookie і де в нього діра?
Double submit — захід для застосунків без стану, коли тримати токен у сесії ніде. Сайт кладе псевдовипадкове значення в кукі й вимагає рівно те саме значення полем форми чи заголовком; запит валідний, якщо обидві половини збіглися. Стоїть це на політиці одного : чужий сайт не прочитає вашу кукі, тому й підставити збіг не зможе. Діра в наївній реалізації названа поіменно: перевірка ловить не «атакувальник не знає значення», а «атакувальник не може його підсунути», — і той, хто здатен писати кукі на ваш домен, підставить обидві половини сам. Таку здатність дає вразливий піддомен, захоплення DNS або кукі відкритим HTTP, якщо кукі не має префікса __Host-. Робочий варіант — підписаний double submit, привʼязаний до автентифікованої сесії: самого підпису без привʼязки замало, бо валідно підписане значення без привʼязки годиться для будь-кого.
У чому різниця між SameSite=Strict, Lax і None?
Атрибут звужує область кукі так, щоб браузер додавав її лише до запитів, які вважає same-site. Strict — найсуворіший: у жодному крос-сайтовому запиті кукі не поїде, зокрема й тоді, коли користувач перейшов за посиланням із чужого сайту у верхньорівневій навігації. Lax — same-site плюс вузький виняток: у крос-сайтовому запиті кукі проходить тоді й лише тоді, коли виконано обидві умови разом — це навігація верхньорівневого контексту перегляду і метод безпечний. GET безпечний, POST — ні, тому чужа форма з POST кукі з собою не потягне, а перехід за посиланням потягне. None знімає обмеження зовсім, і без Secure браузер таку кукі просто не збереже — типова «сервер її ставить, а її немає». Формулювання про Lax варто вимовляти повністю: часта помилка — сказати «Lax пропускає GET», забувши про верхньорівневу навігацію, і на цьому переплутати фонові з переходами.
Кукі без атрибута SameSite — це те саме, що явний Lax?
Ні, і це різниця, на якій два схожі тести дають різні результати. Формально кукі без атрибута трактується як Lax, але типова поведінка Chrome дозвільніша: частину таких кукі він пропускає навіть на верхньорівневих POST. Режим має власну назву в чернетці rfc6265bis — Lax-allowing-unsafe; це не значення атрибута, а поведінка, яку user agent може застосувати виключно до кукі без явного SameSite, обмеживши її віком кукі (розумною межею названо дві хвилини). Сама чернетка описує його як тимчасовий перехідний захід, що дає менший захист, — тобто спиратися на нього не можна ні розробнику, ні тестувальнику. Практичний висновок один: атрибут виставляють явно. І ще дрібниця, яка ловить на ручній перевірці: одрук у значенні атрибут не «вимикає» — усе, що не є одним із трьох відомих ключових слів, підпадає під дефолтний режим, еквівалентний Lax, тож SameSite=Lax і SameSite=Laxx поводитимуться однаково, і тест цього не помітить.
Чому SameSite міряє «сайт», а не походження, і чим це загрожує?
Межа атрибута — сайт, тобто (registrable domain): плюс одна мітка ліворуч, у побуті eTLD+1, і схема сюди теж входить навмисно, щоб відкритий HTTP не став слабким каналом. Наслідок: https://foo.example.org і https://bar.example.org — різні походження, але один сайт, тож кукі, поставлена на один піддомен, лишається same-site для запитів звідусіль у межах домену. Захисту між своїми піддоменами SameSite не дає взагалі — один уразливий піддомен знімає його цілком. І дзеркальний сюрприз: що вважати суфіксом, вирішує список публічних суфіксів, а в ньому не лише домени верхнього рівня, а й сервіси на кшталт github.io, тому your-project.github.io і my-project.github.io рахуються різними сайтами. Для QA це два питання до архітектури: хто ще живе на наших піддоменах і чи не стоїть стенд на хостингу, де сусіди формально «інші сайти».
Чи достатньо SameSite, щоб закрити CSRF?
Тут перевіряють обережність формулювань, і виграє той, хто не зводить різні за силою заяви до одного гасла. Чернетка rfc6265bis називає кукі SameSite надійним захистом від CSRF у суворому режимі за умови підтримки клієнтом — і тут же радить не робити атрибут межею всієї безпеки сайту; про Lax та сама чернетка каже стриманіше — розумна проти атак на небезпечних методах, але не надійний захист від категорії загалом. OWASP формулює як контроль глибини захисту, який у більшості розгортань повноцінного CSRF-захисту не замінює й має йти в парі з токеном чи double submit. MDN каже, що захист є, але неповний і кращий як доповнення. web.dev — що ані Strict, ані Lax повним розвʼязанням безпеки сайту не є. Жодне джерело не каже «SameSite не захищає», і жодне не каже «SameSite закриває CSRF», тож обидві крайнощі — неточний переказ.
Які конкретні щілини лишає SameSite?
Їх чотири, і всі вони практичні. Перша й найчастіша: стано-змінна операція, доступна через GET. Lax блокує лише небезпечні методи, тому мутація на верхньорівневій навігації (GET, HEAD, OPTIONS, TRACE) проходить — OWASP називає це найпоширенішим способом, у який захист на базі SameSite провалюється в реальних розгортаннях. Друга: верхньорівневу навігацію атакувальник влаштує сам — відкриє нове вікно або запустить перехід, і чернетка тут відводить атрибуту скромну роль «лежачого поліцейського» на шляху до експлуатації. Третя: сторінка, на яку кукі Strict не поїхала, тягне свої підресурси вже як same-site, і вони кукі отримають. Четверта: перезавантаження, ініційовані не користувачем, тягнуть усі SameSite-кукі. До цього додається межа по сайту з попереднього питання — і разом виходить, чому атрибут ставлять поруч із токеном, а не замість нього.
Strict ламає перехід за зовнішнім посиланням. Як із цим живуть?
Пріоритет у чеклисті MDN простий: Strict, якщо можеш, Lax, якщо мусиш. Ціна Strict цілком реальна: користувач, який прийшов за посиланням із пошти чи месенджера, перший запит зробить без кукі й побачить себе розлогіненим, хоча сесія жива. Канонічний вихід — дві кукі замість однієї: звичайна сесійна для читання, і окрема Strict-кукі «на запис», відсутність якої не викидає користувача, а запускає повторну автентифікацію перед неідемпотентною дією. Для тестування це означає окремий сценарій: перехід із зовнішнього джерела має показати сторінку, а не помилку, і водночас не пустити переказ без повторного підтвердження. Проґавити цю пару легко — на прямому відкритті вкладки обидві кукі на місці, і тест зелений.
Чим захищають ендпоінти, у яких форм немає взагалі?
Кастомним заголовком запиту — і токен для цього не потрібен. Клієнт додає власний заголовок до кожного стано-змінного звернення, а сервер вимагає його наявності; сила заходу не в секретності назви, а в поведінці браузера. Запит із кастомним заголовком перестає бути «простим», тому браузер спершу шле preflight — OPTIONS, який питає в цільового сервера дозволу; такі запити за замовчуванням заборонені, і без явного дозволу атака зупиняється ще до самої дії. Системне формулювання таке: сайт на fetch() чи XMLHttpRequest захищений тим, що його стано-змінні запити ніколи не потрапляють у категорію простих. І перший практичний крок перед вигадуванням свого — перевірити, чи ваш фреймворк або платформа вже не дає CSRF-захисту, яким можна скористатися, і чи ввімкнений він на цьому ендпоінті.
Коли CORS-конфігурація робить API вразливим до CSRF?
Спершу треба тримати окремо дві речі: CORS — про доступ до читання відповіді, CSRF — про саму дію, яка відбувається й тоді, коли відповідь прочитати неможливо. Тому «у нас CORS налаштований» саме собою не є ані захистом, ані вироком. Але дефолтну заборону браузера, на якій тримається захист кастомним заголовком, послаблює саме сервер — для того CORS і існує, — і умова вразливості читається прямо по заголовках відповіді: сайт вразливий до CSRF з певного походження, якщо відповідь на стано-змінний запит містить Access-Control-Allow-Origin із цим походженням і Access-Control-Allow-Credentials. Другий заголовок тут не деталь: кукі в крос-оріджин запитах за замовчуванням не їдуть, і саме він їх вмикає. Типовий дефект конфігурації — дозволяти всі свої піддомени : захопивши один піддомен (з хмарними сервісами це не екзотика), атакувальник обійде політику одного походження й підробить запит разом із вашим кастомним заголовком. Другий типовий недогляд — JSON-API, який приймає text/plain: простий content-type відтворюється звичайною формою, тож заборона простих content-type на сервері теж входить до заходів.
Як ви відтворите CSRF на POST-ендпоінті, а як на JSON-API?
Для класичного POST рецепт трикроковий: зібрати HTML-сторінку з формою, що повторює цільовий запит, розмістити її на сторонньому ресурсі й підвести жертву до кліку (у демонстрації форму зазвичай ще й надсилають скриптом). Аргумент «у нас лише POST, чужа форма нам не страшна» захистом не є: такі запити спокійно автоматизуються через JavaScript. Для JSON-ендпоінта є окремий обхід: у JSON немає параметрів, обовʼязкових для самонадсильної форми, тому enctype міняють на text/plain, щоб пейлоад доїхав як є, а зайве поле-паддинг парсер здебільшого проігнорує. Для стано-змінного GET форма не потрібна взагалі — достатньо тега img на сторонній сторінці: браузер полізе по «картинку», а насправді виконає ; носіями можуть бути й посилання, і XMLHttpRequest, і всі вони приїдуть до сервера як автентичний запит клієнта. І межа, про яку забувають: ціль не мусить дивитися в інтернет — достатньо, щоб адреса була досяжна жертві, тож внутрішня адмінка з цього кола не випадає; обсяг наслідків при цьому обмежений правами конкретного користувача.
Розробник каже: «ми перевіряємо, що сесія валідна, — це і є захист від CSRF». Що відповісте?
Що це не контрзахід узагалі, і це зафіксовано в каталозі слабкостей окремим прикладом хибного коду. Логіка проста: підроблений запит їде через браузер користувача, у якому валідна сесія вже існує, тому перевірка сесії щоразу проходитиме успішно — вона відсіює анонімів, а не чужу ініціативу. Перевірка Referer виглядає розумнішою, але має названу ціну: заголовок можуть не надсилати сам користувач або з міркувань приватності, і тоді контроль ламає легітимну функціональність, тож основним заходом його не роблять. Що справді закриває питання: звірка токена з токеном сесії, SameSite як глибина захисту, для API — і жодного стано-змінного GET. Окремо варто памʼятати про (client-side CSRF), де підроблений запит формує власний JavaScript застосунку: він здатен обійти й токени, і SameSite, тому «токен у розмітці є» і «токен перевірено» — досі різні твердження.
Як перевірити атрибути кукі з автотесту й у чому межа інструмента?
Атрибути читають із : у Playwright це context.cookies(), звідки видно sameSite, secure, httpOnly, domain і path, а на рівні відповіді сервера ті самі значення видно в Set-Cookie. корисний саме як регресійний: він ловить момент, коли після рефакторингу сесійна кукі поїхала з Lax на None або загубила Secure. Але у Selenium читання SameSite доступне не завжди — документація обіцяє його для Chrome 80+, Firefox 79+ і Selenium 4, тож на старішому стенді тест мовчки проходить, нічого не перевіривши, і «зелено» тут означає «перевірки не було». Це загальне правило для перевірок безпеки: спершу переконайтеся, що інструмент справді читає те, на що ви асертите, інакше ви покриваєте пункт лише в звіті. І памʼятайте, що правильний атрибут — не вердикт: перевірка ендпоінта на CSRF закінчується не на кукі, а на тому, чи звіряється токен і чи немає стано-змінних GET.
Три кейси, у яких вердикт про CSRF спирається не на враження: розбір стано-змінного в DevTools (за чим його взагалі перевіряти), таблиця вибору заходу під архітектуру й Playwright-перевірки, що ловлять два різні провали — загублений атрибут кукі та відсутню звірку токена. Скрізь — що дивитися і чому.
Кейс 1. DevTools: чи цей ендпоінт узагалі кандидат на CSRF
Прийшов тікет «перевірити форму зміни пошти на CSRF». Перш ніж писати сторінку-експлойт, ендпоінт треба прогнати через три умови — інакше є години витратити на ручку, до якої байдужа. Відкрийте вкладку Network, виконайте дію руками й клацніть на стано-змінний запит.
Спершу — запит: що сервер приймає як доказ особи.
POST /account/email HTTP/1.1
Host: app.example.com
Cookie: session=eyJhbGciOi...
Content-Type: application/x-www-form-urlencoded
new_email=user%40example.com
Тепер відповідь на логін, де кукі народжується:
HTTP/1.1 200 OK
Set-Cookie: session=eyJhbGciOi...; Path=/; HttpOnly; Secure; SameSite=Lax
Що дивитися і чому:
- У тілі запиту немає жодного непередбачуваного поля. Є
new_email, який задає сам, і більше нічого — третя умова (передбачуваність параметрів) виконана. Якби тут лежало приховане поле з великим випадковим значенням, це був би кандидат у токени, і перевірка пішла б в інший бік. - Єдиний доказ особи —
Cookie: session. Кастомного заголовка, який кладе JS, немає; отже, упізнання спирається лише на те, що браузер підставляє автоматично. Друга умова виконана. Це і є суть відбору: критерій не «форма чи JSON», а «чим сервер упізнає користувача саме в цьому запиті». SameSite=Lax, а методPOST. Чужа форма зPOSTкукі не потягне — але це ще не «CSRF закрито».Laxблокує лише небезпечні методи; якщо ту саму зміну пошти вдасться зробити черезGET, кукі поїде на верхньорівневій навігації. Тому наступний крок — перевірити, чи ендпоінт не приймаєGET.- Усі три умови зійшлися — ендпоінт у списку. Тепер шукаємо захід: чи є в запиті токен і, головне, чи сервер його звіряє. Наявність прихованого поля в розмітці ще нічого не доводить — доводить поведінка сервера на зіпсованому токені (кейс 3).
Кейс 2. Таблиця вибору заходу під архітектуру
Найчастіша плутанина на рев'ю — «поставимо токен» або «поставимо SameSite» без огляду на те, як застосунок улаштований. Захід добирають під клієнта й метод автентифікації; ось як це лягає на типові випадки.
| Архітектура ендпоінта | Основний захід | На чому тримається | Де ламається |
|---|---|---|---|
Застосунок зі станом, є <form> | Синхронізований токен | Звірка токена запиту з токеном сесії | «Токен у розмітці є» без звірки; токен у URL/логах |
| Без стану, серверної сесії нема | Підписаний double submit, привʼязаний до сесії | Політика одного походження + підпис із привʼязкою | Наївна версія — коли атакувальник може писати кукі на домен |
API без тегів <form> (fetch/XHR) | Кастомний заголовок запиту | Дефолтна заборона браузера + CORS-preflight | CORS, що дозволяє походження відправника з Allow-Credentials; приймання text/plain |
| Будь-який із перелічених | SameSite як глибина захисту | Браузер не додає кукі до частини крос-сайтових запитів | Стано-змінний GET; уразливий піддомен; межа по сайту, не походженню |
Що дивитися і чому:
SameSite— окремий рядок, а не альтернатива іншим. Він доповнює токен чи заголовок, а не замінює: сила заяви про нього різна в канонічних джерелах — від «надійного захисту в суворому режимі» до «неповного захисту», тож у звіті його не подають ані як «нічого не дає», ані як «закриває CSRF».- XSS у таблиці немає навмисно. Він знімає будь-який рядок: із виконаного в контексті сайту коду токен читається просто зі сторінки. Тому свобода від XSS — , а не сусідній пункт.
- Перший крок — не свій код, а фреймворк. Перш ніж будувати власну реалізацію токена, перевіряють, чи фреймворк або платформа вже не дає CSRF-захисту, яким можна скористатися, — і чи ввімкнений він на цьому ендпоінті.
Кейс 3. Playwright: атрибут кукі й реальна звірка токена
Два різні провали потребують двох різних перевірок. Перша — : чи не поїхала сесійна кукі з безпечних атрибутів. Друга — змістовна: чи сервер справді звіряє токен, а не лише приймає його наявність.
import { test, expect } from '@playwright/test';
test('сесійна кукі не втратила Secure і не сповзла на SameSite=None', async ({ context, page }) => {
await page.goto('https://app.example.com/login');
// ... виконуємо вхід через UI ...
await page.waitForURL('**/account'); // логін може ставити кукі через кілька редиректів
const cookies = await context.cookies();
const session = cookies.find((c) => c.name === 'session');
expect(session, 'сесійної кукі не видно — перевіряти нічого').toBeTruthy();
// регресія ловить момент, коли рефакторинг зняв Secure або поставив None
expect(session!.secure).toBe(true);
expect(session!.sameSite).toBe('Lax'); // очікуваний контракт цього застосунку
expect(session!.httpOnly).toBe(true);
});
Тепер найважливіше — довести, що приховане поле не декоративне. Беремо валідний токен, псуємо його й чекаємо ; порожній і чужий токен — окремі негативні випадки.
test('сервер відхиляє стано-змінний запит із невалідним CSRF-токеном', async ({ page }) => {
await page.goto('https://app.example.com/account');
// дістаємо токен так само, як його бачить легітимна сторінка
// (приховане поле не має ролі textbox — беремо його селектором за іменем)
const token = await page.locator('input[name="csrf"]').inputValue();
expect(token.length).toBeGreaterThan(16); // велике випадкове значення, не лічильник
// той самий запит, але з одним зіпсованим символом
const tampered = token.slice(0, -1) + (token.endsWith('a') ? 'b' : 'a');
// саме page.request: він бере кукі з контексту браузера, а фікстура request ізольована
const res = await page.request.post('https://app.example.com/account/email', {
form: { new_email: 'attacker@evil.example', csrf: tampered },
});
// якщо тут 200 — звірки з токеном сесії немає, хоч поле у формі й лежить
expect(res.status(), 'зіпсований токен прийнято — це знахідка').toBe(403);
});
Що дивитися і чому:
- на атрибути — регресійний , а не «безпека». Він не доводить, що CSRF закрито; він ловить сповзання
Lax→Noneчи втратуSecureпісля рефакторингу. У Selenium памʼятайте про межу інструмента: читанняSameSiteдоступне з Chrome 80+, Firefox 79+ і Selenium 4 — на старішому стенді зелений асерт означає лише, що перевірки не було. - Відмова на зіпсованому токені — і є доказ звірки. Конкретний код тут — контракт застосунку (у прикладі
403); важливе інше: якщо сервер відповідає200, він перевірив хіба що присутність поля, а захистом є порівняння з токеном сесії, не сам факт передачі значення. Запит обовʼязково йде черезpage.request(абоcontext.request): він підставляє кукі , а окремаrequestмає власне ізольоване сховище — з нею сервер відмовить через відсутню сесію, і тест «пройде» з хибної причини. Порожній токен і токен іншого користувача варто додати окремими випадками — вони ловлять різні дірки в реалізації. - Довжину токена перевіряють не для краси. Коротке чи структуроване значення (час, лічильник, id користувача) повертає передбачуваність параметрів, яку токен мав прибрати, — отже, знову складається валідний запит.
- Перевірка на цьому не закінчується. Зелені обидва тести кажуть «атрибут на місці й токен звіряється», але не кажуть, чи немає стано-змінного
GETв обхід форми й чи не формує запит власний JavaScript застосунку (). Це окремі сценарії, і вердикт «захищено» без них передчасний.
Механіка атаки й відбір ендпоінтів
- Можу пояснити, чому кукі доводить лише «запит вийшов із браузера користувача», але не «користувач цієї дії хотів».
- Памʼятаю три умови вразливості, які мусять збігтися разом: запит змінює стан, упізнання лише за автоматично підставленим (кукі чи HTTP-автентифікація), параметри передбачувані.
- Розумію, чому відсутність готового експлойта вердикту не скасовує: суттєві три , а зручний носій на кшталт
imgлише спрощує експлуатацію. - Розрізняю CSRF і XSS за рівнем доступу і памʼятаю ієрархію: XSS перемагає будь-які помʼякшення CSRF, тож свобода від XSS — передумова захисту.
Анти-CSRF токен
- Можу пояснити, що саме ламає токен — передбачуваність параметрів, а не «підсилює автентифікацію».
- Знаю вимоги до значення: унікальне для сесії, таємне, велике випадкове від надійного генератора, генерує сервер, у синхронізованому патерні не через кукі, не в URL і не в логах.
- Розумію, що нормативна частина синхронізованого патерну — звірка з токеном сесії, а не сама лише присутність поля в запиті.
- Можу за хвилину перевірити наявність звірки: той самий запит із зіпсованим токеном, з порожнім і з чужим — три успіхи означають, що звірки немає.
- Знаю, на чому стоїть double submit () і де ламається наївна версія — коли може писати кукі на домен; робочий варіант підписаний і привʼязаний до сесії.
SameSite
- Можу без запинки сформулювати
Laxповністю: у крос-сайтовому запиті кукі проходить тоді й лише тоді, коли це верхньорівнева навігація і метод безпечний (GETтак,POSTні). - Знаю, що
Strictне пускає кукі навіть на верхньорівневій навігації з чужого сайту, аNoneбезSecureбраузер не прийме взагалі. - Розрізняю кукі без атрибута й кукі з явним
Lax: перша може пройти на верхньорівневомуPOST(режимLax-allowing-unsafe, застосовний лише до кукі без явногоSameSiteй обмежений віком кукі). - Розумію, що межа атрибута — сайт (, eTLD+1, разом зі схемою): один уразливий піддомен знімає захист цілком, а
your-project.github.ioіmy-project.github.io— різні сайти. - Можу назвати щілини поіменно (стано-змінний
GET— найчастіша причина провалу) і не зводжу різну за силою заяву джерел ані до «SameSite не захищає», ані до «SameSite закриває CSRF».
Ендпоінти без форм: заголовки й CORS
- Розумію, чому працює без токена: такий запит перестає бути простим, іде preflight, а він за замовчуванням заборонений.
- Тримаю окремо CORS і CSRF: перше про доступ до читання відповіді, друге про саму дію, яка стається й тоді, коли відповідь прочитати не можна.
- Знаю умову вразливості по парі заголовків відповіді:
Access-Control-Allow-Originіз відправника плюсAccess-Control-Allow-Credentials(саме він вмикає кукі в крос-оріджин запитах).
Відтворення й доказ
- Знаю три способи відтворення:
imgдля стано-змінногоGET, самонадсильна форма дляPOST,enctype: text/plainдля JSON — і чому «у нас лишеPOST» захистом не є. - Памʼятаю, що перевірка «сесія валідна» контрзаходом проти CSRF не є (запит іде через браузер із живою сесією), а перевірка
Refererмає ціну — її можуть не надсилати з міркувань приватності. - Розумію межу інструмента: читання
SameSiteіз тесту доступне не скрізь (Chrome 80+, Firefox 79+, Selenium 4), тож на старішому стенді зелений означає, що перевірки не було.
Квіз
Перед стартом
- Питань: 16
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Що таке міжсайтова підробка запиту (CSRF)?