방어선 1-④: 단계적 대응과 섀도 모드

아프로·2026년 10월 30일

방어선 1-④ 단계적 대응과 섀도 모드 표지

탐지 규칙을 처음 켠 날, 경보가 울리면 바로 막도록 해 두었다고 해 봅시다. 실험실에서 그렇게 했더니 공격자 다섯을 막는 동안 주문이 많은 정상 사용자 5명도 429를 받았습니다. 주문 60건을 차례로 열어 본 것이 "서로 다른 주문을 많이 연다"는 규칙에 걸렸기 때문입니다. 규칙을 켜는 순서와 대응의 세기가 탐지만큼 중요합니다.

대응의 사다리

기록, 지연, 429 제한, 챌린지, 세션 폐기, TTL 차단으로 올라가는 단계적 대응 사다리

단계적 대응(graduated response)은 확신이 클수록 세게 반응하는 방식입니다.

  1. 기록. 경보만 남깁니다. 사람이 보거나 다른 신호와 엮습니다.
  2. 지연. 응답을 조금 늦춥니다. 사람은 거의 못 느끼고, 자동화의 속도는 떨어집니다.
  3. 제한(429 Too Many Requests). 일정 시간 요청을 거절합니다. Retry-After 헤더로 언제 다시 오라고 알려 줍니다.
  4. 챌린지. 사람인지 확인합니다. Cloudflare는 대부분의 WAF 규칙에 관리형 챌린지(Managed Challenge)를 권하고, 상호작용형 챌린지는 가장 덜 권합니다. API 호출에는 챌린지 페이지가 맞지 않아 별도 처리가 필요합니다.
  5. 세션·토큰 폐기. 공격에 쓰인 로그인 상태를 끊습니다. 계정은 살리고 공격 세션만 죽입니다.
  6. 차단(TTL). 일정 시간 모두 거절합니다. 영구 차단은 두지 않습니다.

차단에는 언제나 TTL(만료 시간)을 붙입니다. CrowdSec도 기본 프로필이 IP를 4시간 차단하고, 반복되면 시간을 늘리는 식을 예시로 둡니다. TTL이 있으면 오탐이 저절로 풀리고, 진짜 공격자는 다시 걸립니다.

실험실의 대응 규칙

실험실 lab/detect.py의 Responder는 신원(계정) 기준 경보에만 반응합니다.

  • 최근 15분 경보 1개 → 기록
  • 2개 → 60초 동안 429
  • 3개 이상, 또는 미끼 접근 1번 → 15분 차단

IP 기준 경보는 기록만 합니다. CGNAT 뒤의 정상 사용자를 함께 막지 않기 위해서입니다(8편). JWT.sub != Path.ID 규칙은 대응에서 뺐습니다. 정상 사용자 대부분에게 울리기 때문입니다(13편).

섀도 모드: 먼저 재고 나중에 막는다

섀도 모드는 규칙을 켜되 대응은 하지 않고, 경보만 남겨 오탐을 재는 단계입니다. 같은 트래픽을 섀도 모드와 대응 모드로 돌려 비교했습니다. 앱에는 소유권 검사가 들어 있습니다.

설정대응을 받은 정상 사용자공격자에게 대응이 시작된 요청
섀도(대응 없음)0명없음
대응, 규칙 전부6명: 주문 많은 사용자 5명(429를 4~9번), 잘못 만든 앱 1명(429를 149번)빠른 열거 11번째, 느린 열거 8번째, IP 바꾸기 11번째, 계정 바꾸기 8번째, 퍼징 121번째, 폭주 수집 안 걸림
대응, distinct_objects 뺌1명: 잘못 만든 앱(429를 149번)위와 같음

섀도 결과에서 distinct_objects 규칙의 오탐이 모두 주문 많은 사용자라는 것을 먼저 알았다면, 그 규칙을 대응에서 빼고 켤 수 있었습니다. 그래도 공격자를 막는 시점은 하나도 늦어지지 않았습니다. 다른 규칙(거부된 서로 다른 객체 수, 미끼)이 이미 잡고 있었기 때문입니다.

