회원가입 | 고객센터 |
DESIGNONEX
디자인원엑스
Q&A
지식공유
공지사항
DX마켓
통계
로그인 회원가입
고객센터

DX Security — Session Hijacking 보안 플러그인

D DX관리자
2026.09.01 14:58 5 0



DX Security — Session Hijacking 방어 v1.0.0 안내서

DesignOneX CMS Session Hijacking 2차 방어 플러그인
PHP 5.6+ · IIS / Apache / Nginx · 공유호스팅 완전 호환
코어(Auth.php / Secure.php / index.php) 수정 없음 — 훅으로만 동작



목차

  1. Session Hijacking이란 무엇인가
  2. DXCMS 코어의 현재 방어 수준
  3. 코어가 막지 못하는 것 — 이 플러그인의 존재 이유
  4. 설치 방법
  5. 방어층 상세 설명
    • G1. Session Guard — Identity · Risk · Version 3중 검증
    • G2. Remember Token Reuse Detection
    • G3. session_restore / session_extend 세션 ID 교체 보완
    • G4. Remember Me Cookie SameSite 정책 통일
    • G5. 관리자 세션 재인증 시간 검증
    • G6. 탈취 확정 시 Remember Token 연쇄 폐기
  6. 훅 실행 타이밍 전체 흐름
  7. 보안 로그 읽는 법
  8. 보안 강점 요약 — 어디까지 방어되는가
  9. 방어하지 못하는 영역
  10. 플러그인 1(dx-security-guard)과의 관계
  11. 다른 플러그인·테마에서 연동하기
  12. 값 변경 방법
  13. 자주 묻는 질문


1. Session Hijacking이란 무엇인가

Session Fixation과 Session Hijacking은 반드시 구분해야 합니다.

Session Fixation (플러그인 1이 방어)
  공격자가 세션 ID를 미리 알고 있음
  → 피해자가 로그인
  → 같은 세션 ID가 인증 세션으로 승격
  → 공격자가 그 ID로 접근

Session Hijacking (이 플러그인이 방어)
  정상 사용자가 로그인
  → 정상 세션 ID 발급
  → 공격자가 세션 ID를 사후에 탈취
  → 공격자가 그 ID로 접근

Session Fixation을 완벽하게 막는다고 Session Hijacking까지 방어되는 것이 아닙니다.
두 가지는 공격 타이밍이 다르고, 방어 방법도 다릅니다.


세션 ID 탈취 경로

공격자가 세션 ID를 탈취하는 주요 경로는 다음과 같습니다.

① XSS — document.cookie 로 세션 쿠키 탈취
   (HttpOnly가 있으면 난이도 대폭 상승하지만 완전 차단은 아님)

② 네트워크 도청 — HTTP 평문 통신 감청
   (Secure 쿠키 + HTTPS가 있으면 차단, HTTP 구간이 있으면 위험)

③ 서버 파일 직접 접근 — 세션 저장 디렉토리 권한 탈취

④ Remember Me 쿠키 탈취 — 장기 자격증명 도용
   세션 ID 대신 30일짜리 Remember Token을 탈취하면
   동일한 효과로 장기간 인증 상태 유지 가능

⑤ 중간자 공격 (MITM) — 프록시를 통한 쿠키 가로채기

⑥ 로그 파일에서 세션 ID 노출 — 잘못된 로깅 설정


Session Hijacking의 본질

탈취된 세션 ID로 접근이 들어오면, 서버는 정상 사용자의 요청인지 공격자의 요청인지 구별하기 어렵습니다.

서버의 세션 검증 흐름:
  요청 수신 → 세션 ID 확인 → $_SESSION 로드 → 사용자 정보 확인

문제:
  세션 ID만 맞으면 서버는 인증된 것으로 처리
  → 공격자의 요청도 정상으로 처리됨

따라서 세션 ID가 탈취되더라도 공격자가 장시간 사용하지 못하도록 하는 2차 방어층이 필요합니다. 이것이 이 플러그인의 역할입니다.



2. DXCMS 코어의 현재 방어 수준

코어를 분석한 결과, 이미 상당히 높은 수준의 방어가 구현되어 있습니다.

항목 코어 구현 평가
Cookie 기반 세션 서버 측 저장, 예측 불가
HttpOnly ✅ Secure.php initSession() XSS의 document.cookie 탈취 난이도 상승
Secure (HTTPS에서만 전송) ✅ HTTPS 감지 후 적용 네트워크 도청 방어
SameSite=Lax (세션 쿠키) ✅ PHP 7.3+ 적용 CSRF 기반 세션 탈취 방어
session.use_strict_mode 서버 미발급 세션 ID 거부
HMAC-SHA256 세션 토큰 ✅ Auth::makeToken() 세션 ID만으론 계정 위조 불가
Remember Token 엔트로피 ✅ randomHex(64) = 256bit 브루트포스 사실상 불가
Remember Token Rolling ✅ tryRememberMe() 사용 후 즉시 새 토큰으로 교체
세션 활동 기록 ✅ DxMemberMonitor IP·UA·시각 기록
세션 키 SHA-256 저장 ✅ sessionKey() DB에 세션 ID 원문 미저장

