Як зловмисники змушують користувача виконувати небажані дії: CSRF-атака та захист токенами й SameSite cookies
Уявіть: ви просто відкрили безневинну сторінку в іншій вкладці — і раптом з вашого банківського рахунку зникли кошти. Ніяких помилок, ніяких підозрілих листів. Це не фантастика — це реальна CSRF-атака. Але як це працює і як себе захистити? Відповідь — у двох простих механізмах: CSRF-токенах і SameSite cookies.
⚡ Коротко
- ✅ CSRF — це атака, яка використовує довіру сайту до автентифікованого користувача. Зловмисник змушує браузер відправити запит від вашого імені без вашої згоди.
- ✅ Головний захист — CSRF-токени. Унікальний секретний ключ, який передається з кожною формою або запитом і перевіряється на сервері.
- ✅ SameSite cookies — додатковий шар безпеки. Цей атрибут запобігає відправленню cookies у міжсайтових запитах.
- 🎯 Ви отримаєте: повне розуміння механізму CSRF, приклади атак, практичні способи захисту та відповіді на поширені питання.
- 👇 Детальніше читайте нижче — з прикладами та висновками
Зміст статті:
- 📌 Що таке CSRF і як вона працює?
- 📌 Як захиститися: CSRF-токени та SameSite cookies
- 📌 Переваги, недоліки та помилки розробників
- ❓ Часті питання (FAQ)
- ✅ Висновки
⸻
🎯 Що таке CSRF і як вона працює?
CSRF (Cross-Site Request Forgery, або «міжсайтова підміна запиту») — це тип вебатаки, при якій зловмисник змушує автентифікованого користувача виконати небажану дію на вразливому вебдодатку, використовуючи його вже активну сесію. Атака спирається не на вразливості коду, а на фундаментальну особливість роботи HTTP і cookies: браузер автоматично надсилає аутентифікаційні дані (наприклад, сесійний cookie) з кожним запитом до домену, навіть якщо цей запит ініційований зовнішнім сайтом.
📊 Як відбувається атака?
CSRF-атака реалізується через зловмисний вміст на сторонньому сайті, який використовує поведінку браузера щодо автоматичного надсилання аутентифікаційних даних. Розглянемо детальний сценарій:
1. Користувач увійшов до свого облікового запису в інтернет-банківському сервісі https://bank.example.com. Після успішної автентифікації сервер встановлює сесійний cookie (наприклад, sessionid=abc123) з атрибутами HttpOnly і Secure. Цей cookie зберігається в браузері і автоматично додається до кожного наступного HTTP-запиту, спрямованого на домен bank.example.com.
2. Пізніше користувач відкриває інший сайт — наприклад, публічний форум або новинний ресурс. На цій сторінці зловмисник розмістив прихований HTML-елемент або JavaScript-код, який ініціює запит до банку. Наприклад:
<img src="https://bank.example.com/transfer?to=ATTACKER_ACCOUNT&amount=1000" width="0" height="0" />або (у випадку POST-запиту):
<form action="https://bank.example.com/transfer" method="POST" id="csrf-form"><input type="hidden" name="to" value="ATTACKER_ACCOUNT" />
<input type="hidden" name="amount" value="1000" />
</form>
<script>document.getElementById('csrf-form').submit();</script>
3. Браузер, дотримуючись специфікації HTTP, автоматично додає cookie sessionid=abc123 до цього запиту, оскільки він спрямований на bank.example.com. Сервер банку отримує запит, бачить дійсну сесію і виконує операцію переказу коштів, вважаючи, що дія ініційована самим користувачем.
4. Користувач навіть не помічає атаки — жодних сповіщень, перенаправлень або помилок. Гроші вже переказані.
- ✅ Ключовий момент: атака працює лише якщо користувач має активну сесію на цільовому сайті. Якщо він не авторизований — запит буде відхилений.
- ✅ Зловмиснику не потрібен доступ до cookie або пароля: він не читає cookie, а лише «підштовхує» браузер до відправлення запиту, який автоматично включає ці дані.
- ✅ GET-запити — найбільш небезпечний вектор: якщо додаток дозволяє змінювати стан через GET (що суперечить принципам REST), атака може бути реалізована через просте зображення, посилання або iframe. Саме тому RFC 7231 чітко вказує: «GET-запити повинні бути ідемпотентними і не змінювати стан сервера».
- ✅ POST не гарантує безпеку: хоча POST-запити не можна ініціювати через
<img>, їх легко відправити через автоматично відправлену форму або JavaScript (навіть без AJAX, завдяки автоматичному сабміту).
👉 Реальний кейс: у 2008 році дослідник безпеки виявив CSRF-вразливість у Gmail. Зловмисник міг розмістити на своєму сайті зображення з URL виду https://mail.google.com/mail/?view=cm&tf=0&[email protected], і якщо жертва була авторизована в Gmail, лист автоматично відправлявся з її облікового запису. Інша вразливість дозволяла додавати фільтри переспрямування — усі нові листи пересилалися зловмиснику.
⚡ Важливо: через політику CORS (Cross-Origin Resource Sharing) зловмисник не може прочитати відповідь сервера (наприклад, HTML-сторінку підтвердження переказу), але йому це й не потрібно. Успіх атаки залежить лише від того, чи був запит виконаний — а не від того, чи отримано відповідь.
✅ Швидкий висновок: CSRF експлуатує базову модель довіри між браузером і вебдодатком: якщо запит містить дійсні аутентифікаційні дані, він вважається легітимним. Без додаткових механізмів перевірки походження запиту (наприклад, CSRF-токенів) сервер не може відрізнити «добровільний» запит від підміненого.
⸻
🔬 Як захиститися: CSRF-токени та SameSite cookies
На щастя, існують надійні, перевірені методи захисту від CSRF-атак. Найефективніший підхід — поєднання двох механізмів: CSRF-токенів (на рівні сервера) та SameSite cookies (на рівні браузера). Це так званий «захист у глибину»: навіть якщо один шар виявиться неефективним (наприклад, через старий браузер), інший все одно зупинить атаку.
🛡️ CSRF-токени: як вони працюють?
CSRF-токен — це криптографічно стійкий, унікальний, випадково згенерований рядок, який сервер пов’язує з поточною сесією користувача. Він передається клієнту (наприклад, у прихованому полі форми або у метаданих API-відповіді) і має бути повернутий у кожному запиті, що змінює стан (POST, PUT, DELETE тощо).
Процес виглядає так:
- Користувач запитує захищену форму (наприклад, «Змінити пароль»).
- Сервер генерує унікальний токен (наприклад,
a1b2c3d4e5f6...), зберігає його в сесії та включає в HTML-форму як приховане поле:<input type="hidden" name="csrf_token" value="a1b2c3d4e5f6..." /> - Коли користувач надсилає форму, браузер включає цей токен у тіло запиту.
- Сервер отримує запит, порівнює переданий токен із тим, що зберігається в сесії. Якщо значення не співпадають або токен відсутній — запит відхиляється з помилкою
403 Forbidden.
Чому це працює? Завдяки політиці однакового походження (Same-Origin Policy), зловмисний сайт не може прочитати відповідь від bank.example.com, а отже — не може отримати CSRF-токен. Без нього будь-який підставний запит буде відхилений, навіть якщо він містить дійсний сесійний cookie.
Важливо: токен має бути унікальним для кожної сесії (або навіть для кожного запиту в критичних системах), непередбачуваним (генерованим криптографічно безпечним генератором випадкових чисел) і прив’язаним до користувача. Зберігати його у localStorage або в URL — погана практика, оскільки це робить його вразливим до XSS.
🍪 SameSite cookies: сучасний захист на рівні браузера
Атрибут SameSite — це механізм, введений у специфікацію cookies для запобігання міжсайтового використання аутентифікаційних cookies. Він явно вказує браузеру, у яких випадках cookie може бути надіслано разом із запитом.
Існує три значення:
SameSite=Strict— cookie надсилається лише у «першоджерельних» запитах, тобто коли URL у адресному рядку збігається з доменом cookie. Навіть перехід за посиланням з іншого сайту не включає такий cookie. Це максимальний рівень захисту, але може погіршити UX (користувач «випадає» з сесії після переходу з Google).SameSite=Lax(рекомендовано за замовчуванням) — дозволяє cookie у «безпечних» міжсайтових запитах: тільки методомGETі лише при переході за звичайним посиланням (наприклад, з пошукової системи). Запити типуPOST,PUT, або ті, що ініційовані через<img>,<form>або JavaScript з іншого сайту — не містять такого cookie. Це ідеальний баланс між безпекою та зручністю.SameSite=None— cookie надсилається завжди, навіть у міжсайтових запитах. Але з 2020 року браузери вимагають, щоб такі cookies мали також атрибутSecure(тобто працювали лише через HTTPS). Використовується для легітимних крос-доменних сценаріїв (наприклад, вбудовані віджети).
Приклад установки cookie з SameSite у HTTP-заголовку:
Set-Cookie: sessionid=abc123; Path=/; Secure; HttpOnly; SameSite=LaxЗ 2020 року Chrome, Firefox, Safari та інші сучасні браузери встановлюють SameSite=Lax за замовчуванням для усіх cookies, якщо атрибут не вказано явно. Однак покладатися лише на це небезпечно — особливо якщо ваші користувачі використовують старіші версії браузерів або мобільні додатки.
📈 Порівняння методів захисту
| Критерій | CSRF-токени | SameSite cookies |
|---|---|---|
| Рівень захисту | Високий — працює навіть при наявності XSS (якщо токен не витік) | Середній–високий — ефективний проти класичних CSRF, але не захищає від «збережених» атак (stored CSRF) у межах того самого сайту |
| Підтримка браузерами | Повна — не залежить від браузера, оскільки реалізується на сервері | Повна з 2020 року; часткова або відсутня в IE та старих версіях мобільних браузерів |
| Складність реалізації | Середня — потрібно інтегрувати в усі форми та state-changing API-ендпоінти | Низька — достатньо додати атрибут при встановленні cookie |
| Вразливість до обхідних шляхів | Може бути обійдений через XSS (якщо токен доступний через DOM) | Не захищає від атак, ініційованих з того самого сайту (наприклад, через збережений XSS) |
✅ Швидкий висновок: найкраща стратегія — використовувати обидва методи разом. CSRF-токени забезпечують надійний, незалежний від браузера захист, а SameSite cookies додають додатковий шар безпеки на сучасних пристроях і зменшують поверхню атаки навіть у разі помилок у реалізації токенів.
Посилання на іншу статтю: OWASP CSRF Guide.
⸻
💡 Переваги, недоліки та помилки розробників
Навіть знаючи про CSRF, розробники часто роблять помилки, які роблять захист марним.
✅ Переваги правильної реалізації
- ✅ Повна імунізація від класичних CSRF-атак
- ✅ Простота тестування (автоматизовані сканери легко виявляють відсутність токенів)
- ✅ Сумісність із сучасними фреймворками (Django, Spring Security, Laravel тощо мають вбудовану підтримку)
❌ Недоліки та пастки
- ❌ Неправильне зберігання токена (наприклад, у localStorage замість сесії) робить його вразливим до XSS
- ❌ Використання одного токена на всю сесію замість унікального на кожен запит знижує безпеку
- ❌ Ігнорування SameSite призводить до вразливостей у старих версіях iOS Safari та інших нішевих браузерів
💡 Порада експерта: завжди використовуйте POST, PUT або DELETE для операцій, що змінюють стан. Ніколи не дозволяйте змінювати дані через GET-запити — це порушує принципи REST і відкриває шлях для CSRF.
⸻
❓ Часті питання (FAQ)
🔍 Чи захищає HTTPS від CSRF?
Ні. HTTPS забезпечує конфіденційність і цілісність даних у транзиті, але не запобігає тому, що браузер автоматично надсилає аутентифікаційні cookies разом із запитами. CSRF-атака працює поверх HTTPS — зловмисник не перехоплює трафік, а лише «підштовхує» жертву до відправлення запиту, який виглядає легітимним для сервера.
🔍 Чи допомагає автентифікація через токени (JWT) уникнути CSRF?
Так, але лише за умови, що JWT не зберігається в cookies. Якщо токен передається вручну через заголовок Authorization: Bearer <token> (наприклад, зберігаючись у localStorage), браузер не додає його автоматично до міжсайтових запитів — отже, CSRF неможлива. Однак такий підхід робить додаток вразливим до XSS. Тому сучасні додатки часто використовують гібрид: аутентифікаційний токен у HTTP-only cookie (захищений від XSS) + CSRF-токен у заголовку або тілі запиту (захищений від CSRF).
🔍 Чи потрібні CSRF-токени для API?
Це залежить від способу автентифікації. Якщо ваш API використовує сесійні cookies (наприклад, через connect.sid у Express або JSESSIONID у Java), то CSRF-захист обов’язковий. Якщо ж API використовує токени у заголовку Authorization (Bearer/JWT) і не покладається на cookies — CSRF не загрожує, оскільки браузер не включає такі токени у міжсайтові запити.
🔍 Чи захищає SameSite=Lax від усіх CSRF-атак?
Не повністю. SameSite=Lax ефективно блокує міжсайтові POST-запити та інші «небезпечні» методи, але дозволяє GET-запити при переході за посиланням. Якщо ваш додаток дозволяє змінювати стан через GET (що є порушенням REST), атака все ще можлива. Крім того, SameSite не захищає від «збережених» CSRF-атак (stored CSRF), коли шкідливий код розміщено безпосередньо на вашому сайті (наприклад, через XSS або небезпечне поле вводу).
🔍 Чи можна використовувати заголовок Referer для захисту від CSRF?
OWASP не рекомендує покладатися на перевірку заголовка Referer. Хоча він іноді містить походження запиту, його легко відключити (через налаштування приватності в браузері) або підробити у певних умовах. Крім того, деякі корпоративні мережі або мобільні оператори фільтрують цей заголовок. Це призводить до помилкових відхилень легітимних запитів і створює хибне відчуття безпеки.
🔍 Чи достатньо використовувати лише CSRF-токени без SameSite?
Так, CSRF-токени самі по собі забезпечують надійний захист, якщо реалізовані правильно: унікальні, непередбачувані, прив’язані до сесії та перевіряються на кожному state-changing запиті. Однак SameSite cookies додають додатковий «пасивний» шар безпеки, який працює навіть у разі людської помилки (наприклад, забули додати токен до нової форми). Тому рекомендується використовувати обидва механізми разом — це принцип «захисту в глибину».
🔍 Що таке login CSRF і чим він небезпечний?
Login CSRF — це особливий випадок атаки, коли зловмисник змушує неавторизованого користувача увійти в обліковий запис, який контролює сам зловмисник. Наприклад, користувач думає, що увійшов у свій власний акаунт, але насправді опиняється в чужому. Потім він може додати особисті дані (наприклад, платіжну картку), які потраплять прямо до зловмисника. Для захисту від login CSRF використовують токени навіть на сторінці входу або прив’язують сесію до IP-адреси/фінгерпринту пристрою.
⸻
✅ Висновки
Підведемо підсумки, ґрунтуючись на принципах безпеки, рекомендаціях OWASP та реальних інцидентах:
- 🎯 CSRF — це не гіпотетична, а практична загроза для будь-якого вебдодатку, що використовує автоматичну автентифікацію через cookies. Атака експлуатує фундаментальну особливість HTTP: браузер автоматично надсилає аутентифікаційні дані з кожним запитом до домену, навіть якщо запит ініційовано зовні. Це дозволяє зловмиснику виконувати дії від імені жертви — від зміни email або пароля до переказу коштів або повного захоплення облікового запису адміністратора.
- 🎯 CSRF-токени залишаються найефективнішим, універсальним і перевіреним часом механізмом захисту. Вони працюють на рівні сервера, не залежать від браузера і надійно запобігають підміні запитів. Ключові вимоги до токенів: криптографічна випадковість, унікальність на сесію (або навіть на запит), прив’язка до користувача та обов’язкова перевірка на кожному state-changing ендпоінті (POST, PUT, DELETE тощо).
- 🎯 Атрибут SameSite (значення Lax або Strict) — обов’язковий для всіх сесійних cookies. Він забезпечує «пасивний» захист на рівні браузера, блокуючи автоматичне надсилання cookies у міжсайтових запитах. Хоча SameSite не замінює CSRF-токени (особливо в старих браузерах), він значно зменшує поверхню атаки і служить важливим додатковим шаром безпеки в рамках стратегії «захисту в глибину».
- 💡 Рекомендація: негайно проведіть аудит усіх форм, API-ендпоінтів і механізмів автентифікації у вашому проекті. Якщо ви використовуєте cookies для управління сесією — додайте CSRF-токени до всіх операцій, що змінюють стан, і встановіть
SameSite=Lax(абоStrict, якщо це не погіршує UX) для всіх аутентифікаційних cookies. Більшість сучасних фреймворків (Spring Security, Django, Laravel, Rails тощо) мають вбудовану підтримку обох механізмів — скористайтеся нею.
💯 Підсумок: CSRF — це одна з найстаріших, але й досі найпоширеніших вебвразливостей, що входить до списку OWASP Top 10. Вона не вимагає складних технік: достатньо одного незахищеного ендпоінта, щоб зловмисник міг змусити користувача виконати небажану дію. Однак захист від неї простий, стандартизований і ефективний. Дотримання двох правил — використання CSRF-токенів і налаштування SameSite cookies — практично повністю усуває ризик, навіть у складних розподілених системах.
Цю статтю підготував засновник і лідер компанії з 8-річним досвідом у веброзробці — Вадім Харов'юк.