HTTP Flood 공격 방어를 위한 요청 속도 제한 알고리즘의 효율성 비교
증상 진단: 웹 서버가 갑자기 응답 불능 상태인가요?
웹 애플리케이션이 평소와 다르게 극도로 느려지거나, 아예 접속이 불가능해졌으며, 서버 모니터링 도구에서 CPU 사용률과 네트워크 트래픽이 정상 범위를 크게 초과하는 패턴을 관찰하고 계신다면, 이는 HTTP Flood 공격을 의심해야 하는 명확한 증상입니다. 공격자는 수천. 수만 개의 가짜 http 요청(주로 get 또는 post)을 초당 몰아쳐 애플리케이션 계층에서 서버 리소스를 고갈시키는 것을 목표로 합니다. 단순한 대역폭 공격과 달리, 이 공격은 정상적인 트래픽처럼 보이는 패킷을 사용하므로 기존의 방화벽만으로는 탐지와 차단이 어려운 경우가 많습니다.
원인 분석: 애플리케이션 계층 자원 고갈
HTTP Flood 공격의 핵심 메커니즘은 웹 서버나 애플리케이션 서버(WAS)의 처리 한계를 의도적으로 초과시키는 데 있습니다. 각 HTTP 요청은 서버 내에서 연결 수락, 세션 관리, 데이터베이스 쿼리 실행, 동적 페이지 생성 등 일련의 비용이 드는 프로세스를 트리거합니다. 공격자는 이 프로세스를 최소한의 비용으로 대량 생성하여 서버의 가용 연결 수(예: TCP Backlog), 스레드 풀, 메모리, DB 커넥션 등을 순식간에 소진시킵니다. 결과적으로 합법적인 사용자의 요청은 처리 리소스를 할당받지 못하고 큐에서 대기하거나 드롭되어 서비스 장애로 이어집니다.
해결 방법: 요청 속도 제한 알고리즘 비교 및 구현
HTTP Flood 공격을 효과적으로 완화하기 위한 핵심 전략은 요청 속도 제한(Rate Limiting)입니다. 이는 클라이언트(IP, 세션, 사용자 ID 등)별로 일정 시간 동안 허용할 최대 요청 수를 정의하고, 이를 초과하는 요청을 지연시키거나 거부하는 메커니즘입니다. 다양한 알고리즘이 존재하며, 각각의 효율성(처리 속도, 메모리 사용량), 공정성, 구현 복잡도가 상이합니다. 적절한 알고리즘 선택은 시스템 규모와 요구사항에 따라 결정되어야 합니다.
주의사항: 본 가이드에서 제시하는 알고리즘 설정 변경은 운영 서버에 직접 적용하기 전에 반드시 스테이징 환경에서 충분히 테스트해야 합니다. 잘못된 설정은 정상 사용자의 접근을 차단하는 역효과를 낳을 수 있습니다, 또한, 알고리즘 구현 전에 웹 서버(nginx, apache) 또는 애플리케이션 프레임워크(spring, express) 레벨에서 제공하는 기본 rate limit 모듈의 활용을 먼저 검토하는 것이 현실적입니다.
Method 1: 토큰 버킷 알고리즘 – 유연한 버스트 트래픽 처리
토큰 버킷(Token Bucket) 알고리즘은 설정된 속도로 버킷에 토큰이 채워지고, 각 요청은 처리하기 위해 하나의 토큰을 소비하는 방식으로 작동합니다. 버킷에 토큰이 있으면 요청을 즉시 처리하며, 없으면 대기하거나 거부합니다. 버킷 크기가 허용하는 범위 내에서 짧은 버스트(Burst) 트래픽을 수용할 수 있어 실제 사용자 패턴을 모방한 공격에 대한 초기 대응에 유리합니다.
- 핵심 파라미터 정의: 버킷 크기(Burst Size, 최대 누적 토큰 수)와 토큰 충전률(Refill Rate, 초당 토큰 수)을 설정합니다. 예:
burst=100 requests, rate=10 requests/second. - 구현 로직: 클라이언트 키(예: IP 주소)별로 마지막 토큰 충전 시간과 현재 토큰 수를 저장합니다. 요청 도착 시, 경과 시간에 따른 토큰 충전량을 계산한 후, 토큰이 1개 이상이면 차감하고 요청을 허용합니다.
- 장단점 분석:
- 장점: 제한된 버스트 트래픽을 허용하여 사용자 경험을 유지할 수 있음. 메모리 사용량이 비교적 적고(키별로 두 개의 값 저장), 계산 효율이 좋음.
- 단점: 버스트 크기를 너무 크게 설정하면 공격자가 이 간격을 이용하여 피해를 줄 수 있음. 장기간의 고속 공격에는 취약할 수 있습니다.
Method 2: 고정 윈도우 카운터 – 단순하고 직관적인 구현
고정 윈도우 카운터(Fixed Window Counter) 알고리즘은 타임라인을 고정된 크기(예: 1초, 1분)의 윈도우로 나누고, 각 윈도우 내에서 클라이언트의 요청 수를 카운트합니다. 카운트가 임계값을 초과하면 해당 윈도우가 끝날 때까지 모든 추가 요청을 거부합니다. 개념이 단순하여 구현과 이해가 쉽습니다.
- 핵심 파라미터 정의: 윈도우 크기(예: 60초)와 윈도우 당 최대 허용 요청 수(예: 100회)를 설정합니다.
- 구현 로직: 클라이언트 키와 현재 윈도우의 시작 타임스탬프를 조합한 키를 사용하여 카운터를 저장소(인메모리 DB 등)에 증가시킵니다. 현재 시간이 윈도우를 벗어나면 카운터가 리셋되는 새로운 윈도우가 시작됩니다.
- 장단점 분석:
- 장점: 구현이 매우 간단하며, 메모리 효율이 좋을 수 있습니다.
- 단점: 윈도우 경계에서 발생하는 버스트에 취약합니다. 예를 들어, 윈도우가 1분이고 제한이 100회일 때, 공격자가 첫 윈도우의 마지막 1초와 다음 윈도우의 첫 1초에 각각 100회의 요청을 보내면 1초 동안 실제로 200회의 요청이 처리될 수 있어 제한이 무력화됩니다.
Method 3: 슬라이딩 윈도우 로그 – 정밀한 제어와 공정성
슬라이딩 윈도우 로그(Sliding Window Log) 알고리즘은 각 요청의 타임스탬프를 로그에 기록합니다. 새로운 요청이 들어오면, 현재 시간에서 윈도우 크기(예: 1분)를 뺀 시점 이후의 타임스탬프만을 남기고 오래된 로그는 삭제합니다. 남아 있는 로그의 개수가 허용 한도를 초과하면 요청을 거부합니다. 이 방법은 가장 정확하게 시간 범위 내의 요청 수를 제한합니다.
- 핵심 파라미터 정의: 윈도우 크기와 허용 요청 수를 설정합니다. (고정 윈도우와 파라미터는 유사그럼에도 동작 방식이 근본적으로 다름)
- 구현 로직: 클라이언트 키별로 타임스탬프 리스트 또는 정렬 집합을 유지합니다. 요청 시 현재 타임스탬프를 삽입하고, 윈도우 범위 밖의 오래된 타임스탬프를 모두 제거한 후, 리스트의 길이를 확인하여 허용 여부를 결정합니다.
- 장단점 분석:
- 장점: 시간에 따른 정확한 제어가 가능하며, 고정 윈도우의 경계 버스트 문제를 완전히 해결합니다. 공정성과 정밀도가 가장 높습니다.
- 단점: 모든 요청의 타임스탬프를 저장해야 하므로 메모리 사용량이 클라이언트 수와 요청량에 비례하여 증가할 수 있습니다. 로그 정리 작업에 추가적인 CPU 오버헤드가 발생합니다.
Method 4: 슬라이딩 윈도우 카운터 – 효율성과 정확성의 절충
알고리즘 개요
슬라이딩 윈도우 카운터(Sliding Window Counter)는 고정 윈도우의 효율성과 슬라이딩 윈도우 로그의 정확성을 절충한 알고리즘입니다. 현재 윈도우와 이전 윈도우의 카운트를 가중치를 두어 계산하여 근사적인 슬라이딩 윈도우 카운트를 추정합니다. Redis의 INCR 명령어와 만료 시간 설정을 활용한 구현이 일반적입니다.
핵심 파라미터 및 구현 로직
-
파라미터 정의: 고정 윈도우 카운터와 동일하게 윈도우 크기와 허용 요청 수를 설정합니다.
-
근사치 계산: 1분 윈도우, 100회 제한 시, 현재 1분($0$초~$60$초)의 카운트와 이전 1분($60$초 전~$120$초 전)의 카운트를 조합합니다. 예를 들어, 현재 시각이 1분 30초라면 현재 윈도우($30$초~$90$초)의 예상 요청 수는 아래와 같은 가중치 산식을 따릅니다.
$$\text{Estimated Count} = (\text{Previous Window Count} \times \text{Overlap Percentage}) + \text{Current Window Count}$$실제 트래픽 변동성이 큰 프로덕션 환경에서 수집된 자동화 대응 규칙이 조건별로 어떻게 바뀌는지 운영 과정에서 관찰한 패턴에 따르면, 이와 같은 가중치 합산 방식은 고정 윈도우 경계에서 발생하는 트래픽 허용 한도 초과 문제를 실질적으로 완화하는 경향이 있습니다. 이 계산값이 설정된 임계치를 초과하면 시스템은 즉시 요청을 거부하여 안정성을 유지합니다.
장단점 분석
-
장점: 슬라이딩 윈도우 로그보다 메모리 효율이 훨씬 뛰어나며, Redis 키별로 소량의 카운터 값만 저장하므로 분산 환경에서 유리합니다. 또한 고정 윈도우 방식보다 훨씬 정밀하게 트래픽을 제어할 수 있습니다.
-
단점: 확률적인 근사치를 사용하므로 $100\%$ 완벽한 정확성을 보장하지는 않으며, 단순 증가 연산 외에 가중치 계산 로직이 추가적으로 수반됩니다.
효율성 비교 및 선택 가이드
상황에 맞는 알고리즘 선택은 시스템의 규모, 트래픽 패턴, 허용 오차 범위에 따라 달라집니다. 다음은 실무 관점에서 정리한 알고리즘별 특징입니다.
처리 속도 및 확장성 측면에서 토큰 버킷과 슬라이딩 윈도우 카운터는 Redis와 같은 분산 환경에서 확장하기에 가장 적합합니다. 고정 윈도우 역시 구현의 단순함 덕분에 속도가 빠르지만, 경계 지점의 트래픽 폭주(Burst) 대응 성능을 여러 대안과 대조한 유사 기법 비교 자료에 따르면 임계치 초과 리스크가 다른 방식보다 상대적으로 높게 나타납니다. 따라서 트래픽 변동성이 큰 환경에서는 이를 보완할 수 있는 정교한 모델 선택이 필요합니다.
메모리 효율성을 살펴보면 고정 윈도우, 토큰 버킷, 슬라이딩 윈도우 카운터는 모두 $O(1)$ 공간 복잡도를 유지하여 효율적입니다. 반면, 슬라이딩 윈도우 로그는 모든 요청의 타임스탬프를 기록해야 하므로 $O(N)$의 공간이 필요하며, 대규모 공격 상황에서는 메모리 부하가 급격히 증가할 위험이 있습니다.
정확도와 공정성 부분에서는 슬라이딩 윈도우 로그가 가장 정밀한 제어를 보장합니다. 반면 토큰 버킷은 일정 수준의 버스트를 허용하도록 설계되었는데, 이는 일시적인 트래픽 급증을 유연하게 수용하여 사용자 경험을 보호하는 측면에서 실무적인 강점으로 작용합니다.
실전 추천 시나리오로 API 게이트웨이나 CDN 엣지처럼 낮은 지연 시간이 필수인 인프라에는 토큰 버킷이나 슬라이딩 윈도우 카운터 기반의 분산 제어를 권장합니다. 로그인이나 OTP 전송 등 보안이 중요한 엔드포인트는 슬라이딩 윈도우 로그를 통해 정확하게 제어하되, 성능 최적화가 관건이라면 근사치 알고리즘으로 타협하는 것이 합리적입니다. 마지막으로 소규모 애플리케이션은 Nginx의 기본 모듈과 같은 웹 서버 수준의 기능만으로도 충분한 방어 효과를 거둘 수 있습니다.
전문가 팁: 다층 방어 체계 구축 단일 알고리즘에 의존하기보다는 다층 방어를 구성하는 것이 실제 공격을 효과적으로 막는 길입니다. 1) 네트워크/인프라 층: DDoS 방어 서비스(AWS Shield, Cloudflare)를 활용해 대역폭 공격을 흡수합니다. 2) 웹 서버 층: Nginx의 limit_req_zone으로 기본적인 IP별 속도 제한을 설정합니다. 3) 애플리케이션 층: 본문에서 분석한 알고리즘을 사용하여 비즈니스 로직에 맞는 세밀한 제어(예: 사용자 ID별 API 호출 제한)를 구현합니다. 4) 위협 인텔리전스: 지속적으로 이상 패턴을 학습하고, 공격 IP 리스트를 실시간으로 차단 목록에 업데이트하는 자동화 프로세스를 갖추는 것이 장기적인 방어 역량을 결정합니다. 모든 로그는 중앙 집중화되어 분석 가능한 상태로 보관되어야 하며, 이는 공격 경로 추적과 사후 대응의 근거가 됩니다.