DXCMS 코어 Session Hijacking 방어 자체 평가: 8/10


코어의 HMAC 토큰이 강력한 이유

// Auth::makeToken()
$fixedSeed = $user['join_date'];  // 변하지 않는 고정값
$payload   = $user['id'] . '|' . $fixedSeed;
$secret    = dx_config('secret_key');  // 사이트마다 다른 64자리 랜덤 키
return hash_hmac('sha256', $payload, $secret);

공격자가 세션 ID를 알더라도, 세션 내부의 HMAC 토큰을 위조하려면 사이트의 secret_key를 알아야 합니다. 소스 코드가 공개되어도 secret_key는 각 사이트마다 다르기 때문에 토큰 위조가 사실상 불가능합니다.



3. 코어가 막지 못하는 것 — 이 플러그인의 존재 이유

코어에서 아직 처리하지 않는 6가지 취약점입니다.


❌ 취약점 1: session_restore / session_extend 후 세션 ID 미교체

팝업 로그인(session_restore.php) 또는 자동 재로그인(session_extend.php) 완료 후, 코어는 세션에 사용자 정보를 기록하지만 세션 ID를 새로 발급하지 않습니다.

공격자 시나리오:
  공격자가 피해자의 만료된 세션 ID를 수집해 보관
  피해자가 팝업 로그인으로 세션을 복구
  → session_restore.php가 같은 세션 ID에 인증 정보를 기록
  → 공격자의 구 세션 ID로 인증된 세션 접근 가능


❌ 취약점 2: Remember Me 쿠키에 SameSite 속성 누락

코어 Auth.php에서 dx_remember 쿠키를 발급할 때 SameSite 속성이 지정되지 않습니다.

// 코어 Auth.php (세션 쿠키는 SameSite=Lax, 그러나 Remember 쿠키는)
setcookie('dx_remember', $userId . ':' . $remToken,
    time() + 2592000, '/', '', $isHttps, true);
    // SameSite 없음 → 브라우저 기본값(None 또는 구버전에서 허용) 적용

세션 쿠키와 Remember Me 쿠키의 보안 정책이 다르면 일관성 없는 방어선이 만들어집니다.


❌ 취약점 3: Remember Token Reuse Detection 없음

코어는 Remember Token을 사용 후 새 토큰으로 Rolling(갱신)합니다. 그러나 이미 갱신된 구 토큰이 재사용되는 것을 감지하지 못합니다.

정상 흐름:
  피해자 기기: Token A → 사용 → Token B 발급

탈취 시나리오:
  공격자가 Token A를 중간에 탈취해 보관
  피해자가 접속 → Token B로 갱신
  공격자가 Token A로 접속 시도 → 코어는 "토큰 없음"으로 실패 처리
  
  그러나:
  공격자가 Token A 탈취 직후 먼저 접속 → Token B 발급 (공격자에게)
  피해자가 Token A로 접속 → 실패 (갱신된 토큰이 없으므로)
  → 코어는 이것이 탈취 신호임을 감지하지 못함

구 토큰이 재사용됐다는 것은 누군가 해당 토큰을 탈취하여 먼저 사용했다는 신호입니다.


❌ 취약점 4: 탈취 감지 후 Remember Token 연쇄 폐기 없음

코어는 session_version 불일치(플러그인 1)나 기타 이상 상황에서 현재 세션만 파기합니다. Remember Token은 DB에 그대로 남아있어 공격자가 자동 재로그인 경로로 다시 진입할 수 있습니다.


❌ 취약점 5: 관리자 세션 재인증 체계 없음

관리자가 로그인한 후 오랜 시간이 지나도 $_SESSION['is_admin'] === true인 한 중요 작업을 무제한으로 실행할 수 있습니다. 공격자가 관리자 세션을 탈취하면 즉시 중요 작업을 실행할 수 있습니다.


❌ 취약점 6: Identity 재검증 없음 (세션 복구 후)

session_restore / session_extend 직후, HMAC 토큰이 현재 DB 회원 정보와 일치하는지 재검증하지 않습니다. 세션 데이터가 변조된 경우를 감지하지 못합니다.



4. 설치 방법


4-1. 권장 설치 순서

이 플러그인은 플러그인 1(dx-security-guard)과 함께 사용할 때 최대 효과를 발휘합니다.

설치 순서:
  1. dx-security-guard (Session Fixation, session_version, Timeout)
  2. dx-security-hijacking (이 플러그인)

