
SQL 인젝션, XSS, RCE, 봇 스캐너 및 175개 이상의 공격 패턴을 탐지하고 기록하는 패시브 Laravel 미들웨어입니다. 내장 대시보드, Slack 알림, REST API 및 지리 정보 보강 기능을 갖추고 있습니다. WAF가 아닌 IDS입니다.
Laravel용 보안 모니터링 및 공격 로깅. SQL 인젝션, XSS, RCE, 디렉터리 트래버설, 봇 스캐너 및 /wp-admin 스타일 정찰 프로브를 탐지하고 로깅합니다 — 모든 적대적 요청이 전체 애플리케이션 컨텍스트와 함께 데이터베이스에 기록됩니다. 이는 IDS이지 WAF가 아닙니다: 요청을 차단, 필터링 또는 수정하지 않습니다.
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가 이번 시간에 다른 40가지를 시도했는지 여부는 알 수 없습니다.
이 패키지는 그러한 질문에 답합니다. Laravel 10–13 앱에 설치하기만 하면 모든 HTTP 요청을 150개 이상의 공격 패턴과 대조해 스캔하기 시작하고, 각 매치를 신뢰도에 따라 점수화하여 데이터베이스에 기록합니다. 내장 대시보드, Slack 알림, 지리 정보 보강, fail2ban/블록리스트 내보내기도 포함되어 있습니다. 어떤 요청도 차단되지 않습니다. 자물쇠가 아닌 보안 카메라라고 생각하세요. 누가 어떤 기법으로 얼마나 자주 라우트를 탐색하는지 정확히 보여줍니다.
> 프로덕션 앱에서 추출되어 실제 트래픽으로 검증되었습니다. 335개의 테스트, 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)를 참조하세요.
## 1분 안에 시작하기```bash
composer require jayanta/laravel-threat-detection
php artisan vendor:publish --tag=threat-detection-migrations
php artisan migrate
그런 다음 web 그룹에 미들웨어를 추가하세요(Laravel 11+에서는 bootstrap/app.php에, Laravel 10에서는 app/Http/Kernel.php에 한 줄 추가) — 전체 코드 조각은 아래 빠른 시작에 있습니다.
이것으로 끝입니다. 탐지가 활성화됩니다.```bash
php artisan threat-detection:doctor # confirms it is actually recording
---
## 어디에 해당하는가: IDS vs WAF vs 엣지
이 패키지는 **수동적(passive), 애플리케이션 수준 IDS**입니다. 요청을 감시하고 기록할 뿐 차단하지는 않습니다. WAF나 엣지 서비스 *옆에* 함께 배치하도록 설계되었으며, 이를 대체하지 않습니다. 각 계층은 다른 계층이 볼 수 없는 것을 봅니다:
| | **이 패키지** (앱 IDS) | **WAF** (mod_security, Cloudflare WAF) | **엣지 / CDN** (Cloudflare) |
|---|:---:|:---:|:---:|
| 악성 요청 차단 | ❌ 로그만 기록 | ✅ | ✅ |
| 전체 앱 컨텍스트 (정확한 라우트, 디코딩된 페이로드, 인증된 사용자) | ✅ | ⚠️ 부분적 | ❌ |
| 내장 대시보드 + DB 내 위협 로그 | ✅ | ⚠️ 제각각 | ⚠️ 엣지 전용 |
| 앱 특화 탐지 (예: Aadhaar / PAN / IFSC PII) | ✅ 사용자 정의 패턴 | ❌ | ❌ |
| 오프라인 동작 / 외부 서비스 불필요 | ✅ | ⚠️ 상황에 따라 다름 | ❌ |
| 앱에 도달하기 전에 트래픽 차단 | ❌ | ✅ 엣지 | ✅ |
| 설정 | `composer require` 한 번 | 중간~높음 | 낮음~중간 |
| 비용 | 무료, MIT | 제각각 | 무료 티어 + 유료 |
**요약:** 엣지/WAF는 문에 걸린 자물쇠이고, 이것은 *내부*의 보안 카메라로, 어떤 라우트에서 무엇이, 누구에 의해, 얼마나 자주 시도되고 있는지 정확히 알려주는 앱 컨텍스트를 제공합니다. 이를 사용해 실제 결정(fail2ban 차단, 속도 제한, 지리적 차단)을 내리세요. 엣지 계층이 결코 볼 수 없는 데이터로 말입니다.
### 의도적으로 NOT인 것
- **WAF가 아님.** 요청을 차단, 필터링, 수정하지 않습니다. 집행(enforcement)에는 Cloudflare, mod_security 또는 실제 WAF를 사용하세요. (넘겨줄 엣지 계층이 없나요? [운영자 측 헬퍼](#acting-on-the-data-operator-side-blocking)가 패키지의 결정을 노출하므로 직접 5줄짜리 차단 미들웨어를 작성할 수 있습니다 — 집행 코드는 패키지가 아닌 여러분의 것이 됩니다.)
- **안전한 코딩의 대체재가 아님.** 파라미터화된 쿼리, 입력 검증, 출력 이스케이프가 실제 방어 수단입니다. 이 패키지는 여러분의 코드가 이미 안전하다고 가정하고 *가시성*을 제공할 뿐, 보호를 제공하지 않습니다.
- **엣지 서비스가 아님.** 앞에 Cloudflare를 둘 수 있다면 두세요 — 그런 다음 엣지 서비스가 볼 수 없는 애플리케이션 수준 세부 정보를 위해 이것을 추가하세요.
### 그렇다면 실제로 무엇에 사용하나요?
차단하지 않는 탐지기에 대한 가장 흔한 질문입니다. 노력이 증가하는 순서대로 네 가지 답변:
| 원하는 것 | 사용 | 노력 |
|---|---|---|
| 무엇이 나를 공격하는지 보기 | [대시보드](#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줄 |
| 실시간 대응 | [`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 | 엣지 WAF, 앱 도달 전 차단 | ✅ | 트래픽이 도착하기 전에 중단시키고 싶을 때 |
정직한 요약: 허니팟은 양식 스팸을 잡고, WAF는 엣지에서 알려진 악성 트래픽을 차단하며, CSP는 브라우저를 제한합니다. **그 어느 것도 공격자가 특정 라우트에 무엇을 시도했는지, 페이로드가 디코딩되고 인증된 사용자가 첨부된 상태로 알려주지 않습니다.** 그 공백을 채우는 것이 바로 이것이며 — 패키지가 의도적으로 차단하지 않는 이유이기도 합니다: 위의 모든 것과 함께 실행해도 서로 충돌하지 않습니다.
---
## 요구 사항
- PHP 8.2+ (Laravel 13은 PHP 8.3+ 필요)
- Laravel 10.x, 11.x, 12.x 또는 13.x
- Laravel이 지원하는 모든 데이터베이스 (MySQL, PostgreSQL, SQLite, SQL Server)
- 모든 캐시 드라이버 — **Redis나 큐 워커 불필요**. Redis/Memcached는 선택적 DDoS 검사를 활성화하기 위해 *권장*될 뿐입니다(비원자적 드라이버에서는 자동 비활성화). 큐 기반 쓰기는 선택 사항이며 기본적으로 꺼져 있습니다.
---
## 작동 방식
1. 미들웨어가 들어오는 모든 HTTP 요청을 스캔합니다
2. 요청이 SQL 인젝션, XSS, RCE, 파일 트래버설, SSRF, LDAP, XPath, SSTI 등을 포함한 158개의 정규식 패턴과 대조됩니다
3. 위협 패턴이 일치하면 IP, URL, 위협 유형, 심각도 수준, 신뢰도 점수와 함께 `threat_logs` 데이터베이스 테이블에 레코드가 기록됩니다
4. 선택적으로 높은 심각도의 위협에 대해 Slack 알림이 전송됩니다
5. 요청은 정상적으로 진행됩니다 — **아무것도 차단되지 않습니다**
탐지에 인터넷 연결은 필요하지 않습니다.
---
## 빠른 시작
### 1. 패키지 설치```bash
composer require jayanta/laravel-threat-detection
이 단계는 필수입니다. 이 단계를 수행하지 않으면 패키지가 위협을 감지할 수는 있지만 데이터베이스에 저장할 수 없습니다. 이 단계를 건너뛰면
threat_logs테이블이 존재하지 않게 되어 모든 감지 결과가 조용히 유실됩니다(storage/logs/laravel.log에 오류만 표시됩니다).```bash php artisan vendor:publish --tag=threat-detection-migrations php artisan migrate
This creates two tables: `threat_logs` (stores detected threats) and `threat_exclusion_rules` (stores false positive rules).
**Verify tables were created:**```bash
php artisan migrate:status
create_threat_logs_table, add_confidence_to_threat_logs_table, create_threat_exclusion_rules_table를 찾으세요. 모두 Ran으로 표시되어야 합니다.
미들웨어는 요청을 스캔하는 역할을 합니다. 이를 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,
],
];
php artisan vendor:publish --tag=threat-detection-config
이 패키지는 합리적인 기본값으로 작동합니다. 구성을 게시하면 탐지 패턴, 민감도 모드, Slack 알림 등을 사용자 지정할 수 있습니다. 이 단계를 건너뛰어도 모든 것이 여전히 작동합니다.
**그게 전부입니다.** 이제 앱이 위협을 탐지하고 있습니다.
---
## 작동 확인
설치 후 테스트 위협을 트리거하여 로그에 기록되었는지 확인하세요.
### 1단계: 앱 시작```bash
php artisan serve
앱의 기존 라우트(홈페이지, 제품 페이지 등)에 악성 쿼리 파라미터를 추가하세요. 예를 들어:
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를 반환하면 미들웨어가 실행되지 않았을 수 있습니다.
### 3단계: 위협이 기록되었는지 확인
**옵션 A - Artisan 명령(가장 빠름):**```bash
php artisan threat-detection:stats
옵션 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`을 사용하면 "cURL Command" 사용자 에이전트 탐지(낮은 심각도)도 기록됩니다. 이는 정상입니다 — 패키지가 자동화 도구를 탐지합니다. |
| 패키지는 요청을 차단하지 않음 | 앱은 정상적으로 계속 작동합니다. 탐지는 수동적입니다. |
| Slack 설정 불필요 | 알림은 기본적으로 꺼져 있습니다. |
| 인터넷 연결 불필요 | 핵심 탐지는 100% 로컬에서 수행됩니다. 선택적 `threat-detection:enrich` 명령만 지리 데이터를 위해 외부 API를 호출합니다. |
### 문제 해결
**여기서 시작하세요 — 한 가지 명령으로 대부분의 문제를 해결할 수 있습니다:**```bash
php artisan threat-detection:doctor
탐지가 조용히 실패하게 만드는 요소를 점검합니다. 즉, 대시보드가 비어 있는 상태로 유지되어 "공격 없음"과 동일하게 보이는 상황을 확인하고, 각각에 대한 정확한 수정 방법을 출력합니다. 실제 실패가 발생하면 0이 아닌 종료 코드로 종료되므로 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.
이 환경에서 활성화된 탐지 기능을 다룹니다. 작성자가 필요한 모든 열(하나라도 누락되면 **모든** 위협이 폐기됨), 대시보드/API 열, 제외 규칙 테이블, 미들웨어가 실제로 라우트나 그룹에 연결되어 있는지 여부, 이 버전보다 먼저 게시된 설정, 내장 패턴을 가리는 사용자 정의 패턴, DDoS 카운팅을 수행할 수 없는 캐시 드라이버, 그리고 인증 없이 열려 있는 대시보드나 API를 다룹니다.
**"테스트했는데 `threat-detection:stats`에 위협이 0개로 표시됩니다" / "위협이 데이터베이스에 저장되지 않습니다"**
의사 점검이 통과했다면 설치에는 문제가 없으며, 문제는 테스트 요청 자체에 있습니다. 이 도구가 확인해 줄 수 없는 세 가지가 있습니다:
| 확인 항목 | 검증 방법 |
|-------|---------------|
| IP가 화이트리스트에 없음 | `.env`에 `THREAT_DETECTION_WHITELISTED_IPS`를 추가했다면 테스트 중에는 제거하세요 |
| 기존 라우트 사용 | 테스트 URL은 실제 라우트(예: `/`)와 일치해야 합니다. 404가 반환되면 미들웨어가 실행되지 않은 것입니다 |
| 중복 제거 캐시 | 동일한 IP + 동일한 공격 유형은 5분 동안 캐시됩니다 - 다른 공격 유형으로 시도해 보세요 |
> `php artisan migrate`만 실행하는 것으로는 충분하지 않습니다. 마이그레이션 파일은 패키지 내부에 있으며 먼저 앱의 `database/migrations/`로 게시해야 합니다. 이 문제가 발생하면 의사 점검이 정확한 명령을 출력합니다.
**"API가 401 Unauthorized를 반환합니다"**
아래의 [API 인증](#api-authentication)을 참조하세요.
**"대시보드에 404가 표시됩니다"**
대시보드는 기본적으로 비활성화되어 있습니다. `.env`에 `THREAT_DETECTION_DASHBOARD=true`를 추가하고 라우트 캐시를 지우세요:```bash
php artisan route:clear
/wp-admin, /.env, /phpmyadmin, /actuator 등)를 노리는 정찰 프로브를 50개 이상의 기본 프로브 경로로 탐지application/json) 요청 본문 모두 검사strict, balanced(기본값), - 민감도 조절 가능이 패키지는 .env 변경 없이 작동합니다. 아래 모든 값은 선택 사항입니다. 기본값을 재정의하려는 경우에만 추가하세요.```env
THREAT_DETECTION_ENABLED=true
THREAT_DETECTION_MODE=balanced
### 탐지 모드
| 모드 | 신뢰도 임계값 | 동작 |
|------|---------------------|----------|
| `strict` | 0 (모든 항목 기록) | 모든 패턴 활성화, 최저 임계값. 모든 것을 포착하지만 정상 트래픽을 플래그할 수 있음. |
| `balanced` | 10 | 기본값. 신뢰도 점수 활성화, 표준 임계값. 대부분의 앱에 적합. |
| `relaxed` | 40 | 높은 심각도 패턴만 트리거. 오탐이 빈번한 콘텐츠 중심 사이트에 가장 적합. |
### 활성화된 환경
기본적으로 탐지는 `production`, `staging`, `local`에서 실행됩니다. 변경하려면 구성을 게시하고 편집하세요:```php
'enabled_environments' => ['production', 'staging', 'local'],
테스트 스위트에서 탐지를 비활성화하려면 APP_ENV=testing(위 목록에 없음)을 설정하거나 phpunit.xml에 추가하세요:```xml
### 구성 참조
구성 파일을 게시하여 사용 가능한 모든 옵션을 확인하세요:```bash
php artisan vendor:publish --tag=threat-detection-config
주요 구성 섹션: skip_paths(건너뛸 경로), only_paths(화이트리스트 모드), auth_paths(로그인 경로 스마트 감지), content_paths(높음 경고 억제), safe_fields(스캔에서 특정 필드 제외), safe_paths(중첩 JSON용 경로 인식 필드 제외), probe_tracking(404 프로브 감지), context_weights(점수 가중치), threat_levels(심각도 키워드 매핑), api_route_filtering(API 경로에서 낮음/중간 억제), queue(비동기 처리), retention(자동 삭제), max_detections_per_request(성능 상한), dashboard.guard / (인증 모드).
only_paths)앱에 많은 경로가 있지만 일부만 신경 쓰는 경우 only_paths를 사용하여 해당 경로만 스캔할 수 있습니다. 다른 모든 경로는 자동으로 건너뛰어집니다 - 미들웨어 오버헤드가 전혀 없습니다.```php
// config/threat-detection.php
'only_paths' => [
'admin/',
'api/',
'login',
'register',
],
비워 두면(기본값) 모든 라우트를 스캔합니다(`skip_paths` 적용 대상). 두 옵션이 모두 설정된 경우 `only_paths`가 먼저 확인된 후, 일치하는 집합 내에서 `skip_paths`가 적용됩니다.
### 큐 지원
기본적으로 위협 로깅은 요청 주기 내에서 동기적으로 수행됩니다. 트래픽이 많은 애플리케이션의 경우 DB 쓰기와 Slack 알림을 큐로 오프로드할 수 있습니다.```env
THREAT_DETECTION_QUEUE=true
THREAT_DETECTION_QUEUE_CONNECTION=redis
THREAT_DETECTION_QUEUE_NAME=threat-logs
이 작업은 StoreThreatLog 작업(재시도 3회, 백오프 10초/30초)을 디스패치합니다. 탐지는 여전히 실시간으로 이루어지며, 쓰기만 지연됩니다.
일일 일정에 따라 오래된 위협 로그를 자동으로 삭제합니다:```env THREAT_DETECTION_RETENTION=true THREAT_DETECTION_RETENTION_DAYS=90
Laravel의 스케줄러가 실행 중이어야 합니다(`php artisan schedule:run`). `threat-detection:purge`를 통해 매일 02:00에 실행됩니다.
### ThreatDetected 이벤트
확인된 모든 위협은 수신 대기할 수 있는 `ThreatDetected` 이벤트를 디스패치합니다:```php
// app/Providers/EventServiceProvider.php
use JayAnta\ThreatDetection\Events\ThreatDetected;
protected $listen = [
ThreatDetected::class => [
YourCustomListener::class,
],
];
이 이벤트는 $threatLog(전체 DB 행 배열), $ipAddress, $threatLevel을 전달합니다. 이를 사용하여 사용자 지정 작업을 트리거할 수 있습니다 - Telegram 알림 전송, 차단 목록 업데이트, SIEM에 공급 등.
클라이언트가 구성된 DDoS 임계값(ddos.threshold 요청이 ddos.window초 내에 발생)을 초과하면 위협 로그 항목과 함께 DdosThresholdExceeded 이벤트가 디스패치됩니다.```php
use JayAnta\ThreatDetection\Events\DdosThresholdExceeded;
protected $listen = [ DdosThresholdExceeded::class => [ YourFloodListener::class, ], ];
이벤트는 `$ipAddress`, `$requestCount`, `$threshold`, `$windowSeconds`를 전달합니다. 이 이벤트는 IP당 중복 제거 창(dedup window)당 한 번으로 제한되며(로그 행과 동일한 스로틀), 따라서 플러드(flood)가 리스너를 압도할 수 없습니다. 알림 용도로 사용하거나 외부 차단 저장소를 공급하는 데 사용하세요. 임계값을 초과한 클라이언트를 *거부*하려면 자체 미들웨어에서 `ThreatDetection::isDdosThresholdExceeded($ip)`를 대신 사용하세요 — [데이터에 대한 조치](#acting-on-the-data-operator-side-blocking)를 참조하세요.
---
## Slack 알림
Slack 알림은 기본적으로 비활성화되어 있습니다. 활성화하려면:```env
THREAT_DETECTION_NOTIFICATIONS=true
THREAT_DETECTION_SLACK_WEBHOOK=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
THREAT_DETECTION_SLACK_CHANNEL=#threat-alerts
기본적으로 높은 심각도의 위협만 알림을 트리거합니다(notify_levels를 config에서 설정하여 변경 가능).
Laravel 10: 내장된 SlackMessage 알림 클래스를 사용합니다. 추가 패키지가 필요 없습니다.
Laravel 11+: 내장된 Slack 채널이 제거되었습니다. 이 패키지는 이를 자동으로 감지하여 Slack URL에 원시 HTTP POST 웹훅을 전송합니다. 추가 패키지가 필요 없습니다. 전체 알림 채널을 선호하는 경우 다음을 설치하세요:```bash composer require laravel/slack-notification-channel
## Dashboard
<p align="center">
<img src="https://assets.kitploit.com/production/public/readmes/12500/fc7950bd0cc6323bcc2d62b03e31c99edc7450b0ef76f9cbfa5133b527b25269.png" alt="위협 탐지 대시보드 — 통계, 7일 타임라인, 실시간 위협 로그, 상위 공격 IP, 국가별 위협" width="100%">
</p>
이 패키지에는 다크 모드 대시보드(Alpine.js + Tailwind CDN — 빌드 단계 불필요)가 내장되어 있습니다.```
+-------------------------------------------------------------------------+
| Threat Detection Dashboard |
+-------------------------------------------------------------------------+
| Total: 847 | High: 23 | Med: 156 | Low: 668 | IPs: 94 |
+-------------------------------------------------------------------------+
| [Timeline Chart - 7 Day Stacked Bar] |
+-------------------------------------------------------------------------+
| Search: [___________] Level: [All] |
| Time IP Type Level Confidence Actions |
| Mar 2 14:02 185.220.101.4 SQL Injection HIGH 80% [FP] |
| Mar 2 13:58 45.33.32.156 XSS Script Tag HIGH 65% [FP] |
| Mar 2 13:45 192.168.1.10 Scanner: Nikto MED 35% [FP] |
+-------------------------------------------------------------------------+
| Top IPs | Threats by Country |
| 185.220.101.4 [23] | US 234 |
| 45.33.32.156 [18] | CN 156 |
| 103.152.220.1 [12] | RU 98 |
+-------------------------------------------------------------------------+
.env에 다음을 추가하세요:```env
THREAT_DETECTION_DASHBOARD=true
`http://your-app.test/threat-detection`를 방문하세요.
### 로컬 개발 중 접근하기
대시보드는 기본적으로 `['web', 'auth']` 미들웨어를 사용하므로 사용자는 로그인해야 합니다. 앱에 아직 인증 기능이 없다면, 대신 자신의 머신으로만 제한하세요:```env
THREAT_DETECTION_DASHBOARD_GUARD=ip
THREAT_DETECTION_DASHBOARD_IPS=127.0.0.1
모든 가드 옵션과, 엔드포인트에서 탐지를 비활성화하는 별도의 가드는 대시보드 및 API 인증에서 다룹니다.
대시보드에 빈 데이터가 표시되는 경우, 페이지는 로드되었지만 API 호출이 실행되지 않은 것입니다. API 인증을 참조하세요.
이 패키지는 사용자 정의 대시보드 또는 통합을 구축하기 위한 15개의 REST 엔드포인트를 제공합니다.
API 라우트는 기본적으로 auth:sanctum 미들웨어를 사용합니다. 패키지는 이를 원활하게 처리합니다:
['api']만으로 대체합니다. API는 인증 없이 작동합니다.Sanctum을 사용하지 않지만 API를 보호하려는 경우, 두 가지 옵션이 있습니다:
옵션 1 - 내장 인증 가드 사용:```env THREAT_DETECTION_API_GUARD=auth
**옵션 2 - 미들웨어를 직접 변경하기:**```php
// config/threat-detection.php
'api' => [
'enabled' => true,
'prefix' => 'api/threat-detection',
'middleware' => ['api', 'auth'], // or 'auth:your-guard'
],
로컬 테스트용 (Sanctum이 접근을 차단하는 경우), 일시적으로 다음을 변경하세요:```php 'middleware' => ['api'], // remove 'auth:sanctum'
> 프로덕션에 배포하기 전에 인증을 복원하세요.
### 엔드포인트 참조
| 메서드 | 엔드포인트 | 설명 |
|--------|----------|-------------|
| GET | `/api/threat-detection/threats` | 위협 목록 (페이지네이션, 필터링 가능) |
| GET | `/api/threat-detection/threats/{id}` | 단일 위협 상세 정보 |
| POST | `/api/threat-detection/threats/{id}/false-positive` | 위협을 오탐(false positive)으로 표시 |
| GET | `/api/threat-detection/stats` | 전체 통계 |
| GET | `/api/threat-detection/summary` | 유형, 수준, IP별 상세 분석 |
| GET | `/api/threat-detection/live-count` | 최근 1시간 내 위협 |
| GET | `/api/threat-detection/by-country` | 국가별 그룹화 |
| GET | `/api/threat-detection/by-cloud-provider` | 클라우드 제공업체별 그룹화 |
| GET | `/api/threat-detection/top-ips` | 상위 공격 IP |
| GET | `/api/threat-detection/timeline` | 위협 타임라인 (차트용) |
| GET | `/api/threat-detection/ip-stats?ip=x.x.x.x` | 특정 IP에 대한 통계 |
| GET | `/api/threat-detection/correlation` | 상관관계 분석 |
| GET | `/api/threat-detection/export` | CSV로 내보내기 |
| GET | `/api/threat-detection/exclusion-rules` | 제외 규칙 목록 |
| DELETE | `/api/threat-detection/exclusion-rules/{id}` | 제외 규칙 삭제 |
### `/threats`에 대한 쿼리 매개변수
| 매개변수 | 설명 |
|-----------|-------------|
| `keyword` | IP, URL, 유형에서 검색 |
| `ip` | IP 주소로 필터링 |
| `level` | 위협 수준으로 필터링 (`high`, `medium`, `low`) |
| `type` | 위협 유형으로 필터링 |
| `country` | 국가 코드로 필터링 |
| `is_foreign` | 외국 IP 필터링 (`true`/`false`) |
| `cloud_provider` | 클라우드 제공업체로 필터링 |
| `is_false_positive` | 오탐 상태로 필터링 (`true`/`false`) |
| `date_from` / `date_to` | 날짜 범위 필터 |
| `per_page` | 페이지당 항목 수 (기본값: 20, 최대: 100) |
### API 응답 예시
**GET `/api/threat-detection/stats`:**```json
{
"success": true,
"data": {
"total_threats": 847,
"high_severity": 23,
"medium_severity": 156,
"low_severity": 668,
"unique_ips": 94,
"foreign_ips": 67,
"cloud_attacks": 12,
"today": 34,
"last_hour": 5
}
}
Vue.js:```javascript async mounted() { const response = await fetch('/api/threat-detection/stats'); this.stats = await response.json();
const threats = await fetch('/api/threat-detection/threats?per_page=20');
this.threats = await threats.json();
}
**React:**```jsx
useEffect(() => {
fetch('/api/threat-detection/stats')
.then(res => res.json())
.then(data => setStats(data));
}, []);
API가
auth:sanctum을 사용하는 경우, 인증 헤더를 포함하거나 쿠키 기반 요청을 위해 Sanctum SPA 인증을 구성하세요.
php artisan threat-detection:doctor
php artisan threat-detection:stats
php artisan threat-detection:enrich --days=7
php artisan threat-detection:purge --days=30
php artisan threat-detection:export-fail2ban --level=high --since=24h --min-hits=5 php artisan threat-detection:export-fail2ban --format=plain > /tmp/banlist.txt
php artisan threat-detection:export-blocklist --format=nginx > /etc/nginx/blocklist.conf php artisan threat-detection:export-blocklist --format=apache > .htaccess-deny php artisan threat-detection:export-blocklist --format=csv --since=7d
---
## 데이터 처리 (운영자 측 차단)
이 패키지는 요청을 차단하지 않습니다. 그것이 이 패키지의 정체성이며, 기본 동작이 아닙니다. 위의 내보내기(export)들은
이미 운영 중인 차단 계층(fail2ban, nginx, 엣지 WAF)에 데이터를 공급합니다. 그러나 일부 배포 환경에는
공급할 계층이 없습니다. 예를 들어 공유 호스팅, PaaS, 또는 통제할 수 없는 로드 밸런서 뒤의 컨테이너가 그렇습니다.
이러한 경우, 패키지는 *결정(decision)* 사항을 헬퍼(helper)로 노출하며, 차단 미들웨어는 직접 작성합니다.
내보내기와 동일한 아키텍처입니다: **지능은 우리가 제공하고, 거부는 여러분이 제공합니다.**```php
// app/Http/Middleware/EnforceThreatDecisions.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use JayAnta\ThreatDetection\Facades\ThreatDetection;
class EnforceThreatDecisions
{
public function handle(Request $request, Closure $next)
{
$ip = (string) $request->ip();
// Static operator denylist (config: blocklisted_ips).
// CIDR supported; whitelisted_ips wins on overlap.
if (ThreatDetection::isBlocklisted($ip)) {
abort(403);
}
// Volumetric flood: refuse over-threshold clients until the window resets.
if (ThreatDetection::isDdosThresholdExceeded($ip)) {
return response('Too Many Requests', 429, [
'Retry-After' => (string) config('threat-detection.ddos.window', 60),
]);
}
return $next($request);
}
}
IP에 대해 강제 적용하기 전에
TrustProxies를 구성하세요.위의 모든 내용은
$request->ip()을 기준으로 동작합니다. 로드 밸런서, CDN 또는 리버스 프록시 뒤에서는 Laravel이 신뢰할 프록시를 알려줘야 클라이언트 IP를 반환합니다. 그렇지 않으면 두 가지 문제가 동시에 발생합니다: 모든 요청이 프록시에서 온 것처럼 보여 차단 목록 항목이 모든 트래픽을 차단하거나 전혀 차단하지 않게 됩니다. 더 나쁜 것은, 앱이 신뢰해서는 안 되는 전달 헤더를 신뢰하면 공격자가X-Forwarded-For를 설정하고 차단 목록을 그대로 통과할 수 있다는 점입니다.이는
whitelisted_ips보다 여기서 더 중요합니다. 잘못된 허용 목록 일치는 단지 패키지가 건너뛸 수 있었던 요청을 검사하게 할 뿐입니다: 안전하게 실패합니다. 트래픽을 거부하는 데 사용되는 차단 목록은 열린 상태로 실패합니다 — 주소가 차단되었다고 믿지만 실제로는 차단되지 않은 것입니다. 두 헬퍼 중 하나에 의존하여 강제 적용하기 전에app/Http/Middleware/TrustProxies.php(또는 Laravel 11+의bootstrap/app.php에 있는trustProxies호출)를 확인하세요.
전역으로 등록하세요(탐지 미들웨어 앞에 등록해도 괜찮습니다 — 헬퍼는 설정과 캐시를 읽을 뿐, 미들웨어 순서에 의존하지 않습니다):```php // bootstrap/app.php (Laravel 11+) ->withMiddleware(function ($middleware) { $middleware->prepend(\App\Http\Middleware\EnforceThreatDecisions::class); })
도우미:
| 도우미 | 반환값 | 기반 |
|---|---|---|
| `ThreatDetection::isBlocklisted($ip)` | `bool` | `blocklisted_ips` 설정 (CIDR은 `IpUtils`를 통해 처리, 화이트리스트가 우선) |
| `ThreatDetection::isWhitelisted($ip)` | `bool` | `whitelisted_ips` 설정 |
| `ThreatDetection::ddosRequestCount($ip)` | `int` | 탐지 미들웨어가 유지하는 플러드 카운터 |
| `ThreatDetection::isDdosThresholdExceeded($ip)` | `bool` | 해당 카운터와 `ddos.threshold` 비교 |
참고:
- **차단 목록은 정적이며 운영자가 유지 관리합니다.** 패키지 내 어떤 기능도 여기에 항목을
추가하지 않습니다 — fail2ban 감옥이 내리는 것과 동일한 판단("대시보드를 확인했습니다. 이 /24
는 적대적입니다")을 앱 내부에서 실행할 뿐입니다.
- DDoS 카운터는 탐지에 도달한 요청만 집계하며(`skip_paths`, 화이트리스트 IP,
비활성화된 환경은 절대 집계되지 않음), DDoS 탐지가 비활성화된 캐시 드라이버(`file`, `database`, `null`)에서는
0으로 유지됩니다.
- 클라이언트가 임계값을 초과하면 [`DdosThresholdExceeded` 이벤트](#ddosthresholdexceeded-event)도
디스패치됩니다 — 알림 또는 외부 차단 목록 연동에 유용합니다. 다만 리스너에서 `abort()`를
호출하지 마세요: 리스너는 탐지 미들웨어의 fail-open `try/catch` 내부에서 실행되므로,
거부 처리는 위에서 설명한 대로 자체 미들웨어에 두어야 합니다.
---
## 404 프로브 추적
패키지는 정찰 프로브를 탐지합니다 — WordPress나 phpMyAdmin이 아닌 사이트에서 `/wp-admin`, `/.env`, `/phpmyadmin` 같은 알려진 취약 경로를 공격하는 봇입니다. 이들은 악성 페이로드가 없으며, 경로 자체가 신호입니다.
페이로드 기반 탐지와 별개로 `[probe]` 유형 태그로 기록됩니다. 프로브 요청에 악성 페이로드도 포함된 경우, 둘 다 독립적으로 기록됩니다.
50개 이상의 프로브 경로로 기본 활성화되어 있습니다. `config/threat-detection.php`에서 사용자 지정하세요:```php
'probe_tracking' => [
'enabled' => true,
'default_level' => 'medium',
'paths' => [
'/wp-admin' => 'WordPress Admin',
'/wp-admin/*' => 'WordPress Admin',
'/.env' => 'Environment File',
'/phpmyadmin' => 'phpMyAdmin',
'/actuator/*' => 'Spring Actuator',
// Add your own probe paths...
],
],
THREAT_DETECTION_PROBE_TRACKING=false로 비활성화합니다.
특정 양식 필드에 HTML, SQL 키워드 또는 코드(예: CMS 편집기, 코드 스니펫 입력)가 정당하게 포함된 경우, 해당 필드를 스캔에서 제외할 수 있습니다:```php // config/threat-detection.php 'safe_fields' => ['content', 'body', 'html', 'description', 'code'],
Fields listed here are stripped from query params and the request body - both form-encoded and JSON (`application/json`) - before detection runs. Other fields on the same request are still fully scanned.
### Safe Paths (path-aware, for nested JSON APIs)
`safe_fields` matches a key name **anywhere** it appears. For nested JSON APIs that's often too broad — you may want to exempt one specific field's value without exempting that key everywhere. Use `safe_paths`, which matches by dot-notation **path** and supports `fnmatch` wildcards:```php
// config/threat-detection.php
'safe_paths' => ['search.query', 'filters.*.value'],
예를 들어, search.query는 {"search": {"query": "..."}}의 값을 면제합니다(텍스트에 SELECT 같은 단어가 정당하게 포함될 수 있는 검색 상자). 반면 요청의 다른 위치에 있는 query 필드는 여전히 스캔됩니다. 나열되지 않은 모든 항목은 이전과 동일하게 스캔됩니다.
정규식만으로는 모든 제약 조건을 표현할 수 없습니다. 어떤 12자리 연속 숫자는 Aadhaar 패턴과 일치하지만, 실제 Aadhaar 번호는 Verhoeff 체크섬도 통과합니다. 패턴 레이블(기본 또는 사용자 정의)을 명명된 검증기에 매핑하면, 정규식 일치는 일치된 값 중 하나 이상이 이를 통과할 때만 탐지로 간주됩니다.```php // config/threat-detection.php 'pattern_validators' => [ 'Aadhaar Number Detected' => 'verhoeff', // shipped default ],
사용 가능한 검증기:
| 검증기 | 체크섬 | 일반적인 용도 |
|------------|----------|-----------------------|
| `verhoeff` | Verhoeff | Aadhaar 번호 |
| `luhn` | Luhn | 신용/직불 카드 번호 |
제공된 매핑을 사용하면 우연히 12자리인 타임스탬프, 주문 ID 및 바코드는 더 이상 PII로 기록되지 않지만, 실제 Aadhaar 번호는 여전히 기록됩니다. 여러 값이 일치하고 그중 하나만 체크섬을 통과하더라도 탐지는 여전히 작동합니다. 노이즈 속의 실제 번호는 여전히 유출입니다.
체크섬 기반 카드 탐지를 위해 자체 패턴과 검증기를 함께 사용하세요:```php
'custom_patterns' => ['/\b(?:\d[ -]?){13,19}\b/' => 'Card Number Detected'],
'pattern_validators' => ['Card Number Detected' => 'luhn'],
알 수 없는 검증기 이름은 **열린 채로 실패(fails open)**합니다 — 매치는 검증되지 않은 채로 집계되고 경고가 한 번 기록되므로, 오타가 감지 패턴을 조용히 비활성화할 수 없습니다. 이 기능 이전에 게시된 구성은 단순히 해당 키가 없으므로 기존의 정확한 동작을 유지합니다.
민감한 데이터를 탐지한다는 것은 예전에는 그것을 저장한다는 의미였습니다. 휴대폰 번호, PAN, 은행 계좌를 담은 프로필 양식은 세 개의 PII 패턴에 걸릴 것이고, 기록된 세 개의 행 각각은 요청 본문 전체를 그대로 보관했습니다 — 전체 보존 기간 동안 유지되며, 대시보드나 데이터베이스 접근 권한이 있는 사람이라면 누구나 읽을 수 있었습니다. 쿼리 문자열의 값도 url 열에 들어갔습니다. 탐지기는 경고하는 바로 그 내용의 두 번째이자 집중된 사본이 되었습니다.
v1.7.0부터 기본적으로 활성화됩니다. 레이블이 나열된 패턴이 발화하면, 일치된 값은 저장된 페이로드와 URL에서 마스킹됩니다:``` BODY: {"name":"Jane Doe","mobile":"[REDACTED]","pan":"[REDACTED]","bank_account":"[REDACTED]"}
알림, 엔드포인트, 필드 이름, 공격 IP는 모두 유지되며, 값만 삭제됩니다. 삭제는 *탐지 이후에* 실행되므로 놓치는 것이 없습니다.```php
// config/threat-detection.php
'redact' => [
'enabled' => env('THREAT_DETECTION_REDACT', true),
'mask' => '[REDACTED]',
'labels' => ['Aadhaar Number Detected', 'PAN Number Detected', /* ... */],
],
공격 페이로드는 의도적으로 그대로 유지됩니다. 주입 문자열은 증거이지 비밀이 아니며, 이를 마스킹하면 조사가 망가질 것입니다. 사용자가 나열한 레이블만 수정됩니다.
이 기능은 안전 필드를 대체하지 않습니다. 안전 필드는 필드가 스캔되는 것을 막고, 수정(redaction)은 스캔은 계속하면서 저장을 막을 수 있게 합니다. 포렌식을 위해 전체 페이로드가 필요하다면
THREAT_DETECTION_REDACT=false로 설정하세요.
대시보드와 API는 .env를 통해 구성 가능한 인증 가드를 지원합니다:```env
THREAT_DETECTION_DASHBOARD_GUARD=auth
THREAT_DETECTION_DASHBOARD_GUARD=role THREAT_DETECTION_DASHBOARD_ROLE=admin
THREAT_DETECTION_DASHBOARD_GUARD=ip THREAT_DETECTION_DASHBOARD_IPS=127.0.0.1,10.0.0.0/8
The same options are available for API routes with `THREAT_DETECTION_API_GUARD`.
When `guard=none` (default), the package logs a warning once per day to remind you to configure authentication.
The guard **fails closed**: an unrecognised guard value (e.g. a typo) is denied with a 403 and a logged warning rather than silently granting access, and `guard=role` denies (with a warning) when the authenticated user model has no `hasRole()` method.
### Disabling a detection needs more than read access
Marking a threat as a false positive and deleting an exclusion rule both silence a detection type for everyone, which is a different privilege from reading the log. Those two endpoints are checked against a separate guard:```env
# Options: none, auth, role, ip. Default: role
THREAT_DETECTION_API_WRITE_GUARD=role
이 라우트에만 적용되므로, 읽기 및 대시보드는 THREAT_DETECTION_API_GUARD가 지정한 대로 정확히 동작합니다. 이 설정이 없으면, 애플리케이션의 인증된 사용자라면 누구나 감지 기능을 끌 수 있습니다.
사용자 모델에 hasRole()이 없다면 =auth를 사용하세요. 1.7.0 이전의 동작(인증된 사용자라면 누구나 감지를 비활성화할 수 있었던)으로 되돌리려면 =none을 사용하세요. 해당 값이 설정된 동안 threat-detection:doctor가 경고를 표시합니다.
대시보드 ↔ API 참고: 기본 제공 대시보드는 브라우저 세션 쿠키를 사용하여 API 라우트에서 데이터를 가져옵니다. API 라우트가
auth:sanctum으로 보호되는 경우, Sanctum stateful/SPA 인증을 구성(또는 대시보드가 쿠키 인증 가드를 가리키도록 설정)하여 해당 AJAX 호출이 승인되도록 하세요. 그렇지 않으면 대시보드가 빈 상태로 렌더링됩니다.
config/threat-detection.php에서 자체 감지 정규식 패턴을 추가하세요:```php
'custom_patterns' => [
'/your-regex-here/i' => 'Your Threat Label',
],
**예시 - 사용자 정의 관리자 엔드포인트 탐지 탐색 감지:**```php
'/\/my-admin-panel/i' => 'Custom Admin Panel Probe',
기존의 문자열 형식과 함께, 패턴의 값은 완전한 제어를 위해 배열이 될 수 있습니다:```php 'custom_patterns' => [ '/\b(?:\d[ -]?){13,19}\b/' => [ 'label' => 'Card Number Detected', // required 'level' => 'high', // low|medium|high — overrides keyword derivation 'contexts' => ['query', 'body'], // query|body|headers — default: all segments 'validator' => 'luhn', // post-match checksum, wins over pattern_validators ], ],
- **`level`**은 `threat_levels` 키워드에서 라벨을 파생하는 대신 위협 수준을 직접 설정합니다.
- **`contexts`**는 스캔을 특정 요청 세그먼트로 제한합니다. 예를 들어 본문에서만 의미가 있는 카드 패턴이 헤더의 숫자 연속과 일치하지 않도록 합니다.
- **`validator`**는 인라인 사후 일치 검사를 지정합니다([사후 일치 검증기](#post-match-validators-checksum-aware-false-positive-reduction) 참조). 이는 `pattern_validators` 라벨 맵보다 우선합니다.
문자열 및 배열 항목은 동일한 구성에서 자유롭게 혼합됩니다. 잘못된 옵션은 **열린 상태로 실패**합니다 — 패턴은 여전히 제한 없이 스캔되고 경고가 기록됩니다 — 따라서 구성 오류가 감지를 조용히 비활성화하거나 좁힐 수 없습니다.
> **참고:** `/wp-login.php`, `/.env`, `/phpmyadmin`과 같은 일반적인 프로브 경로는 이제 [404 프로브 추적](#404-probe-tracking) 기능에서 자동으로 처리됩니다. 이러한 항목에 대해 사용자 지정 패턴이 필요하지 않습니다.
각 패턴의 위협 수준은 라벨의 키워드를 `threat_levels` 구성과 일치시켜 자동으로 결정됩니다:```php
'threat_levels' => [
'high' => ['XSS', 'SQL Injection', 'SQL DDL', 'SQL DML', 'SQL File', 'SQL Hex', 'RCE', ..., 'Shellshock', 'Spring4Shell', 'PowerShell', 'CRLF', 'Null Byte', 'SSTI', 'Java', 'LDAP', 'XPath', 'PHP assert', ...],
'medium' => ['Directory Traversal', 'LFI', 'SSRF', 'Sensitive', 'Config', ..., 'Open Redirect', 'LF Injection', 'GraphQL', 'Spring Boot Actuator', ...],
'low' => ['User-Agent', 'JS Redirect', 'SEO Bot', 'Empty', 'Rate', 'Command-line Downloader', 'DNS Rebinding'],
],
라벨이 어떤 키워드와도 일치하지 않으면 위협은 기본적으로 low 심각도로 처리됩니다.
잘못된 정규식 패턴은 자동으로 건너뛰고 경고로 기록됩니다. 애플리케이션이 중단되지는 않습니다.
미들웨어 외부에서 위협 데이터에 프로그래밍 방식으로 접근하려면:```php use JayAnta\ThreatDetection\Facades\ThreatDetection;
// Get attack statistics for a specific IP $stats = ThreatDetection::getIpStatistics('192.168.1.1');
// Detect coordinated attacks (multiple IPs targeting same URL within 15 minutes) $attacks = ThreatDetection::detectCoordinatedAttacks(15, 3);
// Detect attack campaigns (same threat type from 5+ IPs in last 24 hours) $campaigns = ThreatDetection::detectAttackCampaigns(24);
// Get a summary of all correlation data $summary = ThreatDetection::getCorrelationSummary();
// Operator-side decision helpers (see "Acting on the Data") $blocked = ThreatDetection::isBlocklisted('203.0.113.7'); // static denylist, CIDR, whitelist wins $trusted = ThreatDetection::isWhitelisted('10.0.0.5'); $count = ThreatDetection::ddosRequestCount('203.0.113.7'); // requests in the current DDoS window $flooded = ThreatDetection::isDdosThresholdExceeded('203.0.113.7');
---
## 프로덕션 배포 준비
이 패키지는 설계상 수동적입니다. 요청을 차단, 거부 또는 변경하지 않으며, 탐지 미들웨어는 전체 본문을 `try/catch`로 감싸므로 탐지 실패가 앱을 중단시킬 수 없습니다. 합리적인 기본값을 제공하며 실행에 외부 서비스가 필요하지 않습니다. 라이브 배포 전에 다음 체크리스트를 확인하는 것이 좋습니다:
1. **대시보드와 API 보호.** 둘 다 기본적으로 `guard = none`으로 설정되어 설정 없이 첫 실행이 가능하며, 보호되지 않은 상태에서는 매일 경고를 기록합니다. 프로덕션 전에 가드를 설정하세요 - `THREAT_DETECTION_DASHBOARD_GUARD` 및 `THREAT_DETECTION_API_GUARD` (`auth`, `role` 또는 `ip`). 인식할 수 없는 값이나 `hasRole()`이 없는 사용자 모델에 대한 `role` 가드는 이제 **실패 시 차단**(403)되므로 오타로 인해 데이터가 조용히 노출되지 않습니다. 탐지 비활성화는 기본값이 `role`인 `THREAT_DETECTION_API_WRITE_GUARD`로 별도로 제어됩니다. [대시보드 및 API 인증](#dashboard-and-api-authentication)을 참조하세요.
2. **마이그레이션 실행** (`vendor:publish --tag=threat-detection-migrations && migrate`). 다시 게시해도 안전합니다 - 이미 게시된 마이그레이션은 건너뜁니다.
3. **탐지 모드 선택.** `balanced`(기본값)은 대부분의 앱에 적합합니다. 콘텐츠가 많은 사이트에는 `relaxed`, 높은 보안이 필요한 표면에는 `strict`를 사용하세요. `content_paths`, `safe_fields` 및 `min_confidence`로 조정하세요 - [오탐지 줄이기](#reducing-false-positives)를 참조하세요.
4. **지역별 PII / 사용자 정의 패턴 검토.** 기본값은 인도 중심(Aadhaar, PAN, IFSC)이며 광범위한 숫자 패턴(예: 은행 계좌)은 인증 경로 외부의 긴 숫자 ID와 일치할 수 있습니다. 지역과 앱에 맞게 `custom_patterns`를 교체하거나 축소하고, 콘텐츠가 많은 경로를 `auth_paths` / `content_paths`에 추가하세요.
5. **볼륨이 예상되면 보존 기능 켜기:** `THREAT_DETECTION_RETENTION=true` (스케줄러를 통해 자동 삭제). Laravel 스케줄러(`schedule:run`)가 cron으로 실행되어야 합니다.
6. **선택적 추가 기능, 모두 기본적으로 꺼짐:** Slack 알림(`THREAT_DETECTION_NOTIFICATIONS`), 지리 정보 보강(`php artisan threat-detection:enrich` - 무료 ip-api.com에 아웃바운드 호출을 하는 유일한 기능), 큐 기반 쓰기(`THREAT_DETECTION_QUEUE` - 이미 큐 워커를 실행 중인 경우에만 활성화하세요. 그렇지 않으면 쓰기는 동기식이며 Redis가 필요 없습니다).
핵심 탐지 및 로깅에는 Redis, 큐 워커 또는 아웃바운드 네트워크 호출이 필요하지 않습니다.
---
## 오탐지 줄이기
이 패키지는 오탐지를 줄이기 위한 여러 도구를 제공합니다. 상황에 맞는 것을 사용하세요:
### 안전 필드 및 안전 경로
필드를 스캔에서 완전히 제외합니다. 이름으로 모든 곳에서(`safe_fields`) 또는 중첩 JSON의 점 표기법 경로(`safe_paths`)로 제외할 수 있습니다. 가장 간단하면서도 가장 단순한 접근 방식입니다 - 필드를 건너뛰므로 해당 필드에서는 탐지가 전혀 실행되지 않습니다.
전체 세부 정보 및 예시: [안전 필드](#safe-fields-false-positive-reduction).
### 콘텐츠 경로 억제
CMS 편집자, 블로그 게시물 양식 또는 사용자가 풍부한 콘텐츠를 제출하는 댓글 섹션이 있는 경우 해당 경로는 종종 오탐지를 유발합니다(예: `<script>` 코드 샘플이 포함된 블로그 게시물). 해당 경로를 추가하여 낮음/중간 알림을 억제하세요:```php
// config/threat-detection.php
'content_paths' => [
'admin/posts/*',
'admin/pages/*',
'blog/*/edit',
'comments',
],
이 경로에서는 고심각도 위협만 기록됩니다.
대시보드의 모든 위협에서 FP 버튼을 클릭하여 오탐으로 표시할 수 있습니다. 이렇게 하면:
is_false_positive = true로 표시됩니다.API를 통해 제외 규칙을 관리합니다:```bash GET /api/threat-detection/exclusion-rules DELETE /api/threat-detection/exclusion-rules/{id}
### 신뢰도 점수
모든 위협은 다음 사항을 기반으로 신뢰도 점수(0-100)를 받습니다:
- 동일한 요청 내 패턴 매칭 횟수
- 매칭된 패턴의 심각도
- 패턴이 발견된 위치(쿼리 문자열 > 헤더 > 본문)
- 사용자 에이전트가 알려진 공격 도구와 일치하는지 여부
- 현재 탐지 모드
탐지 모드의 신뢰도 임계값보다 낮은 위협은 기록되지 않습니다([탐지 모드](#detection-modes) 참조).
---
## 탐지된 공격 유형
| 범주 | 예시 |
|----------|---------|
| **SQL 인젝션** | UNION, boolean, time-based, CHAR 인코딩, DDL(DROP/ALTER/CREATE), DML(INSERT/UPDATE/DELETE), 파일 작업(INTO OUTFILE, LOAD_FILE), ORDER BY 열거, 16진수 문자열, UNHEX |
| **NoSQL 인젝션** | MongoDB $ne, $gt, $regex, $where 연산자 |
| **XSS** | 스크립트 태그, SVG 이벤트 핸들러(`<svg onload=`), HTML 이벤트 핸들러(`<body onload=`, `<img onerror=`), CSS 표현식, JavaScript URI, DOM 조작 |
| **코드 실행** | RCE 셸 함수, PHP 역직렬화, Java 역직렬화(base64 + hex 매직 바이트), 템플릿 인젝션(Blade, JSP, ASP, Jinja2, Velocity), eval(), base64 디코드, PHP assert(), create_function(), preg_replace /e |
| **SSTI** | 수학적 프로브(`{{7*7}}`), Jinja2 import/config, Velocity 템플릿, 표현 언어 |
| **명령 인젝션** | Linux(셸 함수, 명령 체인, curl, wget, nc), Windows(cmd.exe, PowerShell, wscript, cscript, net user) |
| **파일 접근** | 디렉터리 트래버설, LFI/RFI 프로토콜, 민감한 파일 프로브(.env, .git, composer.json) |
| **SSRF** | 로컬호스트(127.0.0.1, 0.0.0.0, ::1), AWS/GCP 메타데이터, 사설 IP, 16진수/10진수 인코딩된 로컬호스트, DNS 리바인딩(xip.io, nip.io, sslip.io) |
| **LDAP 인젝션** | LDAP 필터 조작, OR 인젝션 |
| **XPath 인젝션** | 속성 선택자, XPath 함수(contains, substring) |
| **CRLF / 헤더 인젝션** | URL 인코딩된 CRLF(`%0d%0a`), LF 인젝션, 널 바이트 인젝션 |
| **프로토콜 공격** | HTTP 요청 스머글링(CL+TE), SSI 인젝션 |
| **CVE 익스플로잇** | Shellshock(CVE-2014-6271), Spring4Shell(CVE-2022-22965), PHPUnit RCE(CVE-2017-9841), Drupalgeddon, Log4Shell |
| **프로브 추적** | WordPress(`/wp-admin`, `/wp-login.php`), 구성 파일(`/.env`, `/.git`), 데이터베이스 도구(`/phpmyadmin`), 기술 프로브(.asp, .jsp), Spring actuator, Swagger/API 문서 - 50개 이상의 경로 |
| **스캐너** | SQLMap, Nikto, Nmap, Burp Suite, FeroxBuster, FFUF, XSStrike, Dalfox, Netsparker, Qualys, Nuclei 및 20개 이상(총 53개) |
| **AI 스크레이퍼** | GPTBot, ClaudeBot, ChatGPT, ByteSpider, Cohere, Common Crawl |
| **헤드리스 브라우저** | HeadlessChrome, PhantomJS, Selenium, Puppeteer, Playwright |
| **봇** | Python 스크립트, Go HTTP 클라이언트, cURL, wget, AhrefsBot, SEMRushBot, 빈 사용자 에이전트 |
| **인증** | 무차별 대입 공격 탐지, 토큰 유출, 비밀번호 노출, 세션 ID 노출 |
| **DDoS** | 비율 기반 과도한 요청 탐지 |
| **우회 기법** | SQL 주석 삽입, 이중 URL 인코딩, HTML 엔티티 인코딩, 유니코드 이스케이프, IIS 유니코드, 16진수 이스케이프 |
| **기타** | GraphQL 인트로스펙션, 프로토타입 오염, 오픈 리다이렉트, XXE, 웹 셸, 암호화폐 채굴, PII 탐지 |
---
## 테스트 스위트 실행```bash
composer test
패키지에는 탐지 패턴, 미들웨어 동작, API 엔드포인트, 신뢰도 점수, 제외 규칙, DDoS 탐지, 우회 저항성, CVE 패턴, LDAP/XPath/SSTI 인젝션, 봇/스캐너 탐지, 프로브 추적, 내보내기 명령, 대시보드 인증, 안전 필드, 성능 최적화, 그리고 HTTP-to-DB 전체 주기 검증을 다루는 335개의 테스트(856개의 단언)가 포함되어 있습니다.
MIT 라이선스. 자세한 내용은 LICENSE를 참조하세요.
기여를 환영합니다! Pull Request를 제출해 주세요.
relaxedapi.guard