---
title: 'Веб-атаки та захист: XSS, CSRF, SQLi, SSRF, IDOR і завантаження файлів | DevSense'
description: 'Атаки, які найчастіше трапляються у вебзастосунках: ін’єкції (SQL/command), XSS, CSRF, IDOR та помилки контролю доступу, SSRF, небезпечні завантаження файлів, clickjacking і конфігураційні пастки. Практичні захисти, заголовки та чеклисти.'
faq:
    - { question: 'Чому очищення HTML-вводу недостатньо для запобігання SQL-ін’єкціям?', answer: 'SQL-ін’єкція відбувається, коли недовірені дані об’єднуються з тілом SQL-запиту. Очищення HTML видаляє теги на кшталт `<script>`, але не заважає лапкам або крапкам з комою змінити структуру SQL-запиту. Єдине надійне рішення — використовувати параметризовані запити (підготовлені вирази).' }
    - { question: 'Чи можуть SameSite-куки повністю замінити CSRF-токени?', answer: "Хоча налаштування `SameSite=Lax` або `Strict` захищають від міжсайтових запитів у сучасних браузерах, вони не дають абсолютної гарантії. Старі браузери можуть не підтримувати SameSite, а баги або зміна стану через GET-запити можуть обійти цей захист. CSRF-токени залишаються обов'язковим елементом ешелонованої оборони." }
    - { question: 'Чим відрізняється SSRF від CSRF?', answer: 'CSRF змушує браузер користувача надсилати небажані запити в застосунок, де користувач авторизований. SSRF змушує *сервер* застосунку надсилати несанкціоновані запити до внутрішніх ресурсів (наприклад, до хмарних ендпоінтів метаданих або внутрішнього Redis), які недоступні з зовнішнього інтернету.' }
    - { question: 'Чому перевірка тільки розширення файлу при завантаженні є небезпечною?', answer: 'Атакувальники можуть легко обійти перевірку розширення (наприклад, використовуючи подвійні розширення або перейменування) або експлуатувати вразливості, за яких сервер виконує код із файлу з коректним розширенням (наприклад, PHP-код, вбудований у PNG-зображення). Необхідно перевіряти реальний тип вмісту (MIME-тип) та зберігати файли поза веб-коренем.' }
published: '2026-05-31'
---
# Веб-атаки та методи захисту, які має знати кожен розробник

Ваш застосунок працює стабільно, трафік зростає, а останній реліз пройшов без жодного збою. Але ось один некоректний запит від неавторизованого користувача стирає таблицю бази даних, або внутрішній мікросервіс зливає персональні дані клієнтів у мережу через уразливий HTTP-клієнт. Безпека майже завжди ламається **на стиках**: валідація є, але не на межі; авторизація реалізована, але один ендпойнт її пропустив; екранування працює, але в одному шаблоні вивели raw HTML. Добра новина: більшість реальних інцидентів — це повторювані класи помилок, які можна закривати системно.

**Пов’язані матеріали:** [Спостережуваність і моніторинг](observability-monitoring-laravel) · [Бази даних під навантаженням](database-performance-and-scaling)

## Зміст

