대상: 검색 또는 유사 기능이 외부 API를 호출하는 사이트의 운영자. 이 글은 이 사이트 운영자가 운영하는 한 사이트에서 실제로 발생한 일을 일반화한 것입니다. 사이트, 외부 서비스, 정확한 제한값이나 날짜는 밝히지 않으며, 공격 절차는 다루지 않습니다.
무슨 일이 있었나
이 사이트 운영자가 운영하는 사이트에서 검색 기능은 검색할 때마다 외부 API를 호출했습니다. 며칠 사이에 검색 트래픽이 평소의 약 20배로 늘었습니다. 외부 API에는 동시 처리 상한이 있었고, 대다수 검색(90% 이상)이 503을 반환했습니다.
처음에는 IP별 제한(분당 N회)이 이를 막아 줄 것이라고 가정했습니다. 그 가정은 틀렸습니다.
며칠 만에 검색 트래픽이 약 20배 증가
외부 API의 동시 처리 상한에 도달했고, 거의 모든 검색이 503을 반환했습니다. 이 시점에는 단순히 오류가 늘어난 것처럼 보였습니다.
첫 번째 가정: IP별 제한이 막아 줄 것이다 (틀림)
IP별 분당 횟수 제한은 매우 많은 호출을 하는 몇 개의 IP를 잡아낼 수 있습니다. 하지만 이 트래픽은 그런 유형이 아니었습니다.
측정 결과
하루 약 60,000건의 검색이 약 60,000개의 서로 다른 IP에서 들어왔고, IP당 약 한 번의 요청이었습니다. 패턴은 주거용 프록시 네트워크처럼 보였습니다. 사용자 에이전트는 일반 브라우저였고, Referer는 사이트 자체였으며, 검색어는 실제 제품명이었습니다. 가격 수집이 목적이었을 가능성이 높았습니다.
외부 API의 일일 할당량이 그날 이른 시간에 소진
봇의 검색이 할당량을 이른 시간에 소진했고, 일반 방문자는 하루 대부분 검색할 수 없었습니다.
실제 문제로 보이지 않았던 이유
할당량이 보호되고 있었기 때문에 외부 API는 정상으로 보였고, 사이트에는 오류만 늘어난 것으로 보였습니다. 할당량을 보호하는 것과 사용자가 기능을 사용할 수 있게 하는 것은 서로 다른 일이었습니다.
예상된 상황: 소수의 IP가 많은 호출을 보냄
IP별 제한에 걸림 → 차단됨 ✓
실제 상황: 약 60,000개 IP, IP당 한 번씩 요청
어느 IP도 제한에 도달하지 않음 → 외부 API의 할당량만 소진됨
= 세는 단위가 IP인 동안에는 이런 형태가 보이지 않습니다
상위 할당량이 보호되고 있다고 해서 사용자가 기능을 쓸 수 있다는 뜻은 아닙니다
외부 API의 할당량이나 동시 처리 한도를 보호하는 장치는 해당 API가 망가지지 않도록 하기 위해 존재합니다. 이 장치가 작동하는 동안 상위 서비스는 오류를 보이지 않습니다. 비용은 할당량을 잃은 쪽, 즉 사이트 자체의 방문자가 부담합니다.
보호 장치가 작동하고 있었기 때문에 문제가 알아채기 어려웠습니다. 지켜봐야 할 것은 실제 방문자가 검색할 때 얻는 성공률입니다.
어떻게 막았나
막지 못했던 설계
누구의 검색 요청이든 외부 API를 호출했습니다. 유일한 제한은 IP별 횟수였습니다. 분산된 트래픽은 각 IP의 횟수를 낮게 유지하므로 제한이 작동하지 않았습니다.
효과가 있었던 설계
외부 API는 사이트 자체 페이지가 발급한 서명된 토큰(쿠키)이 있는 검색에만 호출합니다. 토큰이 없으면 가벼운 페이지 또는 403을 반환합니다. 그 위에 시간당 상한을 전역 및 토큰별로 적용합니다.
- 서명된 토큰
- 사이트 자체 페이지가 발급한 토큰을 외부 API 호출의 조건으로 삼았습니다. 토큰이 없으면 가벼운 페이지 또는 403
- 집계 보호
- 봇의 검색어로 부풀려져 있던 인기 검색어 순위에서 토큰이 없는 검색을 제외했습니다
- 시간당 상한
- 전역 상한과 토큰별 상한을 모두 적용했습니다
배포 직후 503은 0이 되었습니다. 현재 하루 약 100,000건의 토큰 없는 요청이 403을 받고, 정상 검색은 통과합니다.
교훈
- IP별 제한은 분산된 봇에 효과가 없습니다. 제한을 아무리 낮게 해도 IP당 한 번의 요청은 아무것도 막지 못합니다.
- 상위 할당량이 보호되고 있다는 것은 사용자가 기능을 사용할 수 있다는 것과 같지 않습니다. 보호 장치가 문제를 가릴 수 있습니다.
- 순위 같은 집계는 오염됩니다. 외부 API를 보호하는 것과 함께, 집계에 무엇이 들어가는지도 제한하세요.
사이트에서 할 일
아래 순서는 이 사이트 자체의 조언이며, 인용된 출처의 공식 입장이 아닙니다.
비용이 드는 작업 전에 사이트 발급 토큰을 확인
외부 API 호출처럼 비용이나 할당량을 소비하는 단계 전에, 자체 페이지가 발급한 서명된 토큰이 있는지 확인하세요. 토큰에 만료 시간을 두고 서버에서 검증하세요. 토큰이 없으면 진행하지 말고 가벼운 페이지 또는 403을 반환하세요.
토큰이 없는 트래픽은 집계에서 제외
인기 검색어, 검색어 통계, 추천은 토큰이 있는 검색으로만 만드세요. 그래야 봇의 검색어가 사이트에 표시되는 내용에 섞이지 않습니다.
전역 및 토큰별 상한 설정
IP별 횟수 대신 시간당 전역 상한과 토큰별 상한을 사용하세요. 전역 상한은 외부 API의 할당량이 모두 소진되는 것을 막는 마지막 안전장치입니다.
결과 캐싱과 큐잉 추가
반복되는 검색어는 외부 API를 다시 호출하지 않고 캐시에서 제공하고, 혼잡할 때는 요청을 큐에 넣으세요. 한 가지 조치가 우회되더라도 할당량이 한꺼번에 사라질 가능성이 줄어듭니다.
방문자가 경험하는 성공률 모니터링
외부 API의 오류율만이 아니라, 검색이 성공하는 실제 방문자의 비율을 기록하고 이 비율이 떨어지면 알림을 설정하세요. 이것이 조직 보안 기준선에서 말하는 "무언가 잘못되었음을 알아챌 수 있는 상태"입니다.
토큰은 인증이 아니라 공격자의 비용을 높입니다
토큰은 인증이 아닙니다. 토큰이 주는 효과는 검색 URL을 단순히 두드리는 트래픽을 멈추는 것이며, 완전한 방어가 아닙니다.
그래서 토큰은 캐싱, 큐잉, 토큰별 상한과 함께 사용합니다. 이 사이트의 견해로는, 봇이 매번 새 토큰을 받아 온다면 토큰별 상한은 도움이 되지 않으므로 항상 전역 상한 및 캐싱과 짝지어 사용해야 합니다. 요청 제한과 남용 통제에서와 같이, 이 사이트의 견해는 남용의 성공률을 0으로 만드는 것이 아니라 남용이 비용을 들일 가치가 없도록 만드는 것입니다.
출처 (1차 자료)
- OWASP API Security Top 10 2023, "API4:2023 Unrestricted Resource Consumption" — api-security.owasp.org (일정 시간 내 클라이언트가 API와 상호작용하는 빈도 제한, 외부 서비스의 지출 한도 설정)
- OWASP Automated Threats, "OAT-011 Scraping" — owasp.github.io (스크레이핑을 가격 담합 등 다른 곳에서 사용하기 위해 콘텐츠나 데이터를 수집하는 행위로 정의, 인증이 필요 없는 데이터가 표적이 될 수 있음)
다음으로 읽을 글
- 설계: 요청 제한과 남용 통제: 무차별 대입과 통제되지 않는 비용을 막는 제한 설계
- 선행 지식: X-Forwarded-For 스푸핑과 신뢰된 프록시 (IP 기준 집계의 기초)
- 운영: 조직 보안 기준선 (무언가 잘못되었음을 알아채는 방법)