요청 횟수 제한이 실패하는 것은 대개 제한이 느슨해서가 아니라 질문을 잘못했기 때문입니다. "분당 몇 회"를 정하기 전에 세 가지 질문에 답해야 합니다. 누구를 세는가, 무엇을 세는가, 상한에 닿으면 어떻게 하는가.
질문 1: 누구를 세는가?
IP를 주축으로 하면 공격자에게도 정상 사용자에게도 실패한다
공격자 쪽: 리버스 프록시나 CDN 뒤에서는 앱이 보는 발신 주소가 헤더에 적힌 값일 수 있습니다. X-Forwarded-For를 무조건 신뢰하면 헤더를 바꾸는 것만으로 다른 사람으로 세어집니다. 세고 있다고 믿지만, 실은 아무것도 세지 못합니다. 그 헤더를 어디까지 믿을 수 있는지는 X-Forwarded-For 스푸핑과 신뢰 프록시에서 다룹니다.
정상 사용자 쪽: 회사 NAT나 모바일망에서는 관계없는 많은 사용자가 하나의 주소를 공유합니다. IP별로 제한하면 공격하지 않는 사람들까지 막게 됩니다.
그래서 주축은 행위 주체에 묶인 식별자, 즉 계정, API 키, 확립된 세션으로 하고, IP는 보조 신호로 겹쳐 씁니다. 인증 전 단계(로그인 전)에는 기준으로 삼을 주체가 없으므로, 그 부분은 다음 두 질문이 메워야 합니다.
질문 2: 무엇을 세는가?
요청 수만 세는 설계로는 한 번에 비싼 요청을 막을 수 없습니다. OWASP API Security Top 10은 API4:2023 "Unrestricted Resource Consumption"에서 무엇에 상한이 필요한지 명시합니다.
세고 있는 것
요청 수: 분당 100회 → 한도 이내 ✓
↓ 하지만 실제로 소모되는 것은
CPU · 시간
호출마다 전체 스캔
메모리
지나치게 큰 입력
대역폭
레코드 1만 건
돈
종량제 호출의 반복
= 상한에 한 번도 닿지 않은 채 바닥난다
- 시간과 메모리
- 실행 시간 제한, 최대 메모리 할당
- OS 자원
- 파일 디스크립터 수, 프로세스 수
- 입력 크기
- 최대 업로드 파일 크기, 입력 파라미터의 최대 크기
- 요청당 작업량
- 한 번의 API 클라이언트 요청에서 수행되는 작업 수, 페이지당 반환 레코드 수
- 돈
- 외부 서비스와 API 연동의 지출 한도(아래 참조. 금전 피해를 막는 마지막 통제)
"분당 100회"를 지켜도, 요청 하나가 레코드 1만 건을 반환하거나, 거대한 파일을 처리하거나, 종량제 외부 API를 여러 번 호출한다면 아무것도 지키지 못합니다. 세는 단위를 지키려는 대상에 맞추세요. CPU, 메모리, 대역폭, 돈입니다. 이것이 설계의 핵심입니다.
질문 3: 상한에 닿으면 어떻게 하는가?
가장 많이 오해하는 부분입니다. 엄격할수록 안전한 것은 아닙니다.
무딘 잠금의 부작용
"3번 틀리면 계정 동결"은 옳아 보이지만, 공격자가 남의 계정을 일부러 얼려 버릴 수 있다는 뜻이기도 합니다. 잠금을 내 사용자를 겨냥한 서비스 거부 도구로 만든 셈입니다. 무차별 대입을 막으려다 사람을 쫓아내는 수단을 내놓은 것입니다.
"해 볼 가치가 없게" 설계하기
접근을 막기보다 시도를 느리게 만듭니다. NIST SP 800-63B가 드는 것은 인증 전 CAPTCHA 요구, 계정이 최대 허용치에 가까워질수록 길어지는 실패 후 대기 시간, 사용자가 이전에 인증한 적 있는 IP 주소의 허용 목록, 그리고 행동이 정상 범위인지(주소, 위치, 시간대, 브라우저 메타데이터) 판단하는 위험 기반 또는 적응형 기법입니다.
NIST의 숫자는 생각보다 크다: 연속 실패 100회
NIST SP 800-63B는 "검증자는 한 계정에 대한 연속 인증 실패를 100회 이하로 제한해야 한다(SHALL)"고 정합니다. 흔히 보는 "3회 실패 시 잠금"은 표준이 요구하는 것이 아닙니다.
100회로 충분한 이유: 지연과 CAPTCHA가 들어가면 100회에 도달하는 것이 현실적인 시간 안에는 불가능해집니다. 거꾸로 말하면, 지연은 넣지 않고 횟수만 조이는 것이 최악입니다. 정상 사용자는 쫓아내면서 자동화된 공격은 여전히 빠르게 시도를 소진할 수 있기 때문입니다.
같은 문서의 실무적인 지적이 하나 더 있습니다. 인증에 성공하면, 검증자는 같은 IP 주소에서 온 그 사용자의 이전 실패를 무시하는 것이 바람직하다(SHOULD)고 합니다. 카운터를 언제 초기화할지도 설계의 일부로 정하세요.
어디에 무엇을 적용하나
로그인(무차별 대입과 크리덴셜 스터핑)
계정별 실패 카운터를 주축으로 하고 지연을 단계적으로 늘립니다. IP는 보조 신호로 쓰고, 유일한 근거로 삼지 않습니다. 다중 인증을 더하면 비밀번호가 맞아도 바로 들어갈 수 없으므로, 추측할 가치가 없어집니다. 또 최근 침입은 추측보다 정상 자격 증명을 갖고 들어오는 경우가 늘고 있다는 점도 기억하세요(2026년 침입 경로 분석). 요청 횟수 제한은 필요조건이지 충분조건이 아닙니다.
비밀번호 재설정과 확인 메일(메일 폭탄)
발송을 일으킬 수 있는 것과 같은 수신처로 가는 건수를 모두 셉니다. 남의 주소를 받아 주는 폼은 공격자가 제3자를 괴롭히면서 내 발송 평판도 떨어뜨리게 합니다. 수신처별 대기 시간, 재발송 최소 간격, 확인되지 않은 기록의 삭제를 조합하세요(확인되지 않은 개인 정보 보유도 줄어듭니다). 재설정 경로 자체는 비밀번호 재설정의 설계 결함에서 다룹니다.
공개 엔드포인트와 AI 기능(비용 급증)
요청 수가 아니라 비용을 셉니다. 입력 크기에 상한을 두고, 요청당 작업 수를 제한하고, 서비스 제공자 쪽 지출 한도를 반드시 설정하세요. OWASP의 시나리오에는 월 13달러이던 청구가 8,000달러로 뛴 예가 있습니다. 종량제 외부 API를 공개 기능에 연결할 때는, 내 코드가 틀려도 버티는 층이 있는지 확인하세요. 키가 유출되면 무슨 일이 생기는지는 도난당한 키, 청구서, 계정 정지와 AI가 쓴 코드에서 유출된 API 키에서 다룹니다.
비싼 작업(검색, 내보내기, 파일 처리)
실행 시간, 메모리, 업로드 크기, 페이지당 반환 레코드 수에 명시적인 상한을 둡니다. 상한이 없는 곳에서는 요청 하나가 자원을 바닥낼 수 있습니다. 컨테이너나 서버리스 층에서도 메모리, CPU, 프로세스 수를 제한해, 앱이 예상 밖의 자원을 쓸 때 바깥에서 멈추게 하세요.
상한에 닿고 있다는 것을 알 수 있게 한다
관찰할 수 없는 제한은 반쪽짜리 통제입니다. 상한에 닿고 있다는 것을 아무도 볼 수 없다면, 진행 중인 공격도, 쫓겨나는 정상 사용자도 알아차리지 못합니다. 제한이 발동한 횟수를 기록하고 급증하면 알림을 보내세요. 조직의 보안 기본선에서 말하는 "이상을 알아차릴 수 있는" 상태를 여기에 적용한 것입니다.
본 사이트의 견해: 제한의 목표는 남용이 비용 대비 가치가 없게 만드는 것이다
요청 횟수 제한을 공격을 완전히 막는 것으로 보면, 답할 수 없는 질문에 갇힙니다. 몇 번까지가 안전한가? 본 사이트는 다르게 봅니다. 이것은 공격자의 비용과 이득의 문제입니다. 시도 한 번에 드는 공격자의 비용(시간, 계산, CAPTCHA, 두 번째 요소)을 높이고, 시도 한 번으로 기대할 수 있는 이득(맞힐 확률, 맞혔을 때 얻는 것)을 낮춥니다. 성공률을 0으로 만들 필요는 없습니다. 해 볼 가치가 없게 만들면 됩니다.
그래서 본 사이트는 가혹한 잠금보다 지연과 두 번째 요소를 선호합니다. 돈만은 이 논리가 바뀌는 경우입니다. 믿을 수 있는 유일한 상한은 내 코드 밖에 있는 지출 한도입니다. 코드는 가끔 틀립니다. 실수가 있어도 끝없는 청구서가 나오지 않도록, 통제를 코드 밖에 두세요.
출처(공식)
- NIST SP 800-63B, "Digital Identity Guidelines: Authentication and Lifecycle Management" §5.2.2 Rate Limiting (Throttling) — pages.nist.gov (연속 실패를 100회 이하로 제한하는 SHALL, CAPTCHA, 길어지는 대기, IP 허용 목록, 위험 기반 기법, 성공 후 같은 IP의 이전 실패를 무시하는 SHOULD)
- OWASP API Security Top 10 2023, "API4:2023 Unrestricted Resource Consumption" — owasp.org (제한이 필요한 자원, 비용 영향, 외부 서비스 지출 한도 설정)
다음으로 읽기
- 2026년 주요 사례 비교: KDDI, Aflac, 디지털청, 사쿠라 인터넷의 공통점
- 실제 사례: 덴마크 CPR 등록부 부정 조회 (정상 고객의 조회 권한이 악용되었다)
- 실제 사례: 바이토루 유출(최대 388만 건의 이메일 주소) ("사양상의 결함"을 통한 대량 수집과 사용자당 레코드 상한)
- 전제: X-Forwarded-For 스푸핑과 신뢰 프록시 ("누구를 세는가"의 기초)
- 설계: 비밀번호 재설정의 설계 결함 / 다중 인증 고르는 법
- 비용: 도난당한 키, 청구서, 계정 정지 / AI가 쓴 코드에서 유출된 API 키
FAQ
Q로그인 실패 몇 번이면 계정을 잠가야 하나요? 3번? 5번?
NIST SP 800-63B는 검증자가 한 계정에 대한 연속 인증 실패를 100회 이하로 제한해야 한다고 요구합니다. 3번이나 5번은 표준의 요구 사항이 아닙니다. 같은 문서는 정상 사용자가 잠기지 않도록, 대신 인증 전 CAPTCHA, 계정이 최대 허용치에 가까워질수록 길어지는 대기 시간, 사용자가 이전에 인증한 적 있는 IP 주소의 허용 목록, 위험 기반 또는 적응형 기법을 요구합니다. 공격적인 잠금은 공격자에게 남의 계정을 일부러 얼려 버리는 수단을 주기도 합니다. 횟수만 줄이기보다 시도 한 번의 비용을 높이세요.
QIP 주소로 제한하면 충분한가요?
아닙니다. 리버스 프록시나 CDN 뒤에서는 앱이 보는 주소가 실제 발신지가 아닐 수 있습니다. X-Forwarded-For를 무조건 신뢰하면 공격자는 헤더만 바꿔서 다른 클라이언트가 되므로, 아무것도 세지 못하는 셈입니다. 또 정상 사용자도 회사 NAT나 모바일망을 통해 주소를 공유하므로, IP별 제한은 관계없는 사용자까지 막습니다. 계정, API 키, 확립된 세션처럼 행위 주체에 묶인 식별자를 주축으로 하고, IP는 보조 신호로 다루세요.
Q확인 메일 폭탄은 어떻게 막나요?
발송을 일으킬 수 있는 주체의 제한과, 같은 수신처로 가는 건수의 집계가 둘 다 필요합니다. 폼이 남의 주소를 받아 준다면, 공격자는 그 폼으로 제3자를 괴롭히면서 동시에 내 도메인의 발송 평판을 떨어뜨릴 수 있습니다. 수신 주소별 대기 시간, 재발송 최소 간격, 일정 시간 확인되지 않은 기록의 삭제를 조합하세요. 확인되지 않은 개인 정보를 덜 보유하게 되는 효과도 있습니다.
Q공개 기능 뒤에 AI API를 붙일 때 가장 큰 위험은 무엇인가요?
청구서입니다. OWASP API Security Top 10의 API4:2023 Unrestricted Resource Consumption은 API에 필요한 제한으로 실행 시간 제한, 메모리, 업로드 크기, 요청당 작업 수를 들고, 그와 함께 외부 서비스의 지출 한도도 포함합니다. 시나리오에는 월 13달러이던 청구가 8,000달러가 된 예가 있습니다. 요청 횟수를 제한해도 한 번에 비싼 작업은 막지 못합니다. 서비스 제공자 쪽 지출 한도를 설정하세요. 내 코드가 틀렸을 때도 버티는 유일한 상한입니다.