단독 설치도 가능하며, 플러그인 1이 없으면 일부 기능이 자체적으로 처리됩니다.


4-2. 파일 배치

(DXCMS 루트)/
└── plugins/
    └── dx-security-hijacking/
        ├── manifest.php
        └── plugin.php


4-3. 관리자 페이지에서 활성화

관리자 → 플러그인 → dx-security-hijacking → 활성화


4-4. 자동 DB 스키마 적용

활성화 후 첫 번째 페이지 로드 시 자동 실행됩니다.

-- G2: Remember Token Reuse Detection용
ALTER TABLE `dx_members`
  ADD COLUMN `remember_prev_token` VARCHAR(128) NULL DEFAULT NULL
  COMMENT 'Hijacking Guard: 이전 Remember Token SHA-256 해시 (Reuse Detection용)';

-- G5: 관리자 재인증 시각 기록용
ALTER TABLE `dx_members`
  ADD COLUMN `admin_reauth_at` DATETIME NULL DEFAULT NULL
  COMMENT 'Hijacking Guard: 관리자 마지막 재인증 시각';

적용 조건:

  • 이미 컬럼이 있으면 완전히 스킵 (멱등성 보장)
  • 완료 후 data/shj_v1_installed 플래그 파일 생성 → 이후 SHOW COLUMNS 생략
  • 실패해도 사이트 정상 운영 (해당 기능만 graceful degradation)


5. 방어층 상세 설명



G1. Session Guard — Identity · Risk · Version 3중 검증

매 요청마다 로그인 세션을 3가지 관점에서 검증합니다.
dx_extend_top 훅(priority 2)에서 실행 — 모든 요청에 적용됩니다.


Identity Check — HMAC 토큰 재검증

코어 Auth::loadSession()이 세션 로드 시 HMAC 토큰을 검증하지만, session_restoresession_extend 후에는 재검증이 누락됩니다. 이 플러그인은 로그인된 세션의 모든 요청에서 HMAC 토큰을 재검증합니다.

매 요청
  ↓
세션에서 HMAC 토큰 읽기
  ↓
DB에서 현재 회원 정보(id, join_date) 조회
  ↓
hash_hmac('sha256', id|join_date, secret_key) 재계산
  ↓
세션 토큰과 비교 (hash_equals — 타이밍 공격 방지)
  ↓
불일치 → 토큰 위조 또는 계정 변경 의심
  → _dx_shj_full_revoke() 호출 (G6 연쇄 폐기)
  → SESSION_HIJACK_SUSPECTED 로그
  → 로그아웃 처리


Risk Check — 위험 점수 계산

IP와 UA는 단독으로 세션을 끊지 않습니다. 모바일 사용자는 LTE → Wi-Fi 전환 시 IP가 바뀌고, 브라우저 업데이트로 UA가 변경될 수 있기 때문입니다. 여러 신호를 조합한 점수로만 판단합니다.

이상 신호 점수 비고
IP 변화 +20 로그인 시 IP와 다름 (md5 해시 비교)
UA 변화 +10 로그인 시 UA와 다름 (md5 해시 비교)
UA 없음 · 헤더 인젝션 +30 자동화 도구, 이상 클라이언트
관리자 페이지 접근 +20 /admin 경로 접근 시
관리자 + IP 변화 동시 +30 위 두 가지 동시 발생 시 추가 가중
점수 조치
0 ~ 29 정상 통과
30 ~ 59 SESSION_RISK 경고 로그, 세션 유지
60 ~ 79 __sg_needs_reauth = true 재인증 플래그, __shj_hijack = true 탈취 의심 플래그
80 이상 세션 즉시 폐기. 관리자이면 Remember Token까지 연쇄 폐기(G6)


IP · UA를 세션에 저장하는 방식

IP와 UA 원문을 세션에 저장하지 않습니다. md5() 해시로 저장하여 세션 파일에 개인정보가 남지 않게 합니다.

로그인 시:
  $_SESSION['__shj_ip'] = md5(clientIp())  ← IP 해시
  $_SESSION['__shj_ua'] = md5(User-Agent)  ← UA 해시

매 요청:
  현재 IP 해시와 저장된 해시 비교
  현재 UA 해시와 저장된 해시 비교


G2. Remember Token Reuse Detection

왜 필요한가

코어의 Remember Token Rolling은 사용 후 새 토큰을 발급합니다. 그러나 이미 발급된 구 토큰이 나중에 재사용되는 것을 감지하지 못합니다.

구 토큰이 재사용됐다는 것은 누군가 그 토큰을 탈취하여 이미 사용했거나, 탈취 후 재시도한다는 신호입니다.


동작 원리

정상 흐름:
  Token A → 자동 로그인 → Token B 발급 (Rolling)
  remember_prev_token = SHA-256(Token A) 저장

