Files
obsidian/projects/netbis-sigmatch.md

176 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Netbis Sigmatch — VL 기반 자동 공격 탐지 + CF 차단
updated: 2026-04-24
tags: [netbis, security, ai-defense, wip]
---
## 개요
Netbis NPM 로그(VictoriaLogs)를 실시간 분석해 **사람 개입 없이 공격을 자동 탐지**하고 CF IP Access Rules에 challenge/block 반영. LAPI·CrowdSec·LLM·사전 정의 룰 모두 사용 안 함.
- **저장소**: https://gitea.inouter.com/kaffa/netbis-sigmatch
- **로컬 개발**: `~/netbis-sigmatch/` (Mac)
- **배포 예정**: jp1 Incus `ai-sigmatch` 컨테이너 (systemd timer 1분)
- **책임자**: kappa 직접 개발 (Heimdall 위임 X, 개발 단계)
## 핵심 설계 (v2.2, 2026-04-24 Matrix Profile 추가)
### 탐지 트리거 (OR 조건, 하나라도 충족 시 attack mode ON)
**A1. Static threshold** (급격한 볼륨 이상)
```
현재 5분 총 req > baseline_시간대(UTC hour).p95 × 2.0
OR > baseline_시간대.max × 1.5
```
**A2. CUSUM Page-Hinkley** (점진적 ramp-up 공격)
```
g_t = max(0, g_{t-1} + (x_t - μ_hour - δ)) , δ = μ × 0.3
g_t > μ × 2.0 → trigger → reset g=0
```
- μ는 시간대별 롤링 mean. static threshold를 못 넘게 살살 올리는 low-rate DDoS 커버
- 전역 g_t 1개 유지 (hour 경계 reset 문제 회피)
**A3. Global src IP entropy drop** (소수 IP 집중 공격)
```
현재 5분 트래픽의 Shannon(Counter(client_ip)) < baseline_entropy_p10(시간대별)
```
- entropy 계산은 `min_entropy_baseline_samples=20` 확보 후부터
- `uniq_ips < entropy_min_uniq_ips=30` 이면 판정 스킵 (트래픽 자체가 너무 적을 때)
**A4. Matrix Profile discord** (패턴 이상)
```
stumpy.stump(최근 7일 total_reqs 시계열, m=12)
현재 서브시퀀스의 MP > 과거 MP 분포의 p99 → discord
```
- m=12 (1시간 subsequence length)
- 완전 비파라메트릭. 학습 불필요, 자기 과거와 직접 비교
- z-normalized euclidean distance라 스케일 차이 무시, **형태 차이**만 봄
- CUSUM은 "크기 이상"만, MP는 "패턴/형태 이상" 담당 → 상보적
- 첫 사이클은 numba JIT로 ~7s, 이후 ms 단위
→ attack mode ON: top N contributor IP 일괄 challenge
**B. 개별 극단 (항상 동작)**
```
Scanner-shape: uniq_paths/reqs ≥ 0.8 AND path_entropy ≥ 7 AND reqs ≥ 300
(수백 경로를 각 1회씩 = 취약점 스캐너)
Raw extreme: reqs ≥ 1500 / 5min
(폴링 유저 상한의 약 7배)
```
### 롤링 baseline (자동 갱신)
- 매 사이클 현재 윈도우 샘플을 `baseline_samples`에 누적 (total_reqs + src_ip_entropy + uniq_ips)
- 시간대(hour_utc)별 mean/p95/max/entropy_p10/entropy_p50을 최근 7일 롤링 기준으로 실시간 계산
- **공격 판정된 윈도우 샘플은 baseline에 안 들어감** (baseline 오염 방지)
- 매 60 사이클마다 7일 초과 샘플 자동 prune
- 초기 seed는 `baseline_aggregate.py`로 1회 수집, 롤링 12+ 샘플 쌓이면 seed 대체
## 오탐 방지 특성
| 상황 | 동작 |
|------|------|
| 평상시 정상 트래픽 | 액션 0 (heavy user·폴링 유저 다 통과) |
| 평상시 섞인 취약점 스캐너 | 개별 극단 트리거 (확정 공격) |
| DDoS·대규모 공격 (급격) | static threshold (p95×2.0 / max×1.5) 발동 |
| Low-rate / 점진 ramp-up DDoS | CUSUM Page-Hinkley 누적으로 발동 |
| 소수 IP 집중 공격 (같은 볼륨) | global src IP entropy drop으로 발동 |
| 지속되는 비정형 shape (평소 한 번도 안 본 패턴) | Matrix Profile discord로 발동 |
| 가짜 공격 (baseline 경계선) | samples ≥ 10 확보 후에만 판정 (초기 1~2일은 판정 유예) |
**사람이 쓴 공격 지식 없음**:
- 특정 경로·ASN·UA 리스트 없음
- scanner-shape는 행동 통계 (uniq_paths/reqs 비율, entropy)
- attack mode는 트래픽 볼륨 이상 — 자기 과거와의 편차만 봄
## 조치 레벨
- **challenge** (Cloudflare managed_challenge, CAPTCHA, TTL 30분): 정상 유저는 한 번 풀고 통과
- **block** (TTL 24시간): challenge 통과 후에도 같은 IP가 5+ 사이클 연속 공격 시
## 개발 단계
- [x] Phase 1: feature 추출 (`fetch_features.py`)
- [x] Phase 2: 24h retrospective baseline (`collect_baseline.py`)
- [x] Phase 3-5: (폐기) IsolationForest+DBSCAN+persistence 기반
- [x] Phase 6 (v2): 집계 기반 공격 모드 + 개별 극단 시그니처
- [x] Phase 7: 롤링 baseline 자동 갱신
- [x] Phase 8 (v2.1): CUSUM Page-Hinkley + global src IP entropy drop
- [x] **Phase 9 (v2.2): Matrix Profile discord (stumpy.stump) 추가** ← 현재
- [ ] Phase 10: 장시간 관찰 (dry-run) — 진행 중 (백그라운드 `loop.py`). entropy baseline 20+ 샘플 쌓이려면 약 20시간
- [ ] Phase 11: CF IP Access Rules 호출 (managed_challenge → block)
- [ ] Phase 12: jp1 Incus 배포 (systemd timer)
## 파라미터 (사람 조정 가능)
| 파라미터 | 기본값 | 의미 |
|---------|-------|------|
| `attack_p95_multiplier` | 2.0 | 현재 req가 시간대 p95의 몇 배면 attack mode |
| `attack_max_multiplier` | 1.5 | 또는 max의 몇 배면 |
| `attack_top_n` | 20 | attack 시 challenge할 상위 IP 수 |
| `attack_contributor_min_reqs` | 200 | top IP 중 이 이상인 것만 |
| `cusum_drift_pct` | 0.3 | CUSUM 허용 드리프트 δ = μ × pct |
| `cusum_threshold_mult` | 2.0 | CUSUM 트리거 h = μ × mult |
| `min_entropy_baseline_samples` | 20 | entropy baseline 샘플 부족 시 판정 스킵 |
| `entropy_min_uniq_ips` | 30 | uniq IP 부족 시 entropy 판정 스킵 |
| `mp_subseq_len` | 12 | Matrix Profile subsequence length (5분×12=1h) |
| `mp_threshold_pct` | 0.99 | 과거 MP 분포의 이 percentile 초과 시 trigger |
| `scanner_uniq_ratio` | 0.8 | uniq_paths/reqs 임계 |
| `scanner_min_entropy` | 7.0 | path entropy 임계 (per-IP) |
| `scanner_min_reqs` | 300 | 스캐너 최소 요청 수 |
| `extreme_reqs` | 1500 | 단일 IP 극단 rate 임계 |
| `persistence_for_block` | 5 | challenge → block 승급 사이클 |
| `challenge_ttl_sec` | 1800 | 30분 |
| `block_ttl_sec` | 86400 | 24시간 |
| `baseline_rolling_days` | 7 | 롤링 윈도우 |
| `min_baseline_samples` | 10 | baseline 샘플 부족 시 판정 유예 |
## 검증 결과 (simulate.py)
- 평상시 24h 데이터: static threshold 0회 발동, **CUSUM hour별 μ 적용 시 0회 오탐**
- 마지막 5개 윈도우에 1.5x→3.5x 점진 ramp 주입: CUSUM 3회 발동 (idx 198/199/200 연속)
- CUSUM 유닛 테스트: 평상시 g=0 유지, ramp-up 시 g 누적 → h 초과 시 trigger + reset 확인
- **Matrix Profile**: 평상시 201 윈도우에서 p99 초과 ~1%. 단기 3-point spike는 MP 안 튀고 (CUSUM 담당), **1시간 sustained max×3 형상은 MP=3.46 > p99=3.19로 trigger** 확인. 역할 상보적
- 배포 후 cycle 101에서 `mp=1.87/thr=2.88` 안정 관찰 (현재 패턴이 과거와 유사 → trigger X)
## 폐기된 이전 설계
- ~~IsolationForest + DBSCAN per-IP anomaly~~ → 정상 폴링 유저를 outlier로 잡아 오탐 위험
- ~~Persistence 단독 트리거~~ → 페이지 오래 열어둔 유저 6 사이클 지속 시 오탐
- ~~사전 정의 hard rule (R1~R6)~~ → 공격 패턴 종속, 자동 시그니처 생성 취지 어긋남
## 폐기 후보 (검토 후 불채택)
- **Prophet / LSTM / Bi-LSTM** — 라벨 없음, 학습·추론 무거움, jp1 작은 컨테이너 오버킬
- **CatBoost / Random Forest 지도학습** — 라벨 필요
- **per-IP Isolation Forest / OC-SVM** — 정상 폴링 유저 오탐 (이미 폐기 이유 동일)
향후 데이터 2주+ 쌓이면 검토 예정:
- **Holt-Winters** — level+trend+seasonality. 현재 static p95×mult 대체 가능
- **River HalfSpaceTrees** — streaming Isolation Forest, 다변량 윈도우 feature 학습-예측 동시
- **ADWIN (concept drift)** — baseline 자동 window 조정 (서비스 성장 대응)
## 파일 구조
```
~/netbis-sigmatch/
├── fetch_features.py — feature 추출 (단발 조회)
├── collect_baseline.py — retrospective seed baseline 수집
├── baseline_aggregate.py — 시간대별 seed 통계 수집 (1회성)
├── inspect_baseline.py — baseline DB 탐색
├── state.py — state DB (ip_state, baseline_samples, cusum_state 등)
├── matrix_profile.py — stumpy MP discord 판정 (v2.2)
├── loop.py — 메인 실시간 루프
├── simulate.py — 과거 데이터로 로직 검증
├── baseline.db — seed snapshot 24h
├── state.db — 운영 상태 + 롤링 baseline
└── logs/ — 사이클 로그
```
## 연관 정본
- [[../services/netbis]] — NPM → VL 파이프라인
- [[../infra/security/cloudflare#Pseudo IPv4 (Class E 240/4)]] — 240/4 대역 해석
- [[../history/2026-04-24-cf-pseudo-ipv4-discovery]] — CF Pseudo IPv4 규명