* [Модель загроз: що захищаємо і від кого](#threat-model)
* [Ін’єкції: SQLi, command injection, template injection](#injection)
* [XSS: відбитий, збережений, DOM-based](#xss)
* [CSRF: чому «це ж GET» не є захистом](#csrf)
* [Автентифікація та сесії: крадіжка токенів, фіксація, cookies](#auth-sessions)
* [Контроль доступу й IDOR: «це ж просто id»](#idor)
* [Завантаження файлів і шлях: upload, path traversal, RCE поруч](#uploads)
* [SSRF: коли сервер ходить «куди не треба»](#ssrf)
* [Небезпечна десеріалізація та підміна об’єктів](#deserialization)
* [Браузерні захисти: clickjacking, CORS, заголовки](#browser)
* [DoS та abuse: rate limit, брутфорс, дорогі операції](#dos)
* [Часті помилки](#common-mistakes)
* [Чеклист для рев’ю та релізу](#checklist)
* [Квіз для самоперевірки](#self-test-quiz)

---

<a id="threat-model"></a>
## Модель загроз: що захищаємо і від кого

Перед написанням захисної логіки важливо зафіксувати параметри загроз:

- **Атакувальник**: анонімний користувач, користувач з акаунтом, партнер з API-ключем, «свій» у VPN, актор з доступом до CI/CD.
- **Активи**: гроші, персональні дані, акаунти, адмінка, інтеграції, секрети, доступ до внутрішньої мережі.
- **Поверхня атаки**: форми й API, редіректи, webhooks, імпорт/експорт, завантаження файлів, інтеграції (S3, SMTP, платежі).

> [!NOTE]
> **Золоте правило безпеки**
> Завжди припускайте, що будь-який користувацький ввід є шкідливим. Це стосується заголовків, cookies, webhook-payload, параметрів URL та повідомлень, отриманих із черг.

---

<a id="injection"></a>
## Ін’єкції: SQLi, command injection, template injection

### SQL Injection (SQLi)

SQLi майже завжди починається з «лише один сирий шматок SQL». Захист — не «екранувати», а **параметризувати**.

**Погано** (конкатенація):
```php
// app/Http/Controllers/UserController.php
$rows = DB::select("SELECT * FROM users WHERE email = '{$email}'");
```

**Краще** (плейсхолдери):
```php
// app/Http/Controllers/UserController.php
$rows = DB::select('SELECT * FROM users WHERE email = ?', [$email]);
```

Окремий клас проблем — **динамічні імена колонок / сортування**. Параметри не підставляють ідентифікатори SQL, тому робіть строгий allowlist:
```php
// app/Http/Controllers/UserController.php
$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.
- **Правило**: Користувацький шаблон — лише як дані, максимум через обмежений, не Turing-complete формат (наприклад, Markdown з allowlist і безпечним рендером).

---

<a id="xss"></a>
## XSS: відбитий, збережений, DOM-based

XSS — це виконання JS у контексті вашого домену. Типові джерела:
- raw-рендер (`{!! ... !!}` у Blade, `innerHTML` на фронті).
- HTML у профілі/коментарях без санітизації.
- вставка даних у `<script>`/атрибути без правильного кодування.

### Базові захисти

- **Екранування за замовчуванням**: у Blade використовуйте `{{ $value }}`. Не використовуйте raw-вивод без жорсткої підстави.
- **Розділяйте контексти**: HTML, атрибути, JS-рядки, URL — різні правила.
- **Санітизувати HTML-ввід**: якщо ви дозволяєте «форматування», застосовуйте allowlist (теги/атрибути) та вирізайте `on*`-обробники, `javascript:`-URL тощо.
- **CSP**: це не виправляє логіку, але різко знижує шкоду від XSS.

Стартовий CSP (ідеально — з nonces і без `'unsafe-inline'`):
```http
# /etc/nginx/nginx.conf
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
```

---

<a id="csrf"></a>
## CSRF: чому «це ж GET» не є захистом

CSRF — коли браузер користувача «сам» відправляє запит на ваш сайт із його cookies/сесією, бо інший сайт це тригерить (формою, зображенням, JS).

### Захист

- **CSRF-токени** на state-changing запитах (POST/PUT/PATCH/DELETE) — Laravel має це з коробки.
- **`SameSite` cookies**: мінімум `Lax`, для чутливих сценаріїв — `Strict`, для кросайтових — `None; Secure`.
- **Перевірка Origin/Referer** для критичних дій (зміна email, виплати).
- **Не міняти стан через GET** взагалі.

---

<a id="auth-sessions"></a>
## Автентифікація та сесії: крадіжка токенів, фіксація, cookies

Що найчастіше ламають:
- Слабкі паролі + відсутній rate limiting → credential stuffing.
- Сесії: XSS украв токен/куки; фіксація сесії; відсутня інвалідація.
- Довгоживучі токени без ротації та прив’язки до пристрою.

### Базовий набір

- **Rate limit** на логін/OTP/скидання пароля.
- **Хешування паролів** через `password_hash` (bcrypt/argon2), без «своїх» криптосхем.
- **Cookies** сесії: `HttpOnly`, `Secure`, `SameSite`, по можливості короткий TTL.
- **Ротація session ID** після логіна та підвищення прав.

---

<a id="idor"></a>
## Контроль доступу й IDOR: «це ж просто id»

IDOR — коли користувач підбирає/перебирає ідентифікатори й читає або змінює чужі ресурси.

Типові причини:
- Перевірка «користувач залогінений» замість перевірки володіння/ролей.
- Авторизація є в UI, але немає на API.
- «Адмінський» параметр у запиті керує поведінкою.

### Захист

- **Policies/gates** на кожен ресурс і дію.
- **Мульти-тенантність**: завжди фільтруйте за `tenant_id`/`account_id` на рівні запитів.
- Не покладайтеся на «приховування» елементів в UI.

```php
// app/Http/Controllers/OrderController.php
// Рекомендується обмежувати запит поточним користувачем:
$order = auth()->user()->orders()->findOrFail($id);

// Замість прямого пошуку:
$order = Order::findOrFail($id);
```

---

<a id="uploads"></a>
## Завантаження файлів і шлях: upload, path traversal, RCE поруч

Завантаження — улюблене місце для RCE «поруч із» застосунком.

### Що перевіряти

- **Вміст**, а не лише розширення. MIME від браузера — недоверений.
- **Обмеження розміру** та кількості файлів.
- **Зберігання поза web-root**, роздача через контролер або окремий домен/CDN.
- **Випадкові імена** (не використовувати оригінальне ім’я як шлях).
- **Заборона виконання** в директорії upload (на рівні вебсервера).

```nginx
# /etc/nginx/conf.d/uploads.conf
location /uploads {
    location ~ \.php$ {
        deny all;
    }
}
```

Path traversal часто з’являється в «скачай файл за ім’ям»:
- Забороняйте `../`, `\`, нуль-байти.
- Використовуйте allowlist реальних файлів/ідентифікаторів, а не шлях із запиту.

---

<a id="ssrf"></a>
## SSRF: коли сервер ходить «куди не треба»

SSRF виникає, коли ви берете URL від користувача і **робите запит від імені сервера** (імпорт аватарки, link preview, webhook tester).

Небезпека: доступ к **внутрішнім адресам** (metadata endpoints хмари, Redis/Consul, адмінки), обхід мережевих меж.

### Захист

- **Allowlist доменів/хостів**, а не blacklist.
- **Заборона внутрішніх IP-діапазонів** (127.0.0.0/8, 10.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, ::1, fc00::/7).
- **Вимкнення редіректів** або перевірка кожного хопа.
- **Таймаути**, ліміти розміру відповіді, тільки https.

---

<a id="deserialization"></a>
## Небезпечна десеріалізація та підміна об’єктів

Якщо ви десеріалізуєте дані, які може підробити атакувальник (cookies, hidden fields, payload з зовнішньої черги, кеш без підпису), ви ризикуєте:
- Підміною полів/ролей.
- Gadget chain та RCE (в екосистемах, де це можливо).

> [!NOTE]
> **Цілісність даних**
> Ніколи не зберігайте серіалізовані PHP/JS об'єкти в недовірених місцях. Для токенів використовуйте підпис (HMAC), а для складних даних — JSON зі строгою серверною валідацією за схемою.

---

<a id="browser"></a>
## Браузерні захисти: clickjacking, CORS, заголовки

### Clickjacking

Закривайте вбудовування в iframe:
```http
# /etc/nginx/nginx.conf
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
```

### CORS

Суть: CORS — не «захист від запитів», а політика читання відповіді браузером. Помилки:
- `Access-Control-Allow-Origin: *` разом із cookies/credentials.
- Дозвіл зайвих методів/заголовків.
- Довіра до `Origin` без строгого списку.

### Базові заголовки безпеки
```http
# /etc/nginx/nginx.conf
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
```

---

<a id="dos"></a>
## DoS та abuse: rate limit, брутфорс, дорогі операції

Найчастіші «напів-DoS» у бізнес-застосунках — це не гігабіти трафіку, а дешеві для атакувальника, дорогі для вас запити:
- Пошук без індексів, експорт великих звітів, генерація PDF.
- Endpoints без rate limit (логін, відправка email/OTP, «перевірка промокоду»).
- Складна валідація/регулярки на великих рядках.

### Заходи
- Rate limiting по IP + по акаунту + по ключу (залежно від сценарію).
- Черги на дорогі операції.
- Таймаути, ліміти розмірів, обмеження глибини JSON.
- Кешування «публічних» важких відповідей (з урахуванням персоналізації!).

---

<a id="common-mistakes"></a>
## Часті помилки

1. **Довіра до клієнтської валідації**: Перевірка даних лише на рівні фронтенд-форм і відсутність дублюючої перевірки на сервері, що дозволяє легко обійти всі обмеження безпосередньо через API.
2. **Використання сирого SQL у ORDER BY**: Оскільки плейсхолдери не підходять для назв колонок, розробники конкатенують параметри сортування безпосередньо в запит, створюючи уразливість SQLi.
3. **Чорні списки IP для захисту від SSRF**: Спроба заблокувати тільки `127.0.0.1` або `10.0.0.1`, забуваючи про шістнадцяткові (`0x7f.0.0.1`), вісімкові формати або атаки класу DNS Rebinding.
4. **Небезпечне використання unserialize()**: Зберігання серіалізованих PHP-об’єктів у cookie користувача з надією, що клієнт їх не зможе змінити, що призводить до PHP Object Injection.

---

<a id="checklist"></a>
## Чеклист для рев’ю та релізу

### Ввід і дані
- [ ] Валідація входу на межі (форми/API/webhooks), нормалізація типів.
- [ ] Allowlist для сортування/фільтрів/полей, які стають ідентифікаторами в запитах.
- [ ] Немає state-change через GET.

### Рендер і браузер
- [ ] Екранування за замовчуванням, немає необґрунтованого raw HTML.
- [ ] CSP/`frame-ancestors`, захист від clickjacking.
- [ ] HSTS, `nosniff`, розумний `Referrer-Policy`.

### Доступ
- [ ] Авторизація на сервері для кожної дії (policy/gate), не тільки «в UI».
- [ ] Немає IDOR: вибірки обмежені власником/тенантом.
- [ ] Логи аудиту для чутливих операцій.

### Інтеграції
- [ ] SSRF: allowlist доменів, заборона внутрішніх IP, ліміти та таймаути.
- [ ] Webhooks: підпис, захист від повторів (replay), ідемпотентність.

### Uploads
- [ ] Перевірка типу/розміру, зберігання поза web-root, заборона виконання.
- [ ] Випадкові імена, немає шляхів з користувацького вводу.

---

## Підсумок

Безпека — це підтримка інваріантів на межах системи. Ввід завжди перевіряється на вході, вивід екранується за замовчуванням, права перевіряються сервером при кожній дії, а зовнішні інтеграції захищаються підписами та лімітами.

---

<a id="self-test-quiz"></a>
## Квіз для самоперевірки

### Запитання 1: У чому полягає основна проблема безпеки в наступному коді?
```php
// app/Http/Controllers/DownloadController.php
return response()->download(storage_path('reports/' . $request->get('file')));
```
- А) SQL-ін’єкція.
- Б) Міжсайтовий скриптинг (XSS).
- В) Вихід за межі каталогу (Path Traversal / Arbitrary File Download).

<details>
<summary>Показати правильну відповідь</summary>

**Правильна відповідь: В**
Оскільки параметр `$request->get('file')` підставляється у шлях без валідації на наявність символів `../`, зловмисник може передати `file=../../.env` і прочитати файл конфігурації з секретними ключами.
</details>

### Запитання 2: Навіщо потрібні CSRF-токени для POST-запитів, якщо в сесійних куках налаштовано прапорець `SameSite=Lax`?
- А) `SameSite=Lax` не захищає від POST-запитів, ініційованих переходом по посиланнях або JS-скриптами на старих браузерах.
- B) CSRF-токени запобігають впровадженню SQL-коду.
- C) Без CSRF-токена сесійні куки взагалі не можуть бути прочитані.

<details>
<summary>Показати правильну відповідь</summary>

**Правильна відповідь: А**
SameSite — це чудовий допоміжний механізм, але він не дає 100% захисту через відмінності в поведінці старих версій браузерів, атак на рівні піддоменів або вразливостей, пов'язаних із зміною стану через GET-запити.
</details>