탈취 감지 흐름:
  공격자가 Token A를 탈취하여 보관
  정상 사용자가 Token A로 접속 → Token B로 갱신
  공격자가 Token A로 접속 시도
    → incoming Token의 SHA-256 해시를 prev_token과 비교
    → 일치 → "이 토큰은 이미 Rotate된 구 토큰"
    → 탈취로 판정
    → _dx_shj_full_revoke() 호출 (G6 연쇄 폐기)
    → REMEMBER_REUSED 로그


DB 저장 방식

-- 자동 로그인 Rolling 발생 시
UPDATE `dx_members`
SET remember_prev_token = SHA256(이전 토큰 원문)
WHERE id = ?

-- 원문이 아닌 SHA-256 해시만 저장 → DB 유출 시에도 토큰 원문 노출 없음


주의사항

Reuse Detection이 발동하면 정상 사용자도 재로그인해야 합니다. 이는 의도된 동작입니다. 탈취 의심 상황에서 보안을 우선시하는 정책입니다. 관리자는 보안 로그에서 REMEMBER_REUSED를 확인하고 해당 사용자에게 알릴 수 있습니다.

훅 연동 안내: dx_after_remember_rotate 훅은 현재 DXCMS 코어에 없습니다. Reuse Detection의 완전한 동작을 위해 코어의 Rolling 처리 코드에 이 훅 실행을 추가하는 것을 권장합니다.
dx_run_hook('dx_after_remember_rotate', ['user_id' => $userId, 'old_token' => $oldToken]);



G3. session_restore / session_extend 세션 ID 교체 보완

문제

코어의 두 API는 세션 복구 후 Session ID를 교체하지 않습니다.

session_restore.php (팝업 로그인 후 세션 복구):
  DB 토큰 검증 → $_SESSION[dx_user] 생성 → 완료
  Session ID 교체 없음 ❌

session_extend.php (Remember Me 자동 재로그인):
  dx_remember 쿠키 검증 → $_SESSION[dx_user] 생성 → 완료
  Session ID 교체 없음 ❌

세션 복구는 익명 세션 → 인증 세션으로 신뢰 수준이 올라가는 순간입니다. 이 순간에 Session ID를 교체하지 않으면 Session Fixation과 유사한 위험이 발생합니다.


해결

두 API 모두 dx_after_login 훅을 실행합니다. 이 플러그인은 그 시점에 세션 ID를 교체합니다.

session_restore.php 또는 session_extend.php 실행
  ↓
dx_after_login 훅 실행
  ↓
이 플러그인 (priority 5):
  ① Reuse Detection 확인 (G2)
  ② SameSite 쿠키 재발급 (G4)
  ③ Security Guard 플러그인이 없으면 세션 ID 교체
     (있으면 이미 처리됨 — __sg_verified 마커로 판단)
  ④ Risk Engine 기준값 초기화

추가로 dx_after_session_restore 전용 훅도 등록해두어, 코어에 이 훅이 추가되면 자동으로 동작합니다.



G4. Remember Me Cookie SameSite 정책 통일

문제

코어 Auth.php에서 dx_remember 쿠키를 발급하는 모든 경로에 SameSite 속성이 없습니다.

// 코어 Auth::login() — SameSite 없음
setcookie('dx_remember', $userId.':'.$remToken,
    time()+2592000, '/', '', $isHttps, true);

// 코어 Auth::loginById() — SameSite 없음
setcookie('dx_remember', $member['id'].':'.$remToken,
    time()+2592000, '/', '', $isHttps, true);

// 코어 tryRememberMe() Rolling — SameSite 없음
setcookie('dx_remember', $userId.':'.$newToken,
    time()+2592000, '/', '', $isHttps, true);

세션 쿠키는 SameSite=Lax가 적용되는데, Remember Me 쿠키만 SameSite 없이 발급되면 쿠키 정책이 불일치합니다.


해결

dx_after_login 훅(priority 5)에서 코어가 발급한 쿠키를 SameSite=Lax 속성으로 즉시 덮어씁니다. 토큰 값은 동일하게 유지하고 쿠키 속성만 교체합니다.

// PHP 7.3+
setcookie('dx_remember', $userId.':'.$token, [
    'expires'  => time() + 2592000,
    'path'     => '/',
    'secure'   => $isHttps,
    'httponly' => true,
    'samesite' => 'Lax',   // ← 추가
]);

// PHP 5.6~7.2 (path에 직접 주입하는 호환 방식)
setcookie('dx_remember', $userId.':'.$token,
    time()+2592000, '/; SameSite=Lax', '', $isHttps, true);

로그아웃 시에도 동일한 속성으로 쿠키를 삭제합니다.



G5. 관리자 세션 재인증 시간 검증

왜 필요한가

관리자 세션이 탈취되면 공격자는 모든 관리 기능에 즉시 접근할 수 있습니다. 로그인 시각과 무관하게 세션이 유효한 한 중요 작업을 무제한으로 실행할 수 있기 때문입니다.


동작 원리

관리자가 민감한 관리 경로에 접근할 때 마지막 재인증 시각을 확인합니다.

관리자가 /admin/member, /admin/plugin 등 중요 경로 접근
  ↓
마지막 재인증 시각 확인 (__shj_admin_auth 세션 값)
  ↓
10분(DX_SHJ_ADMIN_REAUTH_TTL) 이내: 정상 통과
10분 초과 또는 미기록: 재인증 요구
  ↓
  일반 요청: __shj_admin_reauth_required = true 플래그 설정
            관리자 레이아웃에서 재인증 모달 표시
  AJAX 요청: HTTP 403 + JSON 응답 즉시 차단
            { "code": "ADMIN_REAUTH_REQUIRED" }


재인증 검증 대상 경로

/admin/member     — 회원 관리 (개인정보 접근)
/admin/plugin     — 플러그인 설치/삭제 (코드 실행)
/admin/config     — 사이트 설정 변경
/admin/file       — 파일 관리 (파일 업로드/삭제)
/admin/permission — 권한 관리
/admin/log        — 보안 로그 조회


재인증 완료 처리

관리자가 비밀번호를 재입력하면 dx_admin_reauth_success 훅을 실행해야 합니다.

// 관리자 재인증 처리 코드에서
if ($passwordVerified) {
    dx_run_hook('dx_admin_reauth_success', ['user_id' => $adminId]);
}

훅이 실행되면:

  1. __shj_admin_auth = 현재 시각 (세션)
  2. admin_reauth_at = 현재 시각 (DB)
  3. 재인증 요구 플래그 해제


세션 만료·재인증 타이머 구조

관리자 로그인
  ↓ (첫 중요 경로 접근 시 재인증 요구)
비밀번호 재입력
  ↓ 10분 타이머 시작
  ↓ 10분간 중요 작업 가능
  ↓ 10분 경과
재인증 재요구


G6. 탈취 확정 시 Remember Token 연쇄 폐기

Identity Check 실패, Reuse Detection, Risk 80+ 등 탈취가 확정적일 때 실행합니다.


연쇄 폐기 순서

_dx_shj_full_revoke($userId, $reason) 호출
  ↓
① DB: remember_token = NULL
       remember_expires = NULL
       remember_prev_token = NULL
  → 모든 자동 로그인 경로 차단

  ↓
② 브라우저: dx_remember 쿠키 삭제 (SameSite=Lax 속성 포함)
  → 현재 브라우저의 자동 로그인 차단

  ↓
③ session_version++ (플러그인 1과 동일 방식)
  → 모든 활성 세션이 다음 요청에서 무효화
  → 공격자가 가진 다른 모든 세션도 폐기

  ↓
④ 현재 세션 파기 (데이터·쿠키·서버 파일 삭제)
  → 새 빈 세션 ID 발급

  ↓
⑤ SESSION_HIJACK_SUSPECTED + REMEMBER_REVOKED 로그 기록


플러그인 1(session_version)과의 시너지

session_version 불일치 감지 (플러그인 1)
  ↓
dx_session_version_mismatch 훅 실행
  ↓
이 플러그인 (G6):
  세션 파기 + Remember Token 폐기 + session_version++ (연쇄)
  → 공격자의 모든 접근 경로 동시 차단


6. 훅 실행 타이밍 전체 흐름

[모든 요청]
      │
      ▼
index.php
  load_plugins()
    └── dx-security-hijacking/plugin.php 로드
          ├── _dx_shj_install()  (최초 1회: DB 컬럼 추가)
          └── 훅 7개 등록

  DxExtend::runTop()  ← 라우팅 전, 모든 요청
    └── dx_extend_top 훅
          ├── priority 1: dx-security-guard (session_version 검증)
          ├── priority 2: [G1] Session Guard (Identity·Risk 검증)  ← 이 플러그인
          └── priority 3: [G5] 관리자 재인증 시간 검증            ← 이 플러그인

[로그인 요청 — Auth::login() / loginById() / session_restore / session_extend]
  Auth 처리 완료
    └── dx_after_login 훅
          ├── priority 1: dx-security-guard (세션 ID 재생성)
          ├── priority 2: dx-security-guard (세션 메타 기록)
          └── priority 5: [G2][G3][G4] Reuse Detection + ID 교체 보완 + SameSite 적용
                                                                        ← 이 플러그인

[로그아웃 요청]
  Auth::logout()
    └── dx_after_logout 훅
          ├── priority 1: dx-security-guard (세션 완전 파기)
          └── priority 5: [G4] Remember Me 쿠키 SameSite 속성으로 삭제 ← 이 플러그인

[Remember Token Rolling — tryRememberMe() / session_extend.php]
  Rolling 완료
    └── dx_after_remember_rotate 훅 (코어에 추가 필요)
          └── priority 1: [G2] 이전 토큰 해시 기록                ← 이 플러그인

[관리자 재인증 완료]
  비밀번호 검증 성공
    └── dx_admin_reauth_success 훅
          └── priority 1: [G5] 재인증 시각 기록                   ← 이 플러그인

[session_version 불일치 — 플러그인 1이 감지]
    └── dx_session_version_mismatch 훅
          └── priority 1: [G6] Remember Token 연쇄 폐기            ← 이 플러그인


7. 보안 로그 읽는 법

data/security.log 에 기록됩니다. Secure.php, dx-security-guard와 동일한 파일이므로 한 곳에서 전체 보안 이벤트를 확인할 수 있습니다.


로그 형식

[날짜 시각][SHJ:이벤트타입][IP:클라이언트IP][UA:유저에이전트] 메시지


이벤트 타입 목록

타입 발생 시점 의미
SHJ:INSTALL_COLUMN 최초 설치 DB 컬럼 추가 완료
SHJ:INSTALL_ERROR 설치 실패 DB 컬럼 추가 실패
SHJ:SESSION_CREATED 로그인 완료 Session Guard 초기화 완료
SHJ:SESSION_ROTATED 로그인/복구 후 세션 ID 교체 완료
SHJ:REGEN_FAIL 세션 ID 교체 실패 서버 환경 점검 필요
SHJ:SESSION_RISK Risk 30+ 발생 점수·원인·조치 기록
SHJ:SESSION_HIJACK_SUSPECTED 탈취 확정 전체 폐기 시작
SHJ:REMEMBER_REUSED Reuse Detection 구 토큰 재사용 탐지
SHJ:REMEMBER_ROTATED Rolling 완료 이전 토큰 해시 기록
SHJ:REMEMBER_REVOKED 연쇄 폐기 Remember Token 전부 삭제
SHJ:ADMIN_REAUTH_REQUIRED 재인증 요구 중요 경로 접근, 10분 초과
SHJ:ADMIN_REAUTH 재인증 완료 관리자 비밀번호 재입력 성공


로그 예시 및 해석

# 정상 로그인 + Guard 초기화
[2026-09-01 09:00:01][SHJ:SESSION_ROTATED][IP:121.131.x.x][UA:Mozilla/5.0...] member_id=42 login session rotated
[2026-09-01 09:00:01][SHJ:SESSION_CREATED][IP:121.131.x.x][UA:Mozilla/5.0...] member_id=42 hijacking guard initialized

