회원가입 | 고객센터 |
DESIGNONEX
DesignOneX CMS
로그인 회원가입
고객센터

DX Security — Replay Attack 보안 플러그인

D DX관리자
2026.09.05 18:30(수정됨) 5 0



DX Security — Replay Attack 방어 v1.0.0 안내서

PHP 5.6+ · IIS / Apache / Nginx 완전 호환 Replay Store: Redis(있으면) / 파일 기반 자동 폴백 코어 수정 없음 — 훅으로만 동작


목차

  1. Replay Attack이란 무엇인가
  2. DXCMS 코어의 현재 방어 수준
  3. 이 플러그인의 역할
  4. 설치 방법
  5. 방어층 상세 설명
  6. 보안 로그 읽는 법
  7. 보안 강점 요약
  8. 개발자 API 가이드

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

 

첨부파일 1개 회원전용

댓글0

로그인 후 댓글을 작성할 수 있습니다.
Plugin 58
45
전체 회원
1,396
전체 게시글
2,748
전체 댓글
84
오늘 방문
53,602
전체 방문
2
현재 접속
인기글 7일 이내
최신글
최신댓글
내 플레이리스트
플레이리스트가 비어있습니다
스튜디오 게시판에서
플레이리스트에 담기 버튼을
눌러보세요
목록
목록