0.4초마다 같은 목록을 부르는 잘못 만든 앱에 429를 준 것은 오탐이라기보다 원하던 대응에 가깝습니다. 서버를 지키면서 그 앱 개발자에게 "너무 자주 부른다"를 알려 줍니다.

놓친 것도 보인다

같은 표에서 폭주 수집은 한 번도 막히지 않았습니다. 60초 동안 600건을 보냈는데, 경보를 내는 규칙이 요청 수 규칙 하나뿐이었습니다. 같은 경보는 60초에 한 번만 나니 경보가 1개에서 멈췄고, 대응은 "기록"에서 올라가지 않았습니다.

여기서 배울 점은 두 가지입니다. 하나, 규칙 하나만 반응하는 공격에는 그 규칙의 대응 단계를 따로 정해야 합니다. 폭주라면 요청 수 규칙이 곧바로 429를 내는 게 맞습니다. 둘, 이런 빈틈은 대응을 켜고 재 봐야 보입니다. 섀도 모드만으로는 "경보가 울렸다"까지만 압니다.

소유권 검사가 없다면

같은 대응 규칙을 소유권 검사를 뺀 앱에 걸어 봤습니다. 열거 공격 네 가지로 나간 남의 주문 수입니다.

대응 없음단계적 대응
빠른 열거299건13건
느린 열거80건62건
IP 바꾸기200건45건
계정 바꾸기78건74건
합계657건194건

탐지와 대응은 유출을 70% 줄였지만 0으로 만들지 못했습니다. 느린 열거와 계정 바꾸기는 거의 그대로 새었습니다. 탐지는 예방이 아닙니다. 예방(소유권 검사)을 넣은 앱에서는 처음부터 0건입니다.

되돌리기와 오탐 처리

대응을 켜기 전에 정해 둘 것입니다.

  • 결정 한곳. 차단·제한 결정을 한 저장소(예: Redis의 TTL 키, 결정 API)에 두고 모든 방어선이 거기서 읽습니다. 풀 때도 한곳에서 풉니다.
  • 결정마다 이유. 어떤 규칙이, 어떤 값으로, 언제 걸었는지 남깁니다. 고객 문의가 오면 바로 답할 수 있어야 합니다.
  • 사람이 푸는 길. 고객센터나 당번이 결정 하나를 지우는 버튼이 있어야 합니다. 지운 기록도 남깁니다.
  • 오탐은 규칙의 시험이 됩니다. 풀어 준 사례를 모아 다음 규칙 변경 때 정상 트래픽 시험에 넣습니다(12편).
  • 허용 목록은 좁게. 사내 배치, 모니터링, 제휴사 서버처럼 정해진 주체만 넣고, 넣은 이유와 만료일을 적습니다.

정리

  • 대응은 기록 → 지연 → 429 → 챌린지 → 세션 폐기 → TTL 차단 순으로 확신에 맞춰 올립니다. 영구 차단은 두지 않습니다.
  • 규칙은 섀도 모드로 먼저 켜서 오탐을 재고, 오탐이 많은 규칙은 대응에서 빼고 켭니다. 실험실에서 이것만으로 대응받은 정상 사용자가 6명에서 1명으로 줄었습니다.
  • 대응을 켜야 보이는 빈틈도 있습니다(폭주 수집). 그리고 탐지·대응은 예방을 대신하지 못합니다.

출처와 확인 날짜

모든 링크는 2026-10-30에 확인했습니다.


이전 글 방어선 1-③: 미끼 객체 하나가 경보 천 개보다 낫다
다음 글 방어선 2-①: WAF와 OWASP CRS가 잡는 것, 못 잡는 것
시리즈 전체 목록 해킹 시도를 몇 초 안에 잡는 서버 전체 목록

profile
고인물 개발자(OG)

0개의 댓글