검증 1: 실험실과 지표

아프로·2026년 10월 30일

검증 1 실험실과 지표 표지

2부까지 "이 규칙이 낫다", "저 규칙은 오탐이 많다"고 여러 번 말했습니다. 이제 그 말을 숫자로 확인합니다. 탐지 규칙도 코드입니다. 코드는 시험해야 합니다. 이 방식을 탐지를 코드처럼 다루기(detection-as-code)라고 부릅니다. 규칙을 바꿀 때마다 같은 정상 트래픽에서 오탐을, 같은 공격 트래픽에서 탐지를 다시 잽니다.

실험실 한 장

트래픽 생성기, 선택적 WAF, FastAPI 앱, Redis, 결과 기록과 평가 스크립트로 이루어진 실험실 구성

모든 것이 이 맥 한 대의 127.0.0.1에서 돕니다. 바깥 주소로는 요청을 한 건도 보내지 않습니다. 코드는 실험실 폴더 lab/에 있고, README.md에 재현 명령이 있습니다.

  • 앱(app.py, 포트 18110). FastAPI로 만든 가짜 쇼핑몰입니다. 사용자 200명, 주문 1,365건(그중 27건은 미끼, 5편), 상품 500개. 주문 조회에는 소유권 검사가 있고(환경 변수로 뺄 수 있음), 모르는 파라미터는 400, 틀린 값은 422로 답합니다.
  • 탐지 미들웨어(detect.py). 3·4편의 보안 이벤트와 슬라이딩 윈도우 규칙 7개, 6편의 단계적 대응. 윈도우는 Redis(포트 18379, 실험용으로 따로 띄움)나 메모리 중에 고릅니다.
  • 트래픽 생성기(traffic.py). 정상과 공격 요청에 이름표를 붙여 보냅니다.
  • 평가(evaluate.py). 요청 기록을 읽어 지표를 계산하고, 기준값을 바꿔 가며 다시 흘려 봅니다.
  • 지연 측정(bench.py), WAF 실험(waf/).

트래픽: 정상 150명, 공격 6종, 2시간

시드를 고정해 매번 같은 트래픽이 나옵니다. 모두 5,373건입니다.

정상(3,913건, 150명)

종류사람 수모양
둘러보기141명주문 목록, 내 주문 1~4건 열기, 상품 검색(한국어 검색어 포함), 가끔 재시도
주문 많은 사용자5명주문 60건을 목록에서 하나씩 모두 열기
모바일 폴링3명2초마다 주문 목록, 15분
잘못 만든 앱1명0.4초마다 주문 목록, 3분
섞여 있는 예외일부가족이 보낸 남의 주문 링크(28건), 지운 주문의 옛 링크(18건), 번호 잘못 입력(8건), 옛 앱의 모르는 파라미터(6건)

141명 가운데 50명은 CGNAT 공유 IP 두 개(100.64.0.1, 100.64.0.2) 뒤에 있습니다.

공격(1,460건, 6종)

이름모양
A1 빠른 열거계정 1, IP 1, 0.2초마다 주문 번호 +1, 300건
A2 느린 열거90초마다 +1, 80건(2시간에 걸쳐)
A3 IP 바꾸는 열거계정 1, 요청마다 다른 IP, 1초 간격 200건
A4 계정 바꾸는 열거IP 1, 계정 20개가 4건씩, 2초 간격 80건
A5 파라미터 퍼징처음 보는 파라미터 이름과 형식이 틀린 값, 0.3초 간격 200건
A6 폭주 수집상품 목록 600건을 60초에

공격 예시는 교과서 수준입니다. 주입 문자열이나 특정 WAF를 피하는 기법은 쓰지 않았습니다. 퍼징의 파라미터 이름도 debug, role, limit 같은 평범한 단어입니다.

모의 시각

느린 열거를 실제로 2시간 기다릴 수는 없습니다. 그래서 요청마다 X-Lab-Time 헤더에 모의 시각을 실어 보내고, 탐지 규칙은 이 시각으로 윈도우를 셉니다. 2시간 분량이 이 맥에서 5~8초 만에 끝납니다. 이 시리즈에서 "몇 초 만에 잡았다"는 모두 모의 시각입니다. 요청 순서와 간격은 그대로이니 윈도우 규칙의 동작은 같지만, 실제 시스템의 처리 지연은 들어 있지 않습니다. 실제 시간의 비용은 따로 쟀습니다(15편).

