Skip to main content
Практика завдань

Що таке CSRF і як його запобігти?

CSRF (Cross-Site Request Forgery) - це вразливість веб-безпеки, яка дозволяє зловмисникам обманом змусити користувачів виконувати небажані дії на сайті, де вони автентифіковані.

Як працює CSRF

Сценарій атаки

  1. Користувач логінитьсяна bank.com (отримує session cookie)
  2. Користувач відвідує зловмисний сайт evil.com (залишаючись залогіненим на bank.com)
  3. evil.com відправляє підроблений запит на bank.com
  4. Браузер автоматично включає cookies з bank.com
  5. Банк обробляє запит, ніби користувач ініціював його

Приклад атаки

Вразливий переказ коштів:

html
<!-- На evil.com --> <img src="https://bank.com/transfer?to=attacker&amount=1000" /> <!-- або --> <form action="https://bank.com/transfer" method="POST"> <input type="hidden" name="to" value="attacker" /> <input type="hidden" name="amount" value="1000" /> </form> <script> document.forms[0].submit(); </script>

Коли користувач завантажує цю сторінку, запит автоматично відправляється з їхніми cookies з bank.com!

Методи запобігання

1. CSRF токени (найпоширеніше)

Генерування унікального токена для кожної сесії/запиту:

Backend (Node.js/Express):

javascript
const csrf = require('csurf'); const csrfProtection = csrf({ cookie: true }); app.get('/form', csrfProtection, (req, res) => { // Генеруємо CSRF токен res.render('form', { csrfToken: req.csrfToken() }); }); app.post('/transfer', csrfProtection, (req, res) => { // Токен автоматично валідується if (valid) { processTransfer(req.body); } });

Frontend:

html
<form action="/transfer" method="POST"> <input type="hidden" name="_csrf" value="{{ csrfToken }}" /> <input name="to" /> <input name="amount" /> <button>Перевести</button> </form>

З React/Fetch:

javascript
function Transfer() { const [csrfToken, setCsrfToken] = useState(''); useEffect(() => { fetch('/api/csrf-token') .then(r => r.json()) .then(data => setCsrfToken(data.token)); }, []); const handleTransfer = async () => { await fetch('/api/transfer', { method: 'POST', headers: { 'Content-Type': 'application/json', 'CSRF-Token': csrfToken // Кастомний заголовок }, body: JSON.stringify({ to, amount }) }); }; return <button onClick={handleTransfer}>Перевести</button>; }

2. SameSite Cookies

Запобігання відправці cookies браузером з cross-site запитами:

javascript
// Node.js/Express res.cookie('sessionId', sessionId, { httpOnly: true, secure: true, sameSite: 'strict' // або 'lax' });

Значення SameSite:

  • strict: Cookie ніколи не відправляється на cross-site запити
  • lax: Cookie відправляється на top-level навігацію (лише GET)
  • none: Cookie відправляється на всі запити (потребує secure)

3. Кастомні заголовки запитів

Вимагати кастомні заголовки, які не можуть бути встановлені формами:

javascript
// Frontend fetch('/api/transfer', { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); // Backend app.post('/api/transfer', (req, res) => { if (req.headers['x-requested-with'] !== 'XMLHttpRequest') { return res.status(403).send('Заборонено'); } // Обробка переказу });

4. Перевірка Origin/Referer заголовків

javascript
app.post('/api/transfer', (req, res) => { const origin = req.headers.origin || req.headers.referer; if (!origin || !origin.startsWith('https://bank.com')) { return res.status(403).send('Невірний origin'); } // Обробка переказу });

5. Повторна автентифікація для чутливих дій

javascript
app.post('/api/delete-account', async (req, res) => { // Вимагаємо підтвердження паролем const user = await User.findById(req.userId); if (!await user.checkPassword(req.body.password)) { return res.status(401).send('Невірний пароль'); } // Видаляємо акаунт });

Реальний приклад: повний захист

javascript
// Express.js з множинними захистами const express = require('express'); const csrf = require('csurf'); const cookieParser = require('cookie-parser'); const app = express(); // 1. SameSite cookies app.use(cookieParser()); app.use((req, res, next) => { res.cookie('session', req.sessionID, { httpOnly: true, secure: true, sameSite: 'strict' }); next(); }); // 2. CSRF токени const csrfProtection = csrf({ cookie: true }); // 3. Перевірка origin const verifyOrigin = (req, res, next) => { const origin = req.headers.origin; const allowedOrigins = ['https://myapp.com']; if (origin && !allowedOrigins.includes(origin)) { return res.status(403).send('Заборонено'); } next(); }; // Захищений роут з множинними захистами app.post('/api/transfer', verifyOrigin, csrfProtection, (req, res) => { // Обробка переказу } );

Тестування на вразливості CSRF

html
<!-- Тестова сторінка для перевірки вразливості сайту --> <!DOCTYPE html> <html> <body> <h1>CSRF тест</h1> <form id="testForm" action="https://target-site.com/api/action" method="POST"> <input type="hidden" name="data" value="malicious" /> </form> <script> // Авто-відправка при завантаженні сторінки document.getElementById('testForm').submit(); </script> </body> </html>

Якщо дія виконується без додаткової перевірки, сайт вразливий!

Типові питання на співбесіді

  1. П: Яка різниця між CSRF і XSS? В: CSRF експлуатує автентифікацію користувача для виконання дій. XSS впроваджує зловмисні скрипти. CSRF використовує користувача як "заплутаного заступника", XSS атакує користувача напряму.

  2. П: Чому зловмисники не можуть прочитати CSRF токен зі сторінки? В: Через Same-Origin Policy, JavaScript з evil.com не може читати вміст з bank.com.

  3. П: Чи достатньо HTTPS для запобігання CSRF? В: Ні! HTTPS запобігає MITM атакам, але не зупиняє CSRF. Вам все одно потрібні CSRF токени або інші захисти.

  4. П: Чи можуть GET запити мати вразливості CSRF? В: Так! Якщо GET запити змінюють стан (видалення, переказ тощо), вони вразливі. Завжди використовуйте POST/PUT/DELETE для зміни стану.

Найкращі практики

РОБІТЬ:

  • Використовуйте CSRF токени для операцій зміни стану
  • Встановлюйте SameSite=Strict для session cookies
  • Перевіряйте Origin/Referer заголовки
  • Використовуйте POST/PUT/DELETE для зміни стану (ніколи GET)
  • Вимагайте повторну автентифікацію для чутливих дій

НЕ РОБІТЬ:

  • Покладайтеся лише на HTTPS
  • Використовуйте GET для операцій зміни стану
  • Довіряйте лише cookies для автентифікації
  • Забувайте валідувати токени на кожному запиті
  • Зберігайте CSRF токени в cookies (використовуйте приховані поля або кастомні заголовки)

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань