Published on

AWS WAF Bot Control 막힘으로 403 지속될 때

Authors

서론

AWS WAF Bot Control을 활성화한 직후부터 특정 API나 페이지가 계속 403을 뿜는 경우가 있습니다. 더 곤란한 점은 “봇을 막으려고 켰는데 정상 트래픽까지 막힌다”는 형태로 나타나며, ALB/CloudFront 뒤에서 동작할 때는 원인 파악이 더 늦어지기 쉽습니다.

이 글은 감으로 룰을 끄고 켜는 방식이 아니라, WAF 로그로 차단 원인을 확정 → 어떤 룰/시그널이 문제인지 분해 → 최소한의 예외로 정상 트래픽을 복구하는 순서로 정리합니다. 운영 환경에서 바로 적용할 수 있도록 콘솔 체크포인트, 스코프다운(Scoped down statement), 라벨(Label) 활용, 그리고 흔한 오탐 패턴을 함께 다룹니다.

증상 정의: “403이 WAF 때문인지”부터 확정

403이 뜨면 먼저 누가 403을 만들었는지 확정해야 합니다.

  • CloudFront: x-cache: Error from cloudfront, Via: ...cloudfront... 헤더가 보이고, CloudFront 표준 로그에서 sc-status=403.
  • ALB: ALB 액세스 로그에 elb_status_code=403가 찍히거나, 타깃은 200인데 클라이언트는 403.
  • 애플리케이션: 애플리케이션 로그에 403 응답 생성 흔적이 있고, WAF 로그에는 차단이 없음.

WAF 차단이면 보통 응답에 아래 중 일부가 보입니다.

  • CloudFront + WAF: x-amzn-waf-action: BLOCK (항상 있는 건 아님)
  • ALB + WAF: WAF 웹 ACL에서 BLOCK 카운트 증가

가장 확실한 방법은 WAF 로그를 보는 것입니다.

1단계: WAF 로그에서 “정확히 어떤 룰이 막는지” 찾기

Bot Control 오탐을 잡는 핵심은 “403이 난 요청 1건”을 잡아 terminatingRuleId / ruleGroupId / labels를 확인하는 것입니다.

WAF 로깅 켜기 (필수)

  • Web ACL → Logging and metrics → Logging 활성화
  • 대상: CloudWatch Logs / S3 / Kinesis Firehose

운영에서는 CloudWatch Logs로 빠르게 확인하고, 장기 분석은 S3 적재를 권장합니다.

CloudWatch Logs Insights 쿼리 예시

아래는 “차단된 요청” 중 어떤 룰이 종결(terminating)했는지 상위 집계를 보는 쿼리입니다.

fields @timestamp, action, terminatingRuleId, terminatingRuleType, httpRequest.clientIp, httpRequest.uri, httpRequest.httpMethod
| filter action = "BLOCK"
| stats count() as blocks by terminatingRuleId, terminatingRuleType
| sort blocks desc

Bot Control이 원인이면 terminatingRuleId가 Bot Control 룰(또는 관리형 룰 그룹 안의 특정 룰)로 나타납니다.

다음은 특정 URI에서만 403이 나는지 확인하는 쿼리입니다.

fields @timestamp, action, terminatingRuleId, httpRequest.uri, httpRequest.headers
| filter action = "BLOCK"
| filter httpRequest.uri like /\/api\/v1\/auth/
| sort @timestamp desc
| limit 50

여기서 중요한 포인트:

  • terminatingRuleId: 마지막으로 BLOCK을 결정한 룰
  • ruleGroupList / nonTerminatingMatchingRules: “매칭은 됐지만 종결은 안 한 룰” 힌트
  • labels: Bot Control/관리형 룰이 붙이는 라벨이 있으면 후속 룰에서 예외 처리 가능

2단계: Bot Control에서 자주 터지는 오탐 패턴

Bot Control은 “헤더/쿠키/행동 시그널”을 종합해 자동화 트래픽을 분류합니다. 오탐은 보통 아래 상황에서 발생합니다.

1) 서버-서버 호출(백엔드 워커, 크론, 파트너 API)이 브라우저처럼 보이지 않음

  • User-Agent가 빈 값이거나 curl/, python-requests/ 등 단순
  • 쿠키/자바스크립트 기반 시그널 부재
  • 동일 IP에서 짧은 시간에 반복 호출