클라이언트 IP도 X-Lab-Client-IP 헤더로 넣었습니다. 앞단 프록시가 넣어 준 IP라고 가정한 것입니다. 실서비스에서는 믿을 수 있는 프록시가 넣은 값만 써야 합니다.

지표 네 가지

  1. 탐지율. 공격자 단위로 셉니다. 그 공격자의 요청 중 하나라도 경보가 나면 "잡았다"입니다.
  2. 오탐률. 정상 사용자 150명 중 경보를 한 번이라도 받은 사람 수입니다. 경보 건수도 함께 봅니다. IP 기준 경보는 그 IP를 함께 쓰는 정상 사용자 수(8편의 CGNAT 문제)도 셉니다.
  3. 탐지까지 걸린 요청 수와 시간. 공격자의 몇 번째 요청에서 첫 경보가 났는지, 첫 요청부터 몇 초(모의 시각) 걸렸는지.
  4. 지연 증가. 탐지를 켰을 때 요청 지연(p50·p95·p99)과 처리량이 얼마나 나빠지는지. 실제 시간으로 잽니다.

p50, p99는 지연을 줄 세웠을 때 50번째, 99번째 백분위 값입니다. p50은 보통의 요청, p99는 운 나쁜 요청의 지연입니다.

소유권 검사를 뺀 설정에서는 추가로 새어 나간 남의 주문 수를 셉니다(13편).

같은 트래픽, 여섯 가지 설정

설정탐지 윈도우소유권 검사대응
shadow-redisRedis있음없음(섀도)
shadow-memory메모리있음없음
vuln-shadowRedis없음없음
vuln-enforceRedis없음단계적 대응
secure-enforceRedis있음단계적 대응
secure-enforce-tunedRedis있음단계적 대응(distinct_objects 뺌)

shadow-redis와 shadow-memory의 경보는 완전히 같았습니다. 그리고 기록을 메모리 윈도우로 다시 흘린 재생 결과도 앱 안에서 실제로 난 경보와 같았습니다. 그래서 기준값을 바꾼 표(14편)는 서버를 다시 돌리지 않고 재생으로 만들었습니다.

실행 환경과 주의

  • 실행일: 2026-10-30.
  • 장비: MacBook Pro(Apple M1 Pro, 10코어, 메모리 32GB), macOS(Darwin 25.6).
  • 소프트웨어: Python 3.12.12, FastAPI 0.115.14, uvicorn 0.32.1(작업자 1개), httpx 0.27.2, Redis 8.2.2(로컬), Docker 27.5.1. 정확한 패키지 목록은 lab/requirements.lock.txt.
  • 합성 트래픽은 운영 트래픽이 아닙니다. 정상 사용자의 모양은 제가 정했습니다. 여러분 서비스에는 제가 상상하지 못한 정상 모양이 있습니다. 이 결과는 "이런 조건에서 이런 차이가 난다"를 보여 줄 뿐, 여러분 서비스의 기준값을 정해 주지 않습니다.
  • 공격자도 제가 만든 여섯 가지뿐입니다. 더 영리한 공격자는 더 조용합니다.

다시 돌리는 법

cd lab
python3 -m venv .venv && .venv/bin/pip install -r requirements.lock.txt
./run_all.sh              # 설정 6가지 + 지연 측정, results/에 원시 결과
./waf/run_waf.sh          # (Docker 필요) ModSecurity + CRS 패러노이아 레벨 1~4

정리

  • 탐지 규칙은 코드처럼 시험합니다. 같은 정상 트래픽에서 오탐, 같은 공격 트래픽에서 탐지를 잽니다.
  • 실험실은 127.0.0.1의 가짜 쇼핑몰, 정상 150명과 공격 6종의 이름표 트래픽, 모의 시각 2시간입니다.
  • 지표는 탐지율, 오탐률, 탐지까지 걸린 요청 수·시간, 지연 증가입니다. 다음 편부터 결과를 봅니다.

출처와 확인 날짜


이전 글 클라우드·상용으로 같은 그림 그리기
다음 글 검증 2: IDOR, 무엇이 잡나
시리즈 전체 목록 해킹 시도를 몇 초 안에 잡는 서버 전체 목록

profile
고인물 개발자(OG)

0개의 댓글