개발 slecs

IP 보안 필터 강화로 공격 차단과 정상 트래픽 보호 균형 확보

목차

서버 보안 필터 작업은 "어떤 요청을 막을 것인가"보다 "어떤 요청을 절대 막으면 안 되는가"를 먼저 정리하는 일이다. 임계값을 조이는 건 5분이면 되지만, 그 과정에서 헬스체크나 모니터링 수집 경로가 같이 막히면 운영 알람이 죄다 울리기 시작한다. 이번 작업도 그 순서로 접근했다.

이번 변경의 배경과 범위

운영 서버에 붙어있는 보안 필터는 크게 세 가지 역할을 한다: IP 기반 Rate Limit, 봇/공격 패턴 감지, 블랙리스트 자동 등록. 이번 작업에서 임계값 조정과 예외 경로 추가, 공격 패턴 감지 확장까지 한꺼번에 처리했다.

필터의 처리 흐름은 아래와 같다.

요청 수신
  → 화이트리스트 IP 확인
  → 봇 UA / 의심 경로 감지
  → Rate Limit 체크 (Redis 슬라이딩 윈도우)
  → 차단 / 블랙리스트 등록 / 통과

순서가 중요한데, 화이트리스트를 먼저 통과시키지 않으면 내부 서비스 간 통신이나 모니터링 에이전트도 Rate Limit 카운트에 쌓인다. 이전에 완화해두었던 임계값을 이번에 다시 조였고, 공격 패턴 감지 쪽에 의심 경로와 페이로드 항목을 더 추가했다.

변경 내용 요약:

항목 기존 변경 후
Rate Limit 임계값 완화 상태 강화 (1분 기준 하향)
공격 패턴 감지 기본 의심 경로 + 페이로드 확장
블랙리스트 수동 DB 자동 등록

블랙리스트 자동 등록은 단순히 편의를 위한 게 아니다. 수동 등록 방식은 공격이 이미 들어온 다음 사람이 확인하고 등록하는 구조라 반응 속도에 한계가 있다. 일정 패턴이 감지되면 DB에 자동으로 올라가고, 이후 요청은 화이트리스트 체크 직후에 걸러지도록 했다.

Rate Limit 임계값 설정의 트레이드오프

Rate Limit을 설정할 때 단순히 "얼마나 많이 막을 것인가"가 아니라 "정상 사용자를 막지 않으면서 공격을 차단할 수 있는가"가 핵심이다. 너무 타이트하게 잡으면 정상 사용자가 피해를 보고, 너무 느슨하면 의미가 없다.

이번에 특히 신경 썼던 부분은 AJAX 요청 처리였다. 페이지 로드 시 AJAX 요청이 여러 개 한꺼번에 나가는 구조라, 동일한 임계값을 그대로 적용하면 정상 사용자도 순식간에 카운트를 채울 수 있다. 그래서 AJAX 요청은 Rate Limit 카운트에서 제외하는 옵션을 따로 추가했다.

예외 경로도 비슷한 맥락이다. 헬스체크 엔드포인트, 모니터링 수집 경로 같은 건 필터를 스킵하도록 명시적으로 빼줘야 한다. 이게 빠지면 모니터링 쪽에서 타임아웃이 발생하고, 서비스 이상이 없는데 알람이 울리는 상황이 생긴다.

Redis 슬라이딩 윈도우 방식은 고정 윈도우 대비 경계 구간 버스팅 공격에 강하다. 고정 윈도우는 윈도우가 리셋되는 순간 직전/직후에 요청을 몰아서 보내면 실효 임계값의 두 배 가까운 요청이 통과될 수 있는데, 슬라이딩 윈도우는 그 틈이 없다. 대신 Redis 연산이 조금 더 들어가고, Redis가 단일 장애점이 되지 않도록 연결 풀과 타임아웃 설정을 같이 챙겨야 한다.

# Redis 슬라이딩 윈도우 Rate Limit - 단순 예시
def is_rate_limited(redis_client, ip: str, limit: int, window_sec: int) -> bool:
    key = f"rl:{ip}"
    now = time.time()
    pipe = redis_client.pipeline()
    pipe.zremrangebyscore(key, 0, now - window_sec)
    pipe.zadd(key, {str(now): now})
    pipe.zcard(key)
    pipe.expire(key, window_sec + 1)
    results = pipe.execute()
    count = results[2]
    return count > limit

pipeline으로 묶어서 한 번에 처리하는 게 기본이다. 트랜잭션이 필요한 경우엔 MULTI/EXEC 또는 Lua 스크립트로 원자성을 보장해야 하는데, 단순 카운팅 용도라면 pipeline으로도 충분하다.

작업 후기

작업 규모 자체는 크지 않았다. 임계값 숫자 바꾸고, 예외 경로 몇 개 추가하고, 자동 블랙리스트 로직 붙인 게 전부다. 그런데 이런 작업을 할 때마다 느끼는 건, 보안 필터는 "뭘 막는가"보다 "뭘 실수로 막으면 안 되는가"를 먼저 정리해야 한다는 점이다.

예외 목록 관리를 코드에 하드코딩으로 박아두면 나중에 경로가 바뀌거나 새 엔드포인트가 생길 때 필터 쪽도 같이 수정해야 한다는 사실을 잊기 쉽다. 설정 파일이나 환경 변수로 빼두는 게 장기적으로 낫다. 이번에도 그 방향으로 정리해뒀다.

보안 필터 관련 변경은 배포 직후에 로그를 좀 봐줘야 한다. 정상 트래픽이 예상치 못한 이유로 걸리는 경우가 있고, 그걸 빠르게 잡을수록 영향 범위가 줄어든다. 이번엔 큰 문제 없이 지나갔지만, 다음에 더 큰 폭으로 임계값을 조이게 되면 스테이징에서 트래픽 재현 테스트를 먼저 돌려보는 게 맞을 것 같다.

댓글 0

첫 댓글 달아줘.