
Пассивное middleware для Laravel, которое обнаруживает и логирует SQL-инъекции, XSS, RCE, бот-сканеры и более 175 паттернов атак. Включает встроенную панель управления, уведомления в Slack, REST API и геообогащение. IDS, а не WAF.
<p align="center">
<a href="https://packagist.org/packages/jayanta/laravel-threat-detection"><img src="https://img.shields.io/packagist/v/jayanta/laravel-threat-detection.svg?style=flat-square" alt="Latest Version"></a>
<a href="https://github.com/jay123anta/laravel-threat-detection/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/jay123anta/laravel-threat-detection/tests.yml?branch=main&style=flat-square&label=tests" alt="Tests"></a>
<a href="https://github.com/jay123anta/laravel-threat-detection/actions/workflows/tests.yml"><img src="https://img.shields.io/badge/PHPStan-level%205-brightgreen?style=flat-square" alt="PHPStan Level 5"></a>
<a href="https://github.com/jay123anta/laravel-threat-detection/actions/workflows/tests.yml"><img src="https://img.shields.io/badge/code%20style-Pint-orange?style=flat-square" alt="Code Style Pint"></a>
<a href="https://packagist.org/packages/jayanta/laravel-threat-detection"><img src="https://img.shields.io/packagist/dt/jayanta/laravel-threat-detection.svg?style=flat-square" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/jayanta/laravel-threat-detection"><img src="https://img.shields.io/packagist/php-v/jayanta/laravel-threat-detection?style=flat-square" alt="PHP Version"></a>
<a href="https://github.com/jay123anta/laravel-threat-detection/blob/main/LICENSE"><img src="https://img.shields.io/packagist/l/jayanta/laravel-threat-detection.svg?style=flat-square" alt="License"></a>
</p>
# Laravel Threat Detection
**Мониторинг безопасности и журналирование атак для Laravel. Обнаруживает и регистрирует SQL-инъекции,
XSS, RCE, обход каталогов, ботов-сканеров и разведочные запросы в стиле `/wp-admin` —
каждый враждебный запрос записывается в вашу базу данных с полным контекстом приложения.
Это IDS, а не WAF: он никогда не блокирует, не фильтрует и не изменяет запрос.**
<p align="center">
<img src="https://raw.githubusercontent.com/jay123anta/laravel-threat-detection/main/art/demo.gif" alt="Установите пакет, отправьте три атаки — SQL-инъекцию, обход каталогов, XSS — каждая возвращает HTTP 200, потому что ничего не блокируется, и все три уже учтены в threat-detection:stats" width="100%">
</p>
### Вы здесь, потому что увидели что-то подобное?
```
GET /wp-admin/setup-config.php 404 — on a site that isn't WordPress
GET /.env 404 — someone wants your database password
GET /?id=1' UNION SELECT password FROM 200 — SQL injection against a real route
GET /phpmyadmin/index.php 404 — scanning for an admin panel
```
Эти запросы уже достигают вашего Laravel-приложения. Ваш лог доступа показывает URL
и код состояния, и больше ничего — ни декодированную полезную нагрузку, ни то, какой из ваших
маршрутов был атакован, ни то, пытался ли тот же IP сделать сорок других вещей за этот час.
Этот пакет отвечает на эти вопросы. Установите его в любое приложение на Laravel 10–13, и он начнёт
сканировать каждый HTTP-запрос по 150+ шаблонам атак, оценивая каждое совпадение по
уровню достоверности и записывая его в вашу базу данных — со встроенной панелью управления, оповещениями в Slack,
геообогащением и экспортом для fail2ban/блок-листов. Ни один запрос никогда не блокируется. Думайте о нём как о камере
наблюдения, а не о замке: он показывает вам, кто именно прощупывает ваши маршруты, как
часто и какими методами.
> Извлечён из production-приложения и проверен в бою на реальном трафике. 2 400+ тестов, никаких runtime-зависимостей
> помимо самого Laravel, и для обнаружения не требуется подключение к интернету.
>
> Обновляетесь? См. [UPGRADING.md](https://github.com/jay123anta/laravel-threat-detection/blob/main/UPGRADING.md). Хотите внести вклад? См. [CONTRIBUTING.md](https://github.com/jay123anta/laravel-threat-detection/blob/main/CONTRIBUTING.md).
## Начните меньше чем за минуту
```bash
composer require jayanta/laravel-threat-detection
php artisan vendor:publish --tag=threat-detection-migrations
php artisan migrate
```
Затем добавьте middleware в вашу группу `web` (одна строка в `bootstrap/app.php` на Laravel 11+,
или `app/Http/Kernel.php` на Laravel 10) — полный фрагмент в разделе [Quick Start](#quick-start) ниже.
Вот и всё; обнаружение работает.
```bash
php artisan threat-detection:doctor # confirms it is actually recording
```
---
## Где это место: IDS против WAF против edge
Этот пакет — **пассивный IDS уровня приложения** — он наблюдает и записывает, но не
блокирует. Он предназначен для работы *рядом* с WAF или edge-сервисом, а не для их замены. Каждый слой видит
то, чего не видят другие:
| | **Этот пакет** (app IDS) | **WAF** (ModSecurity + OWASP CRS, Cloudflare WAF) | **Edge / CDN** (Cloudflare) |
|---|:---:|:---:|:---:|
| Блокирует вредоносные запросы | ❌ только логирует | ✅ | ✅ |
| Полный контекст приложения (точный URL, декодированный payload, аутентифицированный пользователь) | ✅ | ⚠️ частично | ❌ |
| Встроенный дашборд + журнал угроз в вашей БД | ✅ | ⚠️ зависит | ⚠️ только edge |
| Обнаружения, специфичные для приложения (например, Aadhaar / PAN / IFSC PII) | ✅ встроено + кастомные | ⚠️ пишите свои правила | ⚠️ кастомные правила, зависит от плана |
| Работает офлайн / без внешнего сервиса | ✅ | ⚠️ зависит | ❌ |
| Останавливает трафик до того, как он достигнет вашего сервера | ❌ | ⚠️ только облачный WAF | ✅ |
| Установка | `composer require`, миграция, одна строка middleware | средне–высокая | низкая–средняя |
| Стоимость | бесплатно, MIT | зависит | бесплатный тариф + платный |
**Коротко:** edge/WAF — это замок на двери; это — камера безопасности
*внутри*, с контекстом приложения, позволяющим точно сказать, что пытаются сделать на каком маршруте, кем
и как часто. Используйте это для принятия реальных решений — баны fail2ban, ограничения скорости,
геоблокировка — на основе данных, которых ваш edge-слой никогда не видит.
### Чем это намеренно НЕ является
- **Не WAF.** Он никогда не блокирует, не фильтрует и не изменяет запрос. Используйте Cloudflare,
mod_security или настоящий WAF для принудительного применения. (Нет edge-слоя, которому можно передать управление?
[Помощники на стороне оператора](#acting-on-the-data-operator-side-blocking) предоставляют
решения пакета, чтобы вы могли написать собственный middleware для блокировки в пять строк —
код принудительного применения остаётся вашим, а не пакета.)
- **Не замена безопасному кодированию.** Параметризованные запросы, валидация входных данных и
экранирование вывода — это ваша реальная защита. Этот пакет предполагает, что ваш код уже
безопасен, и даёт вам *видимость*, а не защиту.
- **Не edge-сервис.** Если вы можете поставить Cloudflare впереди, сделайте это — затем добавьте это для
детализации на уровне приложения, которую edge-сервисы не видят.
- **Не полный детектор, и он не может им быть.** Сопоставление с шаблонами ловит атаки, которые
*выглядят как* известные атаки. Новая техника или знакомая, достаточно переписанная,
пройдёт незалогированной — и вам не сообщат, что это произошло. Тишина здесь означает
«ничего не совпало», а не «ничего не произошло». Своё место он оправдывает на
высокообъёмном, малозатратном трафике, который составляет большую часть того, что реально попадает в публичное
Laravel-приложение: сканеры, разведочные пробы, готовые строки инъекций, перебор
учётных данных. Относитесь к тихому журналу как к отсутствию доказательств, а не как к доказательству отсутствия.
### Ожидайте, что в первый же день он отметит ваш собственный контент
Ненастроенная установка срабатывает на легитимном контенте, и вам стоит знать это до
установки, а не после. Это измеренные данные, а не гипотетические. Строки
ниже — выборка минимального уровня, зафиксированного
[`LegitimateTrafficCorpusTest`](https://github.com/jay123anta/laravel-threat-detection/blob/main/tests/Feature/LegitimateTrafficCorpusTest.php);
каждая из них перечисляет *всё*, что логирует этот запрос, и
[`ReadmeNoiseFloorTest`](https://github.com/jay123anta/laravel-threat-detection/blob/main/tests/Feature/ReadmeNoiseFloorTest.php) проваливает
сборку, если какая-либо строка перестаёт соответствовать тому, что для неё измеряет набор тестов.
<!-- noise-floor:start -->
| Совершенно легитимный запрос | Что логирует ненастроенная установка |
|---|---|
| `how to write a UNION SELECT in postgres` введённый в поле поиска <!-- case: sql tutorial in a search box --> | `SQL Injection UNION` / high |
| Пост в блоге, содержащий `<script>window.dataLayer=[];</script>` <!-- case: javascript snippet in a blog post --> | `XSS Script Tag` / high · `Command Chain Injection` / medium |
| Тикет в поддержку с вставленной ошибкой `SELECT * FROM users WHERE id = 1` <!-- case: a pasted sql error in a support ticket --> | `SQLi Variant` / high · `SQL SELECT Query` / low |
| Документация, объясняющая, что `../../etc/passwd` — это классический payload для traversal <!-- case: a traversal explainer in documentation --> | `Directory Traversal` / medium · `Sensitive File Access` / medium |
| Форма профиля, собирающая настоящий индийский мобильный номер и PAN <!-- case: a profile form collecting genuine indian pii --> | `PAN Number Detected` / high · `Bank Account Number Detected` / high · `Mobile Number Detected` / low |
<!-- noise-floor:end -->
**Ничто из этого не является багом.** Пост в блоге, содержащий `<script>`, — байт в байт —
это payload для хранимого XSS; поиск `UNION SELECT` неотличим от попытки
его выполнения; десятизначный мобильный номер — это также правдоподобный номер банковского счёта. Ничто,
кроме контекста приложения, не разделяет их, и никакой движок шаблонов не может предоставить этот
контекст за вас.
Предоставить его — это изменение конфигурации в одну строку: назовите поле, которое легитимно несёт
такой контент, в `safe_fields` (или `safe_paths`, для одного пути внутри вложенного JSON), и
оно больше не сканируется. Это тот переключатель, который заглушает строки выше — и это
реальный компромисс, поскольку атака, доставленная через это поле, тоже останется незамеченной.
`content_paths` и режим `relaxed` — это разные инструменты. Они отбрасывают шум низкой и
средней серьёзности и **намеренно продолжают записывать совпадения высокой серьёзности** —
поэтому они не заглушат строки с высокой серьёзностью в этой таблице, по замыслу. См.
[Снижение ложных срабатываний](#reducing-false-positives) о том, что использовать когда.
**Если ваше приложение принимает rich text, примеры кода или поисковые запросы, сделайте это до того, как
судить о выводе.** По умолчанию он намеренно шумный, но честный, а не
тихий и неполный: заглушить известное совпадение легче, чем обнаружить то, которое
никогда не срабатывало.
### Так что же с этим на самом деле делать?
Самый частый вопрос о детекторе, который никогда не блокирует. Четыре ответа в
порядке возрастания усилий:
| Вы хотите | Используйте | Усилия |
|---|---|---|
| Увидеть, что вас атакует | [Дашборд](#dashboard) или `threat-detection:stats` | никаких, он уже работает |
| Банить повторных нарушителей на файрволе | [`threat-detection:export-fail2ban`](#artisan-commands) — в cron | одна строка |
| Отклонять на веб-сервере | [`threat-detection:export-blocklist`](#artisan-commands) → директивы nginx/apache | одна строка |
| Отказывать в запросах внутри приложения | [Помощники на стороне оператора](#acting-on-the-data-operator-side-blocking) — `isBlocklisted()`, `isDdosThresholdExceeded()` | ~10 строк вашего собственного middleware |
| Реагировать в реальном времени | [Событие `ThreatDetected`](#threatdetected-event) — Telegram, SIEM, PagerDuty | слушатель |
Пакет предоставляет разведданные; вы предоставляете отказ. Это разделение
намеренно — код принудительного применения, живущий в вашем приложении, — это код, который вы можете читать,
тестировать и отключать, и это означает, что баг в детекторе никогда не сможет уронить ваш сайт.
### Как это сравнивается с другими пакетами безопасности для Laravel
Они решают разные задачи и хорошо сочетаются — таблица о выборе
правильного инструмента, а не о победе.
| Пакет | Что делает | Блокирует? | Используйте, когда |
|---|---|:---:|---|
| **этот пакет** | Сканирует каждый запрос по 150+ шаблонам, логирует с полным контекстом приложения | ❌ | Вы хотите *видеть*, что пытаются сделать с вашим приложением |
| `spatie/laravel-honeypot` | Скрытое поле формы, ловящее спам-ботов | ✅ только формы | У вас есть публичные формы, которые спамят |
| `graham-campbell/security` | Удаляет XSS-подобную разметку из входных данных | ✅ изменяет | Вы хотите наивную санитизацию входных данных |
| `spatie/laravel-csp` | Отправляет заголовки Content-Security-Policy | ✅ браузер | Вы хотите ограничить то, что загружает браузер |
| `laravel/fortify` + ограничения скорости | Троттлинг аутентификации и блокировка | ✅ | Вам нужна защита от перебора на входе |
| Cloudflare / mod_security | Edge WAF, блокирует до вашего приложения | ✅ | Вы хотите остановить трафик до того, как он придёт |
Честное резюме: honeypot ловит спам форм, WAF блокирует известный плохой трафик
на edge, а CSP ограничивает браузер. **Ни один из них не говорит вам, что
атакующий пытался сделать против ваших конкретных маршрутов, с декодированным payload и
привязанным аутентифицированным пользователем.** Этот пробел и заполняет данный пакет — и именно поэтому
пакет намеренно не блокирует: вы можете запускать его вместе со всем вышеперечисленным
без того, чтобы они конфликтовали друг с другом.
---
## Требования
- PHP 8.2+ (Laravel 13 требует PHP 8.3+)
- Laravel 10.x, 11.x, 12.x или 13.x
- Любая база данных, поддерживаемая Laravel (MySQL, PostgreSQL, SQLite, SQL Server)
- Любой драйвер кэша - **Redis или queue worker не требуется**. Redis/Memcached
только *рекомендуется* для включения опциональной проверки DDoS (которая автоматически отключается на
неатомарных драйверах). Записи через очередь опциональны и по умолчанию отключены.
---
## Как это работает
1. Middleware сканирует каждый входящий HTTP-запрос
2. Запрос проверяется по 158 regex-шаблонам, охватывающим SQL-инъекции, XSS, RCE, обход файловой системы, SSRF, LDAP, XPath, SSTI и другое
3. Если шаблон угрозы совпадает, запись пишется в вашу таблицу базы данных `threat_logs` с IP, URL, типом угрозы, уровнем серьёзности и оценкой уверенности
4. Опционально отправляется оповещение в Slack для угроз высокой серьёзности
5. Запрос продолжается нормально - **ничего не блокируется**
Для обнаружения не требуется интернет-соединение.
---
## Быстрый старт
### 1. Установите пакет
```bash
composer require jayanta/laravel-threat-detection
```
### 2. Публикация миграций и их запуск
> **Этот шаг обязателен.** Без него пакет будет обнаруживать угрозы, но не сможет сохранять их в базе данных. Если пропустить этот шаг, таблица `threat_logs` не будет создана, и все обнаружения будут молча теряться (ошибки появятся только в `storage/logs/laravel.log`).
```bash
php artisan vendor:publish --tag=threat-detection-migrations
php artisan migrate
```
Это создаёт две таблицы: `threat_logs` (хранит обнаруженные угрозы) и `threat_exclusion_rules` (хранит правила ложных срабатываний).
**Проверьте, что таблицы созданы:**
```bash
php artisan migrate:status
```
Ищите `create_threat_logs_table`, `add_confidence_to_threat_logs_table` и `create_threat_exclusion_rules_table` — все они должны показывать `Ran`.
### 3. Зарегистрируйте middleware
Middleware — это то, что сканирует запросы. Вам нужно добавить его в вашу группу middleware `web`.
**Если вы используете Laravel 11 или 12** — откройте `bootstrap/app.php`:
```php
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\JayAnta\ThreatDetection\Http\Middleware\ThreatDetectionMiddleware::class,
]);
})
```
> **Как проверить версию Laravel:** Выполните `php artisan --version` в терминале.
**Если вы используете Laravel 10** — откройте `app/Http/Kernel.php`:
```php
protected $middlewareGroups = [
'web' => [
// ... existing middleware
\JayAnta\ThreatDetection\Http\Middleware\ThreatDetectionMiddleware::class,
],
];
```
### 4. (Необязательно) Опубликуйте файл конфигурации
```bash
php artisan vendor:publish --tag=threat-detection-config
```
Пакет работает с разумными настройками по умолчанию. Публикация конфигурации позволяет настроить шаблоны обнаружения, режимы чувствительности, уведомления Slack и многое другое. Если пропустить этот шаг, всё всё равно будет работать.
**Вот и всё.** Теперь ваше приложение обнаруживает угрозы.
---
## Проверьте, что это работает
После установки вызовите тестовую угрозу и убедитесь, что она была зарегистрирована.
### Шаг 1: Запустите приложение
```bash
php artisan serve
```
### Шаг 2: Откройте тестовый URL в браузере
Добавьте вредоносный параметр запроса к **любому существующему маршруту** в вашем приложении (главная страница, страница товара и т. д.). Например:
**SQL-инъекция:**
```
http://localhost:8000/?q=' UNION SELECT * FROM users--
```
**XSS (межсайтовый скриптинг):**
```
http://localhost:8000/?q=<script>alert(1)</script>
```
**Обход каталогов:**
```
http://localhost:8000/?file=../../etc/passwd
```
**RCE (удалённое выполнение кода):**
```
http://localhost:8000/?cmd=system('ls -la')
```
**Shellshock (CVE-2014-6271):**
```
http://localhost:8000/?cmd=() { :;}; /bin/bash
```
**Инъекция команд Windows:**
```
http://localhost:8000/?cmd=powershell -c whoami
```
**DROP TABLE (SQL DDL):**
```
http://localhost:8000/?q=DROP TABLE users
```
> Используйте маршрут, который действительно существует в вашем приложении (например, `/`). Если URL возвращает 404, возможно, middleware не выполнился.
### Шаг 3: Проверьте, что угрозы были залогированы
**Вариант A — команда Artisan (самый быстрый):**
```bash
php artisan threat-detection:stats
```
Вы должны увидеть таблицу с `Recorded Detections`, количеством по уровням серьёзности и топовыми
IP-адресами. Это число считает *строки*, а не попытки: обнаружение записывается один раз на
IP на тип угрозы за пять минут, а повторы внутри этого окна
дедуплицируются, а не подсчитываются снова.
**Вариант B — Tinker:**
```bash
php artisan tinker
```
```php
DB::table('threat_logs')->latest()->take(5)->get(['ip_address', 'type', 'threat_level', 'confidence_score']);
```
**Вариант C — файл журнала Laravel:**
Каждая обнаруженная угроза записывается как предупреждение в `storage/logs/laravel.log`:
```
[high] Threat Detected: [middleware] SQL Injection UNION from 127.0.0.1 (http://localhost:8000/?q=...) [confidence: 50%]
```
### Что нужно знать при тестировании
| Поведение | Объяснение |
|----------|-------------|
| Одна и та же угроза логируется только раз в 5 минут | Дедупликация: одинаковый IP + одинаковый тип угрозы кэшируется на 5 минут. Используйте **разные типы атак** для каждого теста или делайте паузу между тестами. |
| Запросы `curl` вызывают дополнительное обнаружение | Использование `curl` также логирует обнаружение user-agent "cURL Command" (низкая критичность). Это ожидаемо — пакет обнаруживает автоматизированные инструменты. |
| Пакет никогда не блокирует запросы | Ваше приложение продолжает работать нормально. Обнаружение пассивное. |
| Настройка Slack не требуется | Уведомления отключены по умолчанию. |
| Подключение к интернету не требуется | Основное обнаружение на 100% локальное. Только опциональная команда `threat-detection:enrich` обращается к внешнему API для гео-данных. |
### Устранение неполадок
**Начните отсюда — одна команда отвечает на большую часть этого:**
```bash
php artisan threat-detection:doctor
```
Он проверяет то, что заставляет обнаружение *тихо* давать сбой — когда панель мониторинга остаётся пустой, что выглядит так же, как «нет атак», — и выводит точное решение для каждого случая. При реальной ошибке он завершается с ненулевым кодом, поэтому его безопасно запускать в CI или на этапе развёртывания.
```
Threat Detection — health check
PASS Detection is enabled for this environment
FAIL 'threat_logs' is missing confidence_label — EVERY threat is being discarded
Run: php artisan vendor:publish --tag=threat-detection-migrations && php artisan migrate
WARN 1 custom pattern(s) shadow a built-in: Localhost SSRF
Your copy runs instead of the maintained one, so later fixes to it never reach you.
```
Что охватывается: включена ли детекция для этого окружения; все столбцы, необходимые writer'у (отсутствие хотя бы одного отбрасывает **каждую** угрозу); столбцы dashboard/API; таблица правил исключений; действительно ли middleware подключён к маршруту или группе; опубликованный конфиг, который предшествует этой версии; пользовательские паттерны, перекрывающие встроенные; драйвер кэша, который не может выполнять подсчёт DDoS; и dashboard или API, оставленные открытыми без аутентификации.
**«Я протестировал, но `threat-detection:stats` показывает ноль угроз» / «Угрозы не сохраняются в базе данных»**
Если doctor прошёл, установка в порядке, и проблема в самом тестовом запросе. Три вещи, которые он не может проверить за вас:
| Проверка | Как проверить |
|-------|---------------|
| IP не в белом списке | Если вы добавили `THREAT_DETECTION_WHITELISTED_IPS` в `.env`, удалите его на время тестирования |
| Использован существующий маршрут | Тестовый URL должен соответствовать реальному маршруту (например, `/`). 404 означает, что middleware не запускался |
| Кэш дедупликации | Одинаковый IP + одинаковый тип атаки кэшируется на 5 минут — попробуйте другой тип атаки |
> Запустить только `php artisan migrate` никогда недостаточно: файлы миграций находятся
> внутри пакета и должны быть сначала опубликованы в `database/migrations/`
---
[Read more](https://github.com/jay123anta/laravel-threat-detection)