
DX Security — Log Injection 방어 v1.0.0 안내서
PHP 5.6+ · IIS / Apache / Nginx 완전 호환 코어 dx_log() 수정 없음 — 보완 API 제공
목차
1. Log Injection이란 무엇인가
공격자가 사용자 입력값을 로그에 삽입하여 가짜 로그를 생성하거나, 기존 로그를 위조·오염·은폐하는 공격입니다.
공격 시나리오:
① CRLF Injection:
username = admin\r\nLOGIN_SUCCESS
취약한 로그:
LOGIN_FAILURE username=admin
LOGIN_SUCCESS ← 공격자가 삽입
② JSON Injection:
username = "},"result":"SUCCESS
취약한 JSON 로그:
{"event":"LOGIN_FAILURE","user":"},"result":"SUCCESS"}
→ JSON 구조 파괴, result를 SUCCESS로 위조
③ Log Viewer XSS:
username = <script>alert(document.cookie)</script>
→ 로그 → 관리자 화면 → XSS 실행
④ 민감정보 노출:
Exception: password=qwerty123 token=eyJhbGci...
→ 로그 파일에 원문 저장 → 로그 유출 시 계정 탈취
핵심 원칙
사용자 입력은 로그의 데이터일 수 있지만
로그의 구조·이벤트·심각도·시간을 결정할 수 없어야 한다.
위험:
$line = "[" . date() . "][LOGIN_FAILURE] " . $userInput; // CRLF Injection
file_put_contents($log, $line);
안전:
dx_security_log('LOGIN_FAILURE', array('input' => $userInput));
// 자동 정규화 + 구조화 + 마스킹
2. DXCMS 코어 로그 방식 분석
// 코어 dx_log()
function dx_log($message, $level = 'info')
{
// ... 레벨 확인 ...
$line = '[' . date('Y-m-d H:i:s') . '][' . strtoupper($level) . ']'
. ($caller ? '[' . $caller . ']' : '')
. ' ' . $message; // ← $message에 사용자 입력이 들어오면 위험
@file_put_contents(DX_DATA . '/error.log', $line . PHP_EOL, FILE_APPEND | LOCK_EX);
}
11~19번 보안 플러그인의 로그 방식:
// 취약할 수 있는 기존 방식
$line = sprintf("[%s][AUTH:%s][IP:%s]... %s\n",
date(...), $type, $ip, $msg); // $msg에 사용자 입력 포함 가능
file_put_contents(DX_ROOT . '/data/security.log', $line, FILE_APPEND | LOCK_EX);
| 항목 | 코어/기존 방식 | 평가 |
|---|---|---|
| CRLF 제거 | 없음 | ❌ |
| 민감정보 마스킹 | 없음 | ❌ |
| 구조화 로그 | 없음 | ❌ |
| Log Rate Limit | 없음 | ❌ |
| Request ID | 없음 | ❌ |
| Log Integrity | 없음 | ❌ |
| Log Viewer XSS | 없음 | ❌ |
3. 이 플러그인의 역할
코어 dx_log()를 대체하지 않고 보완 API를 제공합니다. 새로 작성하는 플러그인 코드에서 dx_security_log()를 사용하면 자동으로 모든 방어가 적용됩니다.
4. 설치 방법
(DXCMS 루트)/
└── plugins/
└── dx-security-log/
├── manifest.php
└── plugin.php
관리자 → 플러그인 → dx-security-log → 활성화
5. 방어층 상세 설명
LI1. CRLF / 제어문자 / ANSI Escape 제거
제거 대상:
\r\n, \r, \n → 공백으로 치환 (Log Splitting 방어)
\x00 (NULL) → 제거 (Log Truncation 방어)
ANSI Escape → 제거 (터미널 제어 방어)
\x01~\x1f → 제거 (제어문자 방어)
\t (탭) → 공백으로 치환
처리 순서: 모든 사용자 입력에 적용
LI2. 민감정보 자동 마스킹
탐지 후 마스킹 대상:
password=qwerty123 → password=qw********23
token=eyJhbGci... → token=ey******...
secret=abc123 → secret=ab****23
api_key=key_xxx → api_key=ke****xx
authorization=Bearer... → authorization=Be******er
cookie=session_id=... → cookie=se****...
마스킹 형식:
앞 2자리 + **** + 뒤 2자리
LI3. 구조화 로그 (Structured Logging)
기존 방식:
[2026-09-03 09:00:01][AUTH:LOGIN_FAILED]... login_id=admin reason=WRONG_PASSWORD
구조화 방식:
ts=2026-09-03 req=REQ-a1b2c3 event=AUTH:LOGIN_FAILED ip=45.33.x.x uid=0
method=POST uri=/auth/login ua="Mozilla/5.0..." login_id=admin reason=WRONG_PASSWORD
필드 분리:
ts → 서버 시간 (사용자 변경 불가)
req → Request ID (요청당 고유값)
event → 이벤트 타입 (서버에서 결정)
ip → Canonical Client IP (Proxy Trust 적용)
uid → 로그인 사용자 ID
method/uri → 요청 정보
ua → User-Agent (512자 제한)
[추가 필드] → CRLF 제거 + 마스킹 후 저장
LI4. Log Rate Limit
정책:
같은 IP + Event 조합으로 60초에 50회 초과 시 억제
중요 예외:
첫 번째 이벤트: 항상 기록
임계치 초과 첫 번째: 경고 로그 후 억제 시작
→ 최초 이벤트와 초과 경고는 반드시 보존
저장: data/cache/lirate_*.tmp (TTL 자동 만료)
LI5. 필드 길이 제한
| 필드 | 최대 길이 |
|---|---|
| 이벤트 타입 | 64자 |
| 일반 필드 | 255자 |
| 메시지 | 2,048자 |
| User-Agent | 512자 |
| URI | 200자 |
초과 시: ...(truncated) 접미사 추가
LI6. Log Viewer XSS 방어
관리자 로그 조회 시 자동 헤더 추가:
X-Content-Type-Options: nosniff
Cache-Control: no-store, no-cache
로그 출력 시 HTML 이스케이프 API 제공:
dx_log_escape_html($logValue)
→ htmlspecialchars(ENT_QUOTES, UTF-8)
LI7. Log Integrity — HMAC 검증
보안 중요 로그에 HMAC 태그 포함 가능:
dx_security_log('CRITICAL_EVENT', $fields, true)
로그 행 예시:
ts=2026-09-03 ... event=ADMIN_DELETE ... hmac=a1b2c3d4e5f6g7h8
변조 확인:
dx_log_verify_integrity($logLine)
→ true (무결성 확인)
→ false (변조 또는 태그 없음)
HMAC 키: DX_SECRET_KEY 설정값 사용
LI8. Request ID 추적
요청당 고유 ID 자동 생성:
REQ-a1b2c3d4e5f6
같은 요청의 모든 로그에 동일 ID:
REQ-a1b2c3 AUTH:LOGIN_FAILED
REQ-a1b2c3 RATE_LIMIT_EXCEEDED
REQ-a1b2c3 AUTHZ:ACCESS_DENIED
공격자가 로그를 섞어도 요청 흐름 추적 가능
6. 보안 로그 형식
# 구조화 로그 예시
ts="2026-09-03 09:00:01" req=REQ-a1b2c3 event=AUTH:LOGIN_FAILED
ip=45.33.x.x uid=0 method=POST uri=/auth/login
ua="Mozilla/5.0 (Windows NT 10.0)" login_id=admin reason=WRONG_PASSWORD
# HMAC 태그 포함 (integrity=true)
ts="2026-09-03 14:22:10" req=REQ-d4e5f6 event=ADMIN:CONFIG_CHANGED
ip=121.131.x.x uid=1 method=POST uri=/admin/config
key=security.enabled value=false hmac=a1b2c3d4e5f60000
# 민감정보 마스킹
ts="2026-09-03 16:45:03" req=REQ-g7h8i9 event=LOG:ERROR
ip=58.29.x.x uid=0 method=POST uri=/auth/login
message="Login failed password=qw****23 for user admin"
# Log Viewer 접근 감사
ts="2026-09-03 20:11:44" req=REQ-j0k1l2 event=LOG_VIEWER_ACCESS
ip=121.131.x.x uid=1 method=GET uri=/admin/security/log
7. 보안 강점 요약
| 공격 | 결과 | 방어 레이어 |
|---|---|---|
| CRLF Injection | CR/LF → 공백 치환 | LI1 |
| Log Splitting | CRLF 제거 | LI1 |
| ANSI Escape | 제거 | LI1 |
| 민감정보 노출 | 자동 마스킹 | LI2 |
| JSON 구조 파괴 | 구조화 필드 분리 | LI3 |
| Log DoS | 50회/60초 제한 | LI4 |
| 과도한 로그 길이 | 필드별 최대 길이 | LI5 |
| Log Viewer XSS | HTML 이스케이프 API + 헤더 | LI6 |
| 로그 변조 탐지 | HMAC 검증 | LI7 |
| 로그 흐름 추적 | Request ID | LI8 |
| 정상 로그 기록 | 영향 없음 | ✅ |
8. 개발자 API 가이드
구조화 보안 로그 기록
// 기본 사용
dx_security_log('AUTH:LOGIN_FAILED', array(
'login_id' => $loginId, // 자동 CRLF 제거
'reason' => $reason, // 자동 마스킹 적용
));
// HMAC Integrity 포함 (중요 이벤트)
dx_security_log('ADMIN:CONFIG_CHANGED', array(
'key' => $configKey,
'value' => $newValue,
), true); // 세 번째 인자 true = HMAC 태그 포함
로그 필드 수동 정규화
// 사용자 입력을 로그에 포함하기 전 정규화
$safeInput = dx_log_sanitize($userInput, 100); // 100자 제한
$line = "event=CUSTOM field=" . $safeInput;
민감정보 마스킹
$msg = "Login failed password=$userPassword for user $username";
$masked = dx_log_mask($msg); // password 자동 마스킹
Log Viewer HTML 이스케이프
// 관리자 화면에서 로그 출력 시
foreach ($logLines as $line) {
echo '<pre>' . dx_log_escape_html($line) . '</pre>';
// htmlspecialchars 자동 적용 → XSS 방어
}
HMAC Integrity 검증
// 로그 행이 변조되지 않았는지 확인
$logLine = 'ts="2026-09-03" ... event=ADMIN_DELETE ... hmac=a1b2c3d4';
if (dx_log_verify_integrity($logLine)) {
echo '무결성 확인됨';
} else {
echo '변조 또는 태그 없음';
}
Request ID 활용
// 현재 요청의 ID
$reqId = dx_log_request_id(); // REQ-a1b2c3d4e5f6
// 여러 로그에 같은 Request ID 포함
dx_security_log('STEP_1', array('req_ref' => $reqId));
dx_security_log('STEP_2', array('req_ref' => $reqId));
DX Security — Log Injection 방어 v1.0.0 — DesignOneX https://designonex.com
라이선스
디자인원엑스 라이선스 참고
https://designonex.com/notice/view/1787577858493247