Атаки та їх запобігання, які має знати кожен веброзробник
Безпека майже завжди ламається на стиках: валідація є, але не на межі; авторизація реалізована, але один ендпойнт її пропустив; екранування працює, але в одному шаблоні вивели raw HTML. Добра новина: більшість реальних інцидентів — це повторювані класи помилок, які можна закривати системно.
Пов’язані матеріали: Спостережуваність і моніторинг · Бази даних під навантаженням
Зміст
- Модель загроз: що захищаємо і від кого
- Ін’єкції: SQLi, command injection, template injection
- XSS: відбитий, збережений, DOM-based
- CSRF: чому «це ж GET» не є захистом
- Автентифікація та сесії: крадіжка токенів, фіксація, cookies
- Контроль доступу й IDOR: «це ж просто id»
- Завантаження файлів і шлях: upload, path traversal, RCE поруч
- SSRF: коли сервер ходить «куди не треба»
- Небезпечна десеріалізація та підміна об’єктів
- Браузерні захисти: clickjacking, CORS, заголовки
- DoS та abuse: rate limit, брутфорс, дорогі операції
- Чеклист для рев’ю та релізу
Модель загроз: що захищаємо і від кого
Перед технікою — коротка рамка:
- Атакувальник: анонімний користувач, користувач з акаунтом, партнер з API-ключем, «свій» у VPN, актор з доступом до CI/CD.
- Активи: гроші, персональні дані, акаунти, адмінка, інтеграції, секрети, доступ до внутрішньої мережі.
- Поверхня атаки: форми й API, редіректи, webhooks, імпорт/експорт, завантаження файлів, інтеграції (S3, SMTP, платежі).
Практичне правило: будь-який ввід недовірений (включно з заголовками, cookies, webhook payload, параметрами URL, повідомленнями з черги).
Ін’єкції: SQLi, command injection, template injection
SQL Injection (SQLi)
SQLi майже завжди починається з «лише один сирий шматок SQL». Захист — не «екранувати», а параметризувати.
Погано (конкатенація):
$rows = DB::select("SELECT * FROM users WHERE email = '{$email}'");
Краще (плейсхолдери):
$rows = DB::select('SELECT * FROM users WHERE email = ?', [$email]);
Окремий клас проблем — динамічні імена колонок / сортування. Параметри не підставляють ідентифікатори, тому робіть allowlist:
$allowed = ['created_at', 'email', 'id'];
$sort = in_array($request->get('sort'), $allowed, true) ? $request->get('sort') : 'created_at';
$users = User::query()->orderBy($sort, 'desc')->paginate();
Command injection
Якщо застосунок викликає shell, правило таке: не пропускати користувацький ввід у командний рядок. Навіть escapeshellarg() — це крайній бар’єр, а не дизайн.
Якщо потрібно запускати CLI (конвертація файлів, прев’ю):
- краще бібліотека, ніж
exec(); - якщо CLI неминучий — фіксуйте бінарник/аргументи/директорії та запускайте в обмеженому середовищі (контейнер/черга/воркер під окремим користувачем).
Template injection
Найнебезпечніший варіант — коли користувач задає «шаблон», який потім виконується як Blade/Twig/Smarty.
Правило: користувацький шаблон — лише як дані, максимум через обмежений формат (наприклад, Markdown з allowlist і безпечним рендером).
XSS: відбитий, збережений, DOM-based
XSS — це виконання JS у контексті вашого домену. Типові джерела:
- raw-рендер (
{!! ... !!}у Blade,innerHTMLна фронті); - HTML у профілі/коментарях без санітизації;
- вставка даних у
<script>/атрибути без правильного кодування.
Базові захисти
- Екранування за замовчуванням: у Blade використовуйте
{{ $value }}. - Розділяйте контексти: HTML, атрибути, JS-рядки, URL — різні правила.
- Санітизувати HTML-ввід allowlist’ом (теги/атрибути) та прибирати
on*,javascript:тощо. - CSP як обмежувач шкоди.
Стартовий CSP (ідеально — з nonces і без 'unsafe-inline'):
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
CSRF: чому «це ж GET» не є захистом
CSRF — коли браузер користувача «сам» відправляє запит на ваш сайт із його cookies/сесією, бо інший сайт це тригерить (форма, зображення, JS).
Захист
- CSRF-токени для state-changing запитів (POST/PUT/PATCH/DELETE) — Laravel має це з коробки.
SameSitecookies: мінімумLax; для чутливих сценаріїв —Strict; для кроссайтових —None; Secure.- Перевірка Origin/Referer для критичних дій (зміна email, виплати).
- Не міняти стан через GET.
Автентифікація та сесії: крадіжка токенів, фіксація, cookies
Що найчастіше ламають:
- слабкі паролі + відсутній rate limiting → credential stuffing;
- крадіжка сесії через XSS; фіксація сесії; відсутня інвалідація;
- довгоживучі токени без ротації та прив’язки до пристрою.
Базовий набір:
- Rate limit на логін/OTP/скидання пароля.
- Хешування паролів через
password_hash()(bcrypt/argon2). - Cookies сесії:
HttpOnly,Secure,SameSite, по можливості короткий TTL. - Ротація session ID після логіна та підвищення прав.
Контроль доступу й IDOR: «це ж просто id»
IDOR — коли користувач підбирає/перебирає ідентифікатори й читає або змінює чужі ресурси.
Захист:
- Policies/gates на кожен ресурс і дію.
- У multi-tenant — фільтрація за
tenant_id/account_idна рівні запитів. - Не покладатися на «сховати кнопку» як на безпеку.
- Аудит-логи для чутливих дій.
Ідея: замість Order::findOrFail($id) — auth()->user()->orders()->findOrFail($id).
Завантаження файлів і шлях: upload, path traversal, RCE поруч
Завантаження — улюблене місце для RCE «поруч із» застосунком.
- перевіряйте вміст, а не лише розширення; MIME від браузера — недовірений;
- ліміти розміру/кількості;
- зберігання поза web-root, роздача через контролер або окремий домен/CDN;
- випадкові імена файлів; не використовувати оригінальне ім’я як шлях;
- заборонити виконання в директорії upload на рівні вебсервера.
Path traversal часто з’являється в «скачай файл за ім’ям»: відкидайте ../, \, нуль-байти й використовуйте allowlist реальних ID.
SSRF: коли сервер ходить «куди не треба»
SSRF виникає, коли ви берете URL від користувача і робите запит від імені сервера (імпорт аватарки, link preview, webhook tester).
Захист:
- allowlist доменів/хостів;
- блок приватних IP-діапазонів (127.0.0.0/8, 10.0.0.0/8, 169.254.0.0/16, 172.16/12, 192.168/16, ::1, fc00::/7);
- заборона редіректів або перевірка кожного хопа;
- таймаути, ліміти розміру відповіді, тільки https.
Небезпечна десеріалізація та підміна об’єктів
Якщо ви десеріалізуєте підроблювані дані (cookies, hidden fields, зовнішні payload з черги), ризикуєте підміною полів/ролей і, в деяких екосистемах, gadget chain.
Правило: не зберігати серіалізовані об’єкти в недовірених місцях. Для токенів — підпис (HMAC), для даних — JSON + схема + серверна валідація.
Браузерні захисти: clickjacking, CORS, заголовки
Clickjacking:
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
CORS — це політика читання відповіді в браузері. Уникайте * разом із credentials і тримайте строгий allowlist Origin.
Базові заголовки:
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
DoS та abuse: rate limit, брутфорс, дорогі операції
Найчастіше ламають «дешево для атакера — дорого для вас»: пошук без індексів, експорти, PDF, логін/OTP без rate limit.
Міри:
- rate limiting по IP + акаунту + ключу;
- черги для дорогих операцій;
- таймаути й ліміти розміру/глибини JSON;
- кешування важких публічних відповідей (з урахуванням персоналізації).
Чеклист для рев’ю та релізу
- [ ] Валідація вводу на межі (форми/API/webhooks), нормалізація типів.
- [ ] Allowlist для сортувань/фільтрів/ідентифікаторів.
- [ ] Екранування за замовчуванням, немає необґрунтованого raw HTML.
- [ ] Авторизація на сервері для кожної дії; немає IDOR.
- [ ] SSRF-захисти (allowlist, приватні IP, таймаути).
- [ ] Uploads: перевірки, зберігання поза web-root, заборона виконання.
Підсумок
Ключовий принцип: безпека — це інваріанти на межі. Один раз правильно перевіряйте ввід, екрануйте вивід за замовчуванням, перевіряйте доступ на сервері, підписуйте зовнішні інтеграції та обмежуйте частоту всього дорогого.