Files
obsidian/projects/netbis-sigmatch.md
kaffa ae6237de21 netbis-sigmatch v2.4: A4 Matrix Profile 트리거 제거
18h DRY 가동 결과 99 distinct IP 차단 누적. attack_contributor 2,668건 중
97.5%가 MP only 시그널 발동. MP threshold p99 (1% 오탐) × 매 5분 사이클 평가
= 누적 폭주. attack_contributor의 단순 volume top이 webhook + heavy normal
user 항상 잡음.

LIVE였으면 베팅 콜백 4~5개 차단 + KR 활발 유저 + IPv6 모바일 240/4 가짜 IP
99개 차단 = 서비스 사고.

matrix_profile.py 모듈은 보존 (관찰/연구용). loop.py에서 호출만 제거.

폐기 사유: MP의 self-similar 시계열 가정이 daily seasonality 있는 트래픽과
안 맞음 + 단일 시그널이 contributor 단계로 무차별 차단 증폭
2026-04-25 10:17:24 +09:00

216 lines
12 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.4, 2026-04-25 A4 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** ~~(v2.2~v2.3)~~ — **v2.4에서 제거** (사유 아래 참조)
→ 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** (CF `block`, 내부 TTL 24시간): challenge 통과 후에도 같은 IP가 5+ 사이클 연속 공격 시
- 내부 TTL이 지나면 loop이 CF rule을 DELETE (CF Access Rules 자체 TTL 없음, sigmatch가 관리)
## CF 연동 (Phase 11, v2.3)
### 엔드포인트
- Account-level: `POST /accounts/{id}/firewall/access_rules/rules` (6개 zone 동시 적용)
- 인증: `X-Auth-Email` + `X-Auth-Key` (global_api_key). API token은 Rulesets/Firewall 403
- Vault: `secret/cloud/cloudflare-netbis` (`account_id`, `email`, `global_api_key`)
### 중복 호출 최소화
상태 변화 있을 때만 CF API 호출:
- `없음 → challenge/block`: `create_rule``rule_id` 저장
- `challenge → block`: `update_rule (PATCH mode)` (같은 rule_id 유지)
- `같은 action 지속`: **skip-same** (CF 호출 없음, sqlite state만 갱신하여 TTL 연장)
- `expire_actions`: 내부 TTL 만료 시 `delete_rule` + state clear
### Rate Limit 대응
- `min_interval_sec=0.3` (sleep), 429 시 exponential backoff
- CF API 계정 제한 1200 req/5min — 지속 공격 IP 10개 × 사이클당 skip-same 로직이라 여유
### Rule 개수 상한 (Phase 11.5)
- CF 공식: account-level IP Access Rules 최대 50,000개/account
- sigmatch 기본 상한: `--cf-max-rules 2000` (보수적)
- startup 시 `list_sigmatch_rules` 호출로 현재 카운트 초기화, create/delete 시 in-memory 갱신
- 한도 도달 시 `CFLimitExceededError``apply_cf`에서 catch → `cf_op=limit-exceeded:N/MAX`, cf_rule_id=None으로 저장. 다음 사이클 expire로 slot 생기면 자동 재시도
### Rule 태깅
notes에 `netbis-sigmatch` prefix. startup 시 `list_sigmatch_rules()`로 우리가 만든 rule만 필터 캐시.
### CLI 유틸 (`cf_client.py`)
```bash
# env: CF_ACCOUNT_ID, CF_EMAIL, CF_GLOBAL_API_KEY
uv run python cf_client.py list # 현재 sigmatch rules 확인
uv run python cf_client.py create --ip X --mode managed_challenge --note "..."
uv run python cf_client.py delete --rule-id XXX
uv run python cf_client.py purge [--yes] # sigmatch prefix 전체 삭제
```
## 개발 단계
- [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) 추가
- [x] Phase 10: bootstrap_baseline.py — 과거 24h seed로 모든 hour samples_ok 즉시 충족
- [x] Phase 11 (v2.3): CF Access Rules 호출 (managed_challenge/block) — DRY 검증 단계
- [x] **Phase 11.6 (v2.4): A4 Matrix Profile 트리거 제거** ← 현재 (false positive 누적 사고 회피)
- [ ] Phase 11 LIVE 전환: `--live` 플래그로 실 운영
- [ ] 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)~~ → 공격 패턴 종속, 자동 시그니처 생성 취지 어긋남
- ~~Matrix Profile discord (attack 트리거 용도, v2.2~v2.3)~~ → **v2.4 제거**. 사유:
- threshold p99 = 정의상 1% false positive. 매 5분 사이클 평가 → 하루 ~14회 발동
- MP의 self-similar 시계열 가정이 우리 트래픽과 안 맞음. m=12 (1시간) 윈도우는
daily seasonality(낮/저녁/새벽 패턴 변동)보다 짧아 자연스러운 시간대 변동을 discord로 잡음
- 단일 시그널이 attack_mode 트리거가 되어 attack_contributor에서 reqs 순 top 20을
무차별 차단 → 18시간 동안 99 distinct IP 차단 누적 (webhook 4~5개 + KR 활발 유저
+ IPv6 모바일 240/4 가짜 IP). LIVE였으면 베팅 콜백 중단 사고
- matrix_profile.py 모듈은 보존 (관찰/연구용 수동 호출 가능). attack 트리거에서만 제거
## 폐기 후보 (검토 후 불채택)
- **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 + cf_rule_id, baseline_samples, cusum_state 등)
├── matrix_profile.py — stumpy MP discord 판정 (v2.2)
├── cf_client.py — CF Access Rules API 래퍼 + CLI 유틸 (v2.3)
├── bootstrap_baseline.py — 과거 N시간 seed 주입 (cold start 해소)
├── loop.py — 메인 실시간 루프 (--live로 CF 호출)
├── 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 규명