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

12 KiB
Raw Permalink Blame History

title, updated, tags
title updated tags
Netbis Sigmatch — VL 기반 자동 공격 탐지 + CF 차단 2026-04-24
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_rulerule_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 갱신
  • 한도 도달 시 CFLimitExceededErrorapply_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)

# 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 전체 삭제

개발 단계

  • Phase 1: feature 추출 (fetch_features.py)
  • Phase 2: 24h retrospective baseline (collect_baseline.py)
  • Phase 3-5: (폐기) IsolationForest+DBSCAN+persistence 기반
  • Phase 6 (v2): 집계 기반 공격 모드 + 개별 극단 시그니처
  • Phase 7: 롤링 baseline 자동 갱신
  • Phase 8 (v2.1): CUSUM Page-Hinkley + global src IP entropy drop
  • Phase 9 (v2.2): Matrix Profile discord (stumpy.stump) 추가
  • Phase 10: bootstrap_baseline.py — 과거 24h seed로 모든 hour samples_ok 즉시 충족
  • Phase 11 (v2.3): CF Access Rules 호출 (managed_challenge/block) — DRY 검증 단계
  • 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/                   — 사이클 로그

연관 정본