2) 모바일 앱/임베디드 클라이언트

  • WebView/앱 네트워크 스택이 브라우저 시그널과 다름
  • 프록시/통신 라이브러리 특성상 헤더가 단순

3) 프록시/게이트웨이 뒤에서 원 IP 전달이 깨짐

  • WAF가 클라이언트 IP를 제대로 못 보고 모든 트래픽이 동일 IP로 보임
  • 결과적으로 “한 IP에서 대량 호출”로 분류되어 차단

CloudFront/ALB 앞단에서 X-Forwarded-For 처리가 정상인지, WAF가 참조하는 IP가 기대값인지 확인해야 합니다.

4) 인증/토큰 갱신 엔드포인트가 고빈도

  • /oauth/token, /auth/refresh, /login 류가 집중 호출
  • 정상 사용자의 재시도/SDK 재시도까지 합쳐져 패턴이 봇처럼 보임

이 경우는 해당 경로만 스코프다운하거나, 레이트리밋을 별도로 설계하는 게 안전합니다.

3단계: “그냥 Bot Control 끄기” 대신 안전하게 풀어주는 방법

운영에서 Bot Control을 통째로 끄면 바로 편하긴 하지만, 공격 표면이 급격히 커집니다. 아래 순서대로 “최소 예외”를 만드는 것을 권장합니다.

A. Bot Control 룰 액션을 Count로 내려서 영향도 측정

우선 Bot Control 룰(또는 룰 그룹)을 COUNT 모드로 바꾸고, 실제로 어떤 요청이 걸리는지 로그로 확인합니다.

  • Web ACL → Rules → Bot Control rule → Action override: Count
  • CloudWatch Metrics에서 CountedRequests 증가 확인

이 과정에서 “정상 요청이 얼마나 매칭되는지”를 수치로 확보할 수 있습니다.

B. 스코프다운(Scoped down statement)으로 보호 범위를 줄이기

Bot Control을 전체 경로에 적용하지 말고, “브라우저 트래픽이 주로 들어오는 경로”로 한정합니다.

예:

  • /api/는 서버-서버 호출이 많아 예외
  • //product/* 같은 웹 페이지는 Bot Control 적용

논리 예시(개념):

  • IF uri starts with /api/ THEN 제외
  • ELSE Bot Control 적용

Terraform로 Web ACL을 관리한다면 스코프다운을 코드로 고정하는 편이 재현성과 변경 이력 측면에서 유리합니다.

C. 파트너/내부 트래픽은 “명시적 Allow”로 우회

서버-서버 호출은 브라우저 시그널이 약하므로, 다음 중 하나로 명시적 Allow를 만듭니다.

  • 고정 IP(파트너사 NAT, 사내 egress) → IPSet Allow
  • mTLS(가능하면 최선) → ALB/NLB 레벨에서 구분
  • 커스텀 헤더(공유 시크릿 기반) → WAF에서 헤더 매칭 Allow

커스텀 헤더 방식은 유출 위험이 있으므로 최소한 아래를 같이 하세요.

  • 헤더 값은 짧은 고정 문자열이 아니라 주기적으로 회전 가능한 토큰
  • 해당 헤더는 인터넷 클라이언트가 임의로 붙일 수 없도록 앞단에서 제어(예: API Gateway/Lambda authorizer, mTLS, 사설망 등)

D. 라벨(Label) 기반 예외로 “특정 분류만” 풀기

Bot Control/관리형 룰은 종종 라벨을 붙입니다. 이 라벨을 이용하면 “봇으로 의심되지만 치명적이지 않은 분류”만 Allow/Count로 바꾸는 식의 정교한 예외가 가능합니다.

절차:

  1. WAF 로그에서 차단 요청의 labels 확인
  2. 해당 라벨이 붙은 요청에 대해 별도 룰을 만들어 Allow/Count
  3. Bot Control은 그대로 유지

이 방식은 무작정 경로 전체를 빼는 것보다 안전합니다.

4단계: 디버깅 체크리스트 (403을 재현/분해하는 순서)

1) 동일 요청을 “다른 네트워크/다른 UA”로 재현

  • 사내망 vs LTE
  • 브라우저 vs curl
  • User-Agent를 브라우저처럼 바꿔보기
curl -i https://example.com/api/v1/auth \
  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'

