
DX Security — Rate Limit Bypass 방어 v1.0.0 안내서
PHP 5.6+ · IIS / Apache / Nginx 완전 호환 코어 Secure::rateLimit() 완전 재사용 코어 수정 없음 — 훅으로만 동작
목차
- Rate Limit Bypass란 무엇인가
- DXCMS 코어의 현재 방어 수준
- 이 플러그인의 역할
- 설치 방법
- 방어층 상세 설명
- Endpoint 정책 Registry
- 보안 로그 읽는 법
- 보안 강점 요약
- 개발자 API 가이드
1. Rate Limit Bypass란 무엇인가
공격자가 Rate Limit의 식별 기준(IP, Session 등)을 변경하거나 요청 경로를 바꿔 제한을 무력화하는 공격입니다.
우회 방법:
IP 변경:
IP A → 100회 → 차단
IP B → 100회 → 차단 우회
IP C → 100회 → 차단 우회
Session 변경:
Cookie 삭제 → 새 Session → Rate Limit 초기화
Endpoint 변경:
/api/login 차단됨 → /api/auth/login 시도
XFF 조작:
X-Forwarded-For: 1.1.1.1 → 2.2.2.2 → 3.3.3.3
→ IP Rate Limit 우회
핵심 원칙
Rate Limit을 IP 하나에만 묶지 않는다.
올바른 구조:
Canonical IP (14번 Proxy Trust 검증 결과)
+
Session
+
Account
+
Endpoint
+
Global
2. DXCMS 코어의 현재 방어 수준
코어 rateLimit() 구현
public function rateLimit($key, $limit = 30, $window = 60)
{
$ip = $this->clientIp(); // 14번 Proxy Trust 검증된 IP 사용
// Redis가 있으면 원자적 INCR (Race Condition 방어)
if (self::$redis !== null) {
$slot = floor(time() / $window);
$sKey = 'rl:' . $key . ':' . $slot;
$count = self::$redis->incr($sKey);
if ($count === 1) self::$redis->expire($sKey, $window + 5);
return $count <= $limit;
}
// Redis 없으면 파일 기반 폴백
return $this->fileRateLimit($key, $ip, $limit, $window);
}
코어 평가
| 항목 | 방어 | 평가 |
|---|---|---|
| Redis 원자적 INCR | Race Condition 방어 | ✅ |
| 파일 기반 폴백 | 공유호스팅 호환 | ✅ |
| Canonical IP 사용 | clientIp() = Proxy Trust 검증 | ✅ |
| 다층 Key 없음 | IP 단일 계층만 | ❌ |
| Endpoint 정책 없음 | 개별 정의 필요 | ❌ |
| 429/Retry-After 없음 | 표준 응답 없음 | ❌ |
| Global Rate Limit 없음 | Endpoint 변경 우회 가능 | ❌ |
3. 이 플러그인의 역할
코어 Secure::rateLimit()을 완전히 재사용합니다. 저장소(Redis/파일)를 그대로 공유하면서 다층 Key와 정책 Registry를 추가합니다.
4. 설치 방법
(DXCMS 루트)/
└── plugins/
└── dx-security-ratelimit/
├── manifest.php
└── plugin.php
관리자 → 플러그인 → dx-security-ratelimit → 활성화
5. 방어층 상세 설명
RL1. IP + Session + Account + Global 다층 Rate Limit
단일 Key(IP만) 방어에서 4계층 방어로 강화합니다.
요청 들어옴
↓
[1] Global Rate Limit (Endpoint 변경 우회 방어)
↓ 통과
[2] IP Rate Limit (코어와 동일한 Canonical IP 사용)
↓ 통과
[3] Session Rate Limit (IP 변경 우회 방어)
↓ 통과
[4] Account Rate Limit (Session 변경 우회 방어, 로그인 시)
↓ 통과
비즈니스 로직 실행
각 계층에서 초과 시 즉시 429 반환.
RL2. Endpoint별 정책 Registry
각 엔드포인트마다 독립적인 Rate Limit 정책을 선언합니다.
login:
IP — 60초 15회
Session — 60초 10회
Account — 300초 5회
Global — 60초 300회
password_reset:
IP — 600초 5회
Session — 600초 3회
Account — 3600초 3회 (계정당 1시간에 3회)
Global — 60초 30회
RL3. Rate Limit Bypass 탐지 로그
Rate Limit 초과 시 탐지 이벤트를 기록합니다.
RL4. 표준 429 / Retry-After 응답
코어는 false만 반환합니다. 이 플러그인은 표준 HTTP 응답을 제공합니다.
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Policy: login
일반 브라우저:
<h1>429 Too Many Requests</h1>
AJAX/JSON 요청:
{
"success": false,
"message": "요청이 너무 많습니다. 60초 후 다시 시도하세요.",
"code": "RATE_LIMITED",
"retry_after": 60
}
RL5. Global POST Rate Limit
Endpoint를 계속 변경하는 공격을 방어합니다.
공격:
/api/login → 차단됨
/api/auth → 차단됨
/api/member → 차단됨
...
→ 각 Endpoint는 제한 미도달
방어:
IP별 모든 POST 요청 — 60초 100회
→ Endpoint 변경해도 상위 제한 적용
6. Endpoint 정책 Registry
| Endpoint | IP 제한 | Session 제한 | Account 제한 | Global 제한 |
|---|---|---|---|---|
login |
15회/60초 | 10회/60초 | 5회/300초 | 300회/60초 |
admin_login |
5회/300초 | 3회/300초 | 3회/600초 | 20회/60초 |
register |
5회/300초 | 3회/300초 | - | 50회/60초 |
password_reset |
5회/600초 | 3회/600초 | 3회/3600초 | 30회/60초 |
post_write |
10회/60초 | 8회/60초 | 5회/60초 | - |
comment |
30회/60초 | 20회/60초 | 15회/60초 | - |
upload |
20회/60초 | 15회/60초 | 10회/60초 | - |
og_fetch |
20회/60초 | 15회/60초 | - | - |
api_default |
60회/60초 | 50회/60초 | 40회/60초 | - |
7. 보안 로그 읽는 법
이벤트 타입
| 타입 | 의미 |
|---|---|
RL:EXCEEDED |
Rate Limit 초과 — 429 반환 |
RL:GLOBAL_POST_EXCEEDED |
Global POST Rate Limit 초과 |
RL:BYPASS_ATTEMPT |
코어 rateLimit false 반환 탐지 |
RL:PLUGIN_RL_IP |
플러그인 API — IP 초과 |
RL:PLUGIN_RL_SESSION |
플러그인 API — Session 초과 |
RL:PLUGIN_RL_ACCOUNT |
플러그인 API — Account 초과 |
로그 예시
# 로그인 IP Rate Limit 초과
[2026-09-03 09:00:01][RL:EXCEEDED][IP:45.33.x.x][...][UID:0][POST /auth/login]
endpoint=login layer=IP retry_after=60
# Session 변경 우회 시도 차단
[2026-09-03 14:22:10][RL:EXCEEDED][IP:58.29.x.x][...][UID:0][POST /auth/login]
endpoint=login layer=SESSION retry_after=60
# Global POST 초과 (Endpoint 변경 우회)
[2026-09-03 16:45:03][RL:GLOBAL_POST_EXCEEDED][IP:103.21.x.x][...][UID:0][POST /api/other]
uri=/api/other
8. 보안 강점 요약
| 우회 시도 | 결과 | 방어 레이어 |
|---|---|---|
| IP 변경 | Session/Account 계층에서 차단 | RL1 |
| Session 삭제/변경 | IP/Account 계층에서 차단 | RL1 |
| Account 변경 (다계정) | IP/Session 계층에서 차단 | RL1 |
| Endpoint 변경 | Global POST Rate Limit | RL5 |
| XFF 조작 | 코어 Canonical IP 사용 → 동일 Key | 코어+14번 |
| 비밀번호 재설정 반복 | 계정당 1시간 3회 | RL2 |
| 정상 사용자 | 정책 내에서 정상 처리 | ✅ |
9. 개발자 API 가이드
플러그인 엔드포인트 Rate Limit
// Registry 정책 사용
if (!dx_rate_limit('login')) {
dx_rate_limit_deny(60, 'login');
}
// 직접 정책 정의
if (!dx_rate_limit('my_plugin_vote', array(
'ip_limit' => 3, 'ip_window' => 60,
'acct_limit' => 1, 'acct_window' => 86400,
))) {
dx_rate_limit_deny(60, 'vote');
}
정책 Registry에 추가
// 플러그인 활성화 시 정책 등록
dx_rl_policy_add('my_plugin_api', array(
'ip_limit' => 10, 'ip_window' => 60,
'sess_limit' => 8, 'sess_window' => 60,
'acct_limit' => 5, 'acct_window' => 60,
'global_limit'=> 100, 'global_window'=> 60,
));
// 이후 사용
if (!dx_rate_limit('my_plugin_api')) {
dx_rate_limit_deny(60, 'my_plugin_api');
}
간단한 단일 계층 사용
// 빠른 단일 IP Rate Limit (코어 직접 사용)
$allowed = Secure::getInstance()->rateLimit('my_simple_action', 10, 60);
if (!$allowed) {
http_response_code(429);
exit;
}
DX Security — Rate Limit Bypass 방어 v1.0.0 — DesignOneX https://designonex.com
라이선스
디자인원엑스 라이선스 참고
https://designonex.com/notice/view/1787577858493247