
DX Security — Replay Attack 방어 v1.0.0 안내서
PHP 5.6+ · IIS / Apache / Nginx 완전 호환 Replay Store: Redis(있으면) / 파일 기반 자동 폴백 코어 수정 없음 — 훅으로만 동작
목차
1. Replay Attack이란 무엇인가
공격자가 정상 사용자의 요청·토큰·Webhook을 가로채거나 복사하여 동일한 작업을 반복 실행하는 공격입니다.
공격 시나리오:
① 비밀번호 재설정 링크 재사용:
/auth/reset?token=abc123
→ 1회 사용 후 다시 접근 → 비밀번호 다시 변경
② 결제 요청 재전송:
POST /api/payment {amount: 10000}
→ 캡처 후 반복 전송 → 중복 결제
③ Webhook 재전송:
외부 서비스 Webhook을 캡처
→ 반복 전송 → 동일 처리 반복
④ 관리자 명령 재전송:
회원 삭제 요청 캡처 → 반복 전송
핵심 원칙
"유효한 Token인가?"만 검사해서는 안 됩니다.
반드시:
Token + Expiration + Nonce + Session + Purpose + One-Time Use
를 함께 검사해야 합니다.
2. DXCMS 코어의 현재 방어 수준
| 항목 | 코어 구현 | 평가 |
|---|---|---|
| CSRF 토큰 | randomHex(64) + 만료 + hash_equals | ✅ |
| makeToken | HMAC-SHA256(id, join_date, secret_key) | ✅ |
| Session binding | 세션에 token 저장 | ✅ |
| Nonce 시스템 | 없음 | ❌ |
| Replay Store | 없음 | ❌ |
| One-Time Token 강제 | 없음 | ❌ |
| Idempotency Key | 없음 | ❌ |
| Webhook Replay 방어 | 없음 | ❌ |
| Timestamp 검증 | 없음 | ❌ |
3. 이 플러그인의 역할
코어의 CSRF/Session 체계를 유지하면서 Nonce, Replay Store, Idempotency, Webhook Replay 방어를 훅으로 추가합니다.
4. 설치 방법
(DXCMS 루트)/
└── plugins/
└── dx-security-replay/
├── manifest.php
└── plugin.php
관리자 → 플러그인 → dx-security-replay → 활성화
Replay Store 저장 위치: data/cache/replay/ (자동 생성)
5. 방어층 상세 설명
RP1. Nonce 발급 + 검증
서버가 발급하고 세션에 저장하는 One-Time 값입니다. 클라이언트 생성값을 신뢰하지 않습니다.
발급 흐름:
dx_replay_nonce_make('payment')
→ random_bytes(16) 생성
→ 세션에 저장 (Session Binding)
→ Nonce 값 반환
검증 흐름:
dx_replay_nonce_verify($nonce, 'payment')
→ 세션 저장값과 비교 (timing-safe hash_equals)
→ Purpose Binding 확인
→ 만료 시간 확인
→ Replay Store 원자적 소비
→ 세션에서 제거 (One-Time 보장)
→ 성공/실패 반환
RP2. Replay Store — 원자적 소비
동일 Nonce가 두 번 사용되지 않도록 원자적으로 처리합니다.
Redis 환경:
SET key "1" NX EX ttl
→ SETNX 원자적 연산
→ Race Condition 방어
파일 기반 폴백:
fopen + LOCK_EX + 상태 확인
→ Race Condition 최소화
Race Condition 시나리오:
Request A → Nonce 없음 → 등록
Request B → Nonce 없음 → 등록 ← 동시에 판단 가능
Redis SETNX: 하나만 성공, 나머지 실패 → 1회만 처리
RP3. Timestamp 검증
요청 시간이 허용 범위(기본 ±5분) 밖이면 거부합니다.
허용: 현재 시각 ± 300초 이내
거부: 5분 이상 오래된 요청 또는 미래 요청
단독으로는 부족합니다:
Timestamp만 → 5분 내 재전송 가능
Timestamp + Nonce → 재전송 완전 차단
RP4. One-Time Token 강제
비밀번호 재설정·이메일 인증 토큰을 훅에서 한 번 소비합니다.
비밀번호 재설정 (dx_before_password_reset 훅):
token + user_id → Replay Store 확인
→ 신규: 처리 허용 + 등록 (1시간 보관)
→ 중복: 400 + "이미 사용된 재설정 링크" 반환
이메일 인증 (dx_before_email_verify 훅):
token + user_id → Replay Store 확인
→ 신규: 처리 허용 + 등록 (24시간 보관)
→ 중복: 400 + "이미 사용된 인증 링크" 반환
RP5. Idempotency Key
결제·주문 등 중복 처리 방지를 위한 Idempotency Key를 지원합니다.
첫 요청:
Idempotency-Key: 8f72a3b...
→ 처리 실행
→ 결과 저장 (24시간)
재요청 (동일 Key):
Idempotency-Key: 8f72a3b...
→ 캐시에서 동일 결과 반환
→ IDEMPOTENCY_REPLAY 로그
재전송 vs 중복 방지:
Replay Attack: 공격자의 악의적 재전송 → 차단
Idempotency: 네트워크 오류로 인한 정상 재전송 → 동일 결과 반환
RP6. Webhook Replay 방어
외부 서비스의 Webhook을 Event ID 기준으로 중복 처리하지 않습니다.
첫 Webhook:
event_id = EVT-8F92
→ 처리 실행
→ Replay Store 등록 (24시간)
→ 200 OK
재전송 Webhook:
event_id = EVT-8F92
→ Replay Store 확인 → 이미 처리됨
→ WEBHOOK_REPLAY 로그
→ 200 {"status": "already_processed"}
(재전송 방지를 위해 200으로 응답)
Event ID 없는 Webhook:
→ WEBHOOK_NO_EVENT_ID 로그 (차단은 하지 않음)
RP7. 관리자 민감 작업 Replay 탐지
감시 대상 작업:
member_delete, member_ban, permission_change
plugin_install, plugin_delete, theme_change
config_update, admin_create
동일 작업+대상 5분 내 중복 시:
→ ADMIN_REPLAY_DETECTED 로그
(차단은 하지 않음 — 관리자 판단에 맡김)
6. 보안 로그 읽는 법
이벤트 타입
| 타입 | 의미 |
|---|---|
RP:NONCE_NONCE_EMPTY |
Nonce 값 없음 |
RP:NONCE_NONCE_EXPIRED |
Nonce 만료 |
RP:NONCE_NONCE_MISMATCH |
Nonce 불일치 |
RP:NONCE_NONCE_ALREADY_USED |
Nonce 재사용 탐지 |
RP:NONCE_PURPOSE_MISMATCH |
Purpose 불일치 (교차 사용 시도) |
RP:TIMESTAMP_MISSING |
Timestamp 없음 |
RP:TIMESTAMP_EXPIRED |
Timestamp 범위 초과 |
RP:TOKEN_REUSED |
비밀번호 재설정/이메일 인증 토큰 재사용 |
RP:WEBHOOK_REPLAY |
Webhook 재전송 탐지 |
RP:WEBHOOK_NO_EVENT_ID |
Event ID 없는 Webhook |
RP:WEBHOOK_ACCEPTED |
Webhook 정상 처리 |
RP:IDEMPOTENCY_REPLAY |
Idempotency Key 중복 요청 |
RP:ADMIN_REPLAY_DETECTED |
관리자 작업 반복 탐지 |
로그 예시
# 비밀번호 재설정 링크 재사용
[2026-09-03 09:00:01][RP:TOKEN_REUSED][IP:45.33.x.x][...][UID:0][GET /auth/reset]
type=password_reset user_id=42
# Nonce 재사용 (Replay 탐지)
[2026-09-03 14:22:10][RP:NONCE_NONCE_ALREADY_USED][IP:58.29.x.x][...][UID:1][POST /api/payment]
purpose=payment
# Webhook 재전송
[2026-09-03 16:45:03][RP:WEBHOOK_REPLAY][IP:103.21.x.x][...][UID:0][POST /api/webhook]
source=payment_gateway event_id=EVT-8F92
# Idempotency Key 중복
[2026-09-03 20:11:44][RP:IDEMPOTENCY_REPLAY][IP:121.131.x.x][...][UID:5][POST /api/order]
key=order_8f72a3b user_id=5
7. 보안 강점 요약
| 공격 | 결과 | 방어 레이어 |
|---|---|---|
| 비밀번호 재설정 링크 재사용 | 400 + 차단 | RP4 |
| 이메일 인증 링크 재사용 | 400 + 차단 | RP4 |
| Nonce 재사용 | false 반환 + 로그 | RP1, RP2 |
| Nonce Purpose 교차 사용 | false 반환 + 로그 | RP1 |
| 만료된 Nonce 재사용 | false 반환 + 로그 | RP1 |
| Webhook 재전송 | 200 already_processed | RP6 |
| Idempotency 중복 결제 | 동일 결과 반환 | RP5 |
| 관리자 명령 반복 | 탐지 로그 | RP7 |
| 오래된 Timestamp 요청 | false 반환 | RP3 |
| 정상 첫 요청 | 통과 | ✅ |
8. 개발자 API 가이드
Nonce 기반 Replay 방어
// 1. 폼 생성 시 Nonce 발급
$nonce = dx_replay_nonce_make('payment');
echo '<input type="hidden" name="_rp_nonce" value="' . $nonce . '">';
echo '<input type="hidden" name="_rp_ts" value="' . time() . '">';
// 2. 처리 시 Nonce + Timestamp 검증
$nonce = isset($_POST['_rp_nonce']) ? $_POST['_rp_nonce'] : '';
$ts = isset($_POST['_rp_ts']) ? (int)$_POST['_rp_ts'] : 0;
if (!dx_replay_timestamp_check($ts)) {
dx_error('요청 시간이 유효하지 않습니다.', 400);
}
if (!dx_replay_nonce_verify($nonce, 'payment')) {
dx_error('이미 처리된 요청입니다.', 400);
}
// 3. 처리 로직 실행
processPayment();
Idempotency Key 처리
$idempKey = isset($_POST['idempotency_key']) ? $_POST['idempotency_key'] : '';
// 이미 처리된 요청인지 확인
$cached = dx_replay_idempotency_check($idempKey);
if ($cached !== null) {
exit(json_encode($cached)); // 동일 결과 반환
}
// 처리 로직
$result = array('success' => true, 'order_id' => createOrder());
// 결과 저장 (재요청 대비)
dx_replay_idempotency_save($idempKey, $result);
exit(json_encode($result));
Webhook Replay 방어 (훅 자동 적용)
// dx_before_webhook 훅이 자동으로 Event ID 중복 차단
// 훅 호출 예시 (코어 또는 플러그인에서)
$args = array(
'event_id' => $webhookPayload['id'],
'source' => 'payment_gateway',
'data' => $webhookPayload,
);
$args = dx_run_hook('dx_before_webhook', $args);
if (!empty($args['blocked'])) exit; // Replay 차단
DX Security — Replay Attack 방어 v1.0.0 — DesignOneX https://designonex.com
라이선스
디자인원엑스 라이선스 참고
https://designonex.com/notice/view/1787577858493247