# 모바일 IP 변경 — 경고만 (세션 유지)
[2026-09-01 11:30:22][SHJ:SESSION_RISK][IP:175.200.x.x][UA:Mozilla/5.0 (iPhone...] member_id=42 score=30 reasons=ip_change — warn

# 관리자 + IP 변경 — 고위험 (세션 폐기 + Remember 폐기)
[2026-09-01 14:22:10][SHJ:SESSION_RISK][IP:103.21.x.x][UA:python-requests/2.28] member_id=1 score=80 reasons=ip_change,admin_access,admin_ip_change — destroy
[2026-09-01 14:22:10][SHJ:SESSION_HIJACK_SUSPECTED][IP:103.21.x.x][UA:python-requests/2.28] member_id=1 reason=RISK_SCORE_80 — full revoke initiated
[2026-09-01 14:22:10][SHJ:REMEMBER_REVOKED][IP:103.21.x.x][UA:python-requests/2.28] member_id=1 all tokens revoked reason=RISK_SCORE_80

# Remember Token Reuse Detection
[2026-09-01 16:45:03][SHJ:REMEMBER_REUSED][IP:58.29.x.x][UA:curl/7.68.0] member_id=88 — prev token reuse detected, full revoke
[2026-09-01 16:45:03][SHJ:REMEMBER_REVOKED][IP:58.29.x.x][UA:curl/7.68.0] member_id=88 all tokens revoked reason=REMEMBER_TOKEN_REUSE

# HMAC 토큰 위조 시도
[2026-09-01 20:11:44][SHJ:SESSION_HIJACK_SUSPECTED][IP:45.33.x.x][UA:Go-http-client/1.1] member_id=12 reason=IDENTITY_TOKEN_MISMATCH — full revoke initiated

# 관리자 재인증
[2026-09-01 10:05:00][SHJ:ADMIN_REAUTH_REQUIRED][IP:121.131.x.x][UA:Mozilla/5.0...] member_id=1 uri=/admin/plugin last_auth=09:50:00
[2026-09-01 10:05:30][SHJ:ADMIN_REAUTH][IP:121.131.x.x][UA:Mozilla/5.0...] member_id=1 reauth success

⚠️ 세션 ID 원문·토큰 원문은 절대 로그에 기록하지 않습니다.
실패 시 세션 ID의 md5 앞 8자리만 참고용으로 기록합니다.



8. 보안 강점 요약 — 어디까지 방어되는가


✅ 완전 방어

공격 유형 방어 방법
HMAC 토큰 위조 Identity Check로 매 요청 재검증 → 위조 즉시 감지 + 폐기
session_restore 후 세션 ID 고정 세션 복구 완료 시 즉시 세션 ID 교체 (G3)
session_extend 후 세션 ID 고정 자동 재로그인 완료 시 즉시 세션 ID 교체 (G3)
Remember Token 재사용 공격 Reuse Detection → 전 세션 + 모든 토큰 즉시 폐기 (G2)
Remember Me 쿠키 SameSite 우회 발급 즉시 SameSite=Lax로 덮어씀 (G4)
탈취 후 Remember Me로 재진입 탈취 감지 시 DB 토큰 + 쿠키 동시 폐기 (G6)
관리자 세션 탈취 후 즉시 중요 작업 10분 재인증 요구로 실행 시간 차단 (G5)
UA 없는 자동화 도구 세션 도용 Risk +30 → 80점 도달 시 즉시 폐기 (G1)


⚠️ 부분 방어 (탐지·로그만)

공격 유형 현재 대응
모바일 IP 변경 Risk +20 → 단독으로 세션 폐기 안 함 (정상 이용자 보호)
브라우저 업데이트로 UA 변경 Risk +10 → 단독으로 세션 폐기 안 함


9. 방어하지 못하는 영역

공격 유형 이유 추가 대책
XSS로 세션 쿠키 탈취 탈취 시점 감지 불가 HttpOnly(코어), CSP 헤더 강화, XSS 플러그인(8번)
HTTPS 없는 환경의 도청 암호화 계층 부재 HTTPS + HSTS 강제
서버 파일 직접 접근 서버 권한 탈취 시 서버 권한 관리, 세션 디렉토리 격리
secret_key 유출 후 HMAC 위조 키 자체가 노출되면 Identity Check 우회 secret_key 안전 보관, 정기 로테이션
동시 세션 수 제한 미구현 추후 확장 가능
지역 기반 이상 탐지 GeoIP 미구현 외부 GeoIP 서비스 연동 시 확장 가능


10. 플러그인 1(dx-security-guard)과의 관계

두 플러그인은 서로 다른 공격을 막으며 함께 사용할 때 시너지가 발생합니다.

상황 플러그인 1 (Fixation·Timeout) 플러그인 2 (Hijacking·이 플러그인)
로그인 세션 ID 재생성 (priority 1) Risk 기준값 초기화 (priority 5)
매 요청 Timeout · session_version 검증 (priority 1) HMAC · Risk 검증 (priority 2)
session_version 불일치 세션 파기 Remember Token까지 연쇄 폐기
Risk 80+ 세션 파기. 관리자이면 Remember Token도 폐기
로그아웃 세션 완전 파기 (priority 1) SameSite=Lax로 Remember 쿠키 삭제 (priority 5)

플러그인 1이 없어도 이 플러그인은 단독으로 동작합니다. 단, session_version 연동 기능(G6의 version++ 부분)은 플러그인 1과 함께 설치할 때 완전히 동작합니다.



11. 다른 플러그인·테마에서 연동하기

관리자 재인증 연동

// 관리자 비밀번호 재입력 처리 코드에서
$inputPassword = $_POST['password'] ?? '';
$adminUser = Auth::getInstance()->user();

if (password_verify($inputPassword, $adminUser['password_hash'])) {
    // 재인증 성공을 Security Hijacking 플러그인에 알림
    dx_run_hook('dx_admin_reauth_success', array(
        'user_id' => $adminUser['id'],
    ));
    // 이후 중요 작업 처리
}


관리자 레이아웃에서 재인증 모달 처리

// 관리자 레이아웃 _head.php 또는 _body_top.php
if (!empty($_SESSION['__shj_admin_reauth_required'])) {
    // 재인증 모달 표시 (예시)
    echo '<div id="reauth-modal">비밀번호를 재입력하세요.</div>';
    echo '<script>document.getElementById("reauth-modal").style.display="flex";</script>';
}


Remember Token Rolling 훅 연동 (코어 수정)

완전한 Reuse Detection을 위해 코어의 Rolling 처리 코드에 훅을 추가합니다.

// Auth::tryRememberMe() 내 Rolling 처리 부분
$oldToken = $storedToken;  // Rolling 전 기존 토큰 보관
$newToken = Secure::randomHex(64);
// ... DB 업데이트 ...
// Rolling 완료 후 훅 실행 (dx-security-hijacking이 수신)
dx_run_hook('dx_after_remember_rotate', array(
    'user_id'   => $userId,
    'old_token' => $oldToken,
));


세션 버전 불일치 훅 연동 (플러그인 1 확장)

플러그인 1에서 session_version 불일치를 감지할 때 훅을 실행하면 이 플러그인이 Remember Token까지 폐기합니다.

// dx-security-guard의 session_version 불일치 처리 부분에서
dx_run_hook('dx_session_version_mismatch', array(
    'user_id' => $userId,
));


12. 값 변경 방법

plugin.php 상단의 상수를 수정합니다.

// 관리자 재인증 유효 시간 (초)
define('DX_SHJ_ADMIN_REAUTH_TTL', 600);   // 기본 10분

// Risk Engine 임계값
define('DX_SHJ_RISK_WARN',    30);  // 경고 (로그만)
define('DX_SHJ_RISK_REAUTH',  60);  // 재인증 요구 플래그
define('DX_SHJ_RISK_DESTROY', 80);  // 세션 즉시 폐기
환경 관리자 재인증 TTL Risk Destroy
일반 커뮤니티 (기본) 600 (10분) 80
금융·쇼핑몰 300 (5분) 70
내부 업무 시스템 900 (15분) 80
고보안 기업 환경 300 (5분) 60


13. 자주 묻는 질문

Q. Session Fixation 플러그인(1번)과 이 플러그인의 차이가 무엇인가요?

Session Fixation은 로그인 전에 공격자가 세션 ID를 심는 공격이고, Session Hijacking은 로그인 후 정상 발급된 세션 ID를 훔치는 공격입니다. 공격 타이밍이 다르기 때문에 방어 방법도 다릅니다. 두 플러그인을 함께 사용해야 완전한 방어가 됩니다.


Q. Remember Token Reuse Detection이 발동하면 정상 사용자도 로그아웃되나요?

네, 맞습니다. 이는 의도된 동작입니다. 구 토큰이 재사용됐다는 것은 토큰이 탈취되었을 가능성이 높다는 신호입니다. 보안을 우선시하여 모든 세션을 폐기합니다. 정상 사용자는 비밀번호로 다시 로그인하면 됩니다. 보안 로그에서 REMEMBER_REUSED를 확인하고 해당 사용자에게 상황을 알릴 수 있습니다.


Q. 관리자 재인증 10분은 너무 짧지 않나요?

10분은 권장값입니다. DX_SHJ_ADMIN_REAUTH_TTL 상수를 수정하면 됩니다. 단, 너무 길게 설정하면 세션 탈취 후 공격자가 중요 작업을 실행할 수 있는 시간이 늘어납니다. 환경에 따라 5분~15분 사이에서 조정하세요.


Q. dx_after_remember_rotate 훅이 코어에 없는데 Reuse Detection이 동작하나요?

부분적으로 동작합니다. 코어에 이 훅이 없으면 remember_prev_token이 기록되지 않아 이전 토큰 해시와 비교할 수 없습니다. 그러나 dx_after_login 훅에서 현재 쿠키 값을 읽어 확인하는 방식은 동작합니다. 완전한 Reuse Detection을 위해 코어의 Rolling 처리 코드에 훅 실행을 추가하는 것을 권장합니다.


Q. 관리자 재인증 모달은 어디서 구현하나요?

이 플러그인은 $_SESSION['__shj_admin_reauth_required'] = true 플래그만 설정합니다. 실제 UI(모달, 폼)는 관리자 테마 레이아웃에서 이 플래그를 읽어 구현해야 합니다. 재인증 완료 후 dx_admin_reauth_success 훅을 실행하면 플러그인이 타이머를 리셋합니다.


Q. 플러그인 1 없이 이 플러그인만 설치해도 되나요?

동작합니다. 단, 다음 기능은 플러그인 1이 있을 때 완전히 동작합니다.

  • session_version 연동 (G6의 version++ 연쇄)
  • dx_session_version_mismatch 훅 수신

플러그인 1의 __sg_verified 마커가 없으면 이 플러그인이 Auth::isLoggedIn()으로 직접 판단합니다.


Q. IIS 환경에서 세션 ID 교체가 실패할 수 있나요?

이 플러그인의 _dx_shj_regenerate()session_write_close() → session_start() → session_regenerate_id(true) 순서로 실행합니다. IIS 파일 잠금 문제를 이 순서로 해결합니다. 실패 시 REGEN_FAIL 로그가 기록됩니다.


DX Security — Session Hijacking 방어 v1.0.0 — DesignOneX
https://designonex.com
 

라이선스

디자인원엑스 라이선스 참고
https://designonex.com/notice/view/1787577858493247

 

첨부파일 1개 회원전용

댓글0

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