
DX Security — Session Hijacking 방어 v1.0.0 안내서
DesignOneX CMS Session Hijacking 2차 방어 플러그인
PHP 5.6+ · IIS / Apache / Nginx · 공유호스팅 완전 호환
코어(Auth.php / Secure.php / index.php) 수정 없음 — 훅으로만 동작
목차
- Session Hijacking이란 무엇인가
- DXCMS 코어의 현재 방어 수준
- 코어가 막지 못하는 것 — 이 플러그인의 존재 이유
- 설치 방법
- 방어층 상세 설명
- 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 연쇄 폐기
- 훅 실행 타이밍 전체 흐름
- 보안 로그 읽는 법
- 보안 강점 요약 — 어디까지 방어되는가
- 방어하지 못하는 영역
- 플러그인 1(dx-security-guard)과의 관계
- 다른 플러그인·테마에서 연동하기
- 값 변경 방법
- 자주 묻는 질문
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_restore나 session_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]);
}
훅이 실행되면:
__shj_admin_auth= 현재 시각 (세션)admin_reauth_at= 현재 시각 (DB)- 재인증 요구 플래그 해제
세션 만료·재인증 타이머 구조
관리자 로그인
↓ (첫 중요 경로 접근 시 재인증 요구)
비밀번호 재입력
↓ 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