---
title: 'Веб-атаки и защита: XSS, CSRF, SQLi, SSRF, IDOR и загрузки файлов | DevSense'
description: 'Атаки, которые встречаются чаще всего в веб-приложениях: инъекции (SQL/command), XSS, CSRF, IDOR и ошибки контроля доступа, SSRF, небезопасные загрузки файлов, clickjacking и misconfig. Практические меры, заголовки и чеклисты.'
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)
* [Аутентификация и сессии: кража токенов, фиксация, куки](#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()` — не серебряная пуля, а последний барьер.

Если нужно конвертировать файл/генерировать превью:
- Предпочитайте библиотеку, а не `exec()`.
- Если всё же CLI — фиксируйте бинарник, аргументы и директории, запускайте в ограниченном окружении (контейнер/очередь/worker под отдельным пользователем).

### Template injection

Самый опасный вариант — когда вы позволяете пользователю задавать «шаблон», который потом исполняется как Blade/Twig/Smarty.
- **Правило**: Пользовательский шаблон — только как данные, максимум через ограниченный, не Turing-complete формат (например, Markdown с жестко заданным набором разрешенных тегов и безопасным рендером).

---

<a id="xss"></a>
## XSS: отражённый, хранимый, DOM-based

XSS — это выполнение JS в контексте вашего домена. Типовые источники:
- Вывод «как есть» (`{!! ... !!}` в 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>
## Аутентификация и сессии: кража токенов, фиксация, куки

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

### Минимальный набор

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

---

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

IDOR (Insecure Direct Object Reference) — когда пользователь угадывает/перебирает идентификаторы и видит/меняет чужие ресурсы.

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

### Защита

- **Политики/гейты** на каждый ресурс и каждое действие (view/update/delete).
- **Мульти-тенантность**: всегда фильтруйте по `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 от пользователя и **делаете запрос от имени сервера** (импорт аватарки, webhook tester, «проверить ссылку», PDF-сервис).

Опасность: доступ к **внутренним адресам** (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` без строгого списка.

### Базовые security headers
```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>