UA만 바꿨는데 403이 풀리면 “브라우저 시그널 부족” 가능성이 큽니다.

2) 클라이언트 IP가 WAF에 어떻게 보이는지 확인

WAF 로그에서 httpRequest.clientIp가 기대한 최종 사용자 IP인지 확인합니다.

  • 모두 같은 IP로 보이면 XFF 처리/프록시 구성 문제
  • CloudFront 사용 시 뷰어 IP가 반영되는지 확인

3) Bot Control 외 룰과의 충돌 확인

Bot Control을 켠 뒤 403이 늘었다고 해서 항상 Bot Control만 문제는 아닙니다.

  • AWS Managed Rules(CommonRuleSet)의 Body/Size 제한
  • Rate-based rule이 특정 엔드포인트를 과하게 제한
  • 커스텀 룰에서 BLOCK이 앞 순서에 위치

WAF는 룰 우선순위가 중요합니다. “Allow 예외 룰”은 반드시 Bot Control보다 우선순위가 높아야 합니다.

4) 인증/세션 엔드포인트는 별도 설계

/login, /token, /refresh는 공격도 많고 정상 재시도도 많습니다.

  • Bot Control로만 해결하려 하지 말고
  • rate limit + CAPTCHA/챌린지 + 계정 잠금 정책 + 관측(로그)

을 조합하는 편이 안정적입니다.

5단계: 운영에서 자주 쓰는 예외 패턴 3가지

패턴 1) /api/*는 Bot Control 제외 + Rate-based로 보호

  • 봇 분류는 브라우저 트래픽에 강함
  • API는 “정상 자동화”가 많아 오탐 비용이 큼

대신 /api/*에는

  • IP/토큰 기준 레이트리밋
  • 인증 실패 횟수 제한

을 적용합니다.

패턴 2) 파트너 트래픽은 IPSet Allow + 모니터링

  • IP가 고정이라면 가장 간단하고 강력
  • 단, 파트너 NAT 변경에 대비해 운영 프로세스 필요

패턴 3) 사내 워커/크론은 전용 egress + 헤더 서명

  • egress NAT를 고정하고
  • X-Internal-Caller 같은 헤더에 HMAC 서명

애플리케이션에서도 서명을 검증하면 “WAF 우회 헤더 위조” 위험을 낮출 수 있습니다.

간단한 HMAC 서명 예시(Python):

import hmac
import hashlib
import time

SECRET = b"rotate-me"

def sign(path: str, ts: int) -> str:
    msg = f"{path}:{ts}".encode()
    return hmac.new(SECRET, msg, hashlib.sha256).hexdigest()

ts = int(time.time())
path = "/api/v1/jobs/run"
headers = {
    "X-Caller-Timestamp": str(ts),
    "X-Caller-Signature": sign(path, ts),
}

WAF에서는 이 헤더 존재 여부로 1차 Allow를 하고(또는 Count), 애플리케이션에서 HMAC을 최종 검증하는 방식이 현실적입니다.

6단계: 403이 사용자 체감 장애로 번질 때 함께 점검할 것

WAF 403 자체를 고쳤는데도 사용자는 “접속이 불안정하다”고 느끼는 경우가 있습니다. 특히 프록시/ALB/CloudFront가 여러 겹이면 4xx/5xx가 섞여 보입니다. 이때는 게이트웨이 계층에서의 오류도 함께 보세요.

결론: “로그로 룰을 특정하고, 최소 예외로 복구”가 정답

AWS WAF Bot Control로 인한 403 연속 발생은 대부분 아래 3가지 중 하나로 귀결됩니다.

  1. 브라우저 시그널이 없는 정상 자동화 트래픽(서버-서버, 앱 호출)
  2. 클라이언트 IP 인식 문제(프록시/XFF/CloudFront 구성)
  3. 우선순위/스코프 설계 미스(전체 경로 적용, Allow 룰 위치 오류)

해결은 단순합니다. 먼저 WAF 로그에서 terminatingRuleIdlabels로 원인을 확정하고, Bot Control을 무력화하지 말고 Count 전환 → 스코프다운 → 명시적 Allow(IP/헤더/라벨) 순으로 안전하게 풀어가면 됩니다. 이렇게 하면 정상 트래픽을 살리면서도 봇 방어 효과는 유지할 수 있습니다.