
DX Security — XSS 방어 v1.0.0 안내서
DesignOneX CMS XSS 기업 납품 수준 2차 방어 플러그인
PHP 5.6+ · IIS / Apache / Nginx · 공유호스팅 완전 호환
코어(DxSanitizer.php / Secure.php) 수정 없음 — 훅으로만 동작
OWASP XSS Prevention Cheat Sheet 준수
목차
- XSS란 무엇인가
- DXCMS 코어의 현재 XSS 방어 수준
- 코어가 막지 못하는 것 — 이 플러그인의 역할
- 설치 방법
- 방어층 상세 설명
- XS1. CSP Nonce 기반 강화
- XS2. DxSanitizer 우회 패턴 보완
- XS3. JSON 출력 XSS 방어
- XS4. URL 출력 이스케이프 강화
- XS5. 관리자 HTML 콘텐츠 추가 검증
- XS6. WAF XSS 패턴 강화
- XS7. 출력 컨텍스트별 이스케이프 표준 API
- XS8. 보안 감사 로그
- 훅 실행 타이밍
- 보안 로그 읽는 법
- 보안 강점 요약
- 방어하지 못하는 영역
- 테마 개발자 — CSP Nonce 적용
- 개발자 API 가이드
- 자주 묻는 질문
1. XSS란 무엇인가
XSS(Cross-Site Scripting)는 공격자가 웹 페이지에 악성 JavaScript를 삽입하여 다른 사용자의 브라우저에서 실행시키는 공격입니다.
공격 유형
① Stored XSS (저장형):
공격자가 게시판에 <script>...</script> 삽입
→ 서버에 저장
→ 다른 사용자가 페이지 조회 시 스크립트 실행
→ 세션 탈취, 피싱, 악성코드 배포
② Reflected XSS (반사형):
?q=<script>...</script> URL 공유
→ 피해자가 링크 클릭
→ 서버가 검색어를 그대로 응답에 포함
→ 즉시 실행
③ DOM XSS:
JavaScript에서 document.write(location.hash) 등
서버를 거치지 않고 DOM을 직접 조작
→ 서버 측 필터링을 완전히 우회
XSS로 할 수 있는 것
document.cookie → 세션 쿠키 탈취 (HttpOnly 없으면)
fetch('/api/...', {credentials: 'include'}) → CSRF 공격
localStorage, sessionStorage → 저장 데이터 탈취
document.forms[0] → 입력 폼 데이터 도용
window.location → 피싱 페이지로 리다이렉트
키로거 구현 → 모든 키 입력 수집
XSS와 CSP의 관계
CSP 없음: 삽입된 스크립트 즉시 실행
CSP with 'unsafe-inline': 삽입된 인라인 스크립트 여전히 실행
CSP with nonce: 서버 발급 nonce가 없는 스크립트는 실행 불가
→ 공격자가 nonce를 예측할 수 없으므로 효과적 차단
2. DXCMS 코어의 현재 XSS 방어 수준
코어 DxSanitizer.php와 Secure.php를 직접 분석한 결과입니다.
DxSanitizer::editorContent() — 매우 우수
저장 전 HTML 정화 파이프라인:
1. 차단 태그 완전 제거 (script, iframe, object, embed, form, svg 등)
2. 이벤트 핸들러 제거 (onclick, onerror, onload 등 모든 on* 속성)
3. blocked:/blocked: 프로토콜 차단
4. data: URI img src 차단
5. PHP 태그 제거
6. CSS 제거
7. Allowlist 태그만 유지 (strip_tags)
8. 각 태그의 속성 Allowlist 검사
9. href/src blocked: 차단
10. target="_blank" → rel="noopener noreferrer" 자동 추가
11. style 속성 CSS 속성 Allowlist + 위험 값 제거
보안 헤더 — 양호
X-Frame-Options: SAMEORIGIN Clickjacking 방어
X-Content-Type-Options: nosniff MIME Sniffing 방어
X-XSS-Protection: 1; mode=block 구버전 브라우저 XSS 필터
Referrer-Policy 정보 유출 최소화
Permissions-Policy 카메라/마이크/위치 차단
HSTS (HTTPS 환경) 프로토콜 다운그레이드 방어
CSP 헤더 (기본 구조) 콘텐츠 출처 제한
XSS 방어 자체 평가: 8.5/10 — 우수
3. 코어가 막지 못하는 것 — 이 플러그인의 역할
❌ 미비점 1: CSP의 'unsafe-inline' — 가장 중요
코어 CSP:
script-src 'self' 'unsafe-inline' https: blob:
'unsafe-inline'이 있으면 공격자가 삽입한 인라인 스크립트도 실행됩니다.
DxSanitizer가 필터링에 실패하거나
우회 기법으로 <script> 태그가 삽입되면
'unsafe-inline'으로 인해 그대로 실행됩니다.
nonce 방식:
서버가 매 요청마다 랜덤 nonce 발급
<script nonce="a3f29c..."> ← 이 스크립트만 실행
삽입된 <script> ← nonce 없음 → 실행 차단
코어에 getCspNonce() 메서드가 있지만 발급된 nonce가 CSP 헤더에 포함되지 않습니다.
❌ 미비점 2: DxSanitizer 우회 패턴
srcset 속성:
<img srcset="blocked:alert(1)"> → 일부 브라우저 실행
HTML Entity 우회:
javascript: → html_entity_decode 후 blocked:
CSS -moz-binding (구버전 Firefox):
style="-moz-binding:url(evil.xml#xss)"
공백 우회:
j a v a s c r i p t : → 공백 제거 후 blocked:
Mutation XSS:
파싱 중 DOM이 변형되어 발생하는 XSS
❌ 미비점 3: JSON 출력 XSS
// 코어의 일반 json_encode()
echo json_encode($data);
// {"name":"<script>alert(1)</script>"}
// HTML 컨텍스트에서 <script>가 그대로 출력됨
4. 설치 방법
(DXCMS 루트)/
└── plugins/
└── dx-security-xss/
├── manifest.php
└── plugin.php
관리자 → 플러그인 → dx-security-xss → 활성화
5. 방어층 상세 설명
XS1. CSP Nonce 기반 강화
nonce CSP의 동작 원리
1. 서버가 요청마다 랜덤 nonce 생성
nonce = base64(random_bytes(16))
예: "a3f29c11d7e8b4c2..."
2. CSP 헤더에 nonce 포함
script-src 'nonce-a3f29c11d7e8b4c2...'
3. 정상 스크립트 태그
<script nonce="a3f29c11d7e8b4c2...">
// 실행됨 ✅
</script>
4. 공격자가 삽입한 스크립트
<script>alert(document.cookie)</script>
// nonce 없음 → 실행 차단 ✅
호환 모드 vs 엄격 모드
| 모드 | CSP 설정 | 효과 | 요구사항 |
|---|---|---|---|
| 호환 (기본) | nonce + unsafe-inline | nonce 지원 시작, 기존 스크립트 유지 | 없음 |
| 엄격 | nonce만 | 인라인 스크립트 완전 차단 | 테마의 모든 <script>에 nonce 추가 |
엄격 모드 활성화 (plugin.php에서):
define('DX_XSS_STRICT_CSP', true);
추가된 CSP 지시어
코어에 없는 지시어를 추가합니다:
base-uri 'self' <base href="..."> XSS 방어
form-action 'self' 폼 action 외부 제출 차단
frame-ancestors 'self' Clickjacking 이중 방어
XS2. DxSanitizer 우회 패턴 보완
코어 DxSanitizer::clean() 처리 후 추가로 적용됩니다.
보완 항목 6가지
① srcset 속성 제거
<img srcset="blocked:alert(1)"> → 제거
② HTML Entity 우회 탐지
javascript: → decode → blocked: → 탐지 + 엔티티 제거
③ CSS 우회 패턴
-moz-binding: url(evil.xml#xss); → blocked:
behavior: url(evil.htc); → blocked:
④ XML Namespace 속성
<a xml:base="blocked:"> → data-blocked=
<use xlink:href="evil.svg#xss"> → data-blocked=
⑤ 신규 이벤트 핸들러 우회
\x00onclick= (Null byte 우회) → data-blocked=
⑥ 공백 우회 정규화
j a v a s c r i p t : → 공백 제거 → 탐지 → blocked:
XS3. JSON 출력 XSS 방어
dx_json_encode_safe() 함수
// 기본 json_encode() — 위험
echo json_encode(['html' => '<script>alert(1)</script>']);
// {"html":"<script>alert(1)</script>"} → XSS 가능
// dx_json_encode_safe() — 안전
echo dx_json_encode_safe(['html' => '<script>alert(1)</script>']);
// {"html":"\u003Cscript\u003Ealert(1)\u003C\/script\u003E"}
// → 브라우저가 HTML로 파싱하지 않음
| 플래그 | 변환 |
|---|---|
JSON_HEX_TAG |
< → \u003C, > → \u003E |
JSON_HEX_AMP |
& → \u0026 |
JSON_HEX_APOS |
' → \u0027 |
JSON_HEX_QUOT |
" → \u0022 |
XS4. URL 출력 이스케이프 강화
dx_esc_url() — 코어 Secure::safeUrl() 대비 강화
추가 탐지:
① Unicode 공백/제어문자 제거
② HTML Entity 디코딩 후 재검사
③ 로그 기록
탐지 우회 방지:
j\u0000avascript: → 제어문자 제거 → blocked: 탐지
javascript: → decode → blocked: 탐지
XS5. 관리자 HTML 콘텐츠 추가 검증
관리자가 HTML을 직접 입력하는 영역(메뉴, 배너, 위젯)에서 추가 검증합니다.
탐지 및 제거:
① <script> 태그
② 이벤트 핸들러 (onclick, onerror 등)
③ blocked:/blocked: 프로토콜
관리자라도 스크립트 삽입은 허용하지 않습니다.
XS6. WAF XSS 패턴 강화
코어 WAF 3가지 패턴에 12가지 추가 패턴을 보강합니다.
| 추가 패턴 | 탐지 대상 |
|---|---|
document.cookie |
쿠키 탈취 시도 |
window.location |
리다이렉션 시도 |
.innerHTML = |
DOM XSS |
eval() |
동적 코드 실행 |
<svg> |
SVG 기반 XSS |
onfocus, onblur |
HTML5 이벤트 |
|
CSS XSS |
data:text/html |
Data URI XSS |
| template literal + script | 템플릿 리터럴 XSS |
WAF는 탐지·로그 목적입니다. 에디터 본문 필드는 DxSanitizer가 처리하므로 제외합니다.
XS7. 출력 컨텍스트별 이스케이프 표준 API
XSS 방어의 핵심은 출력 컨텍스트에 맞는 이스케이프입니다.
| 컨텍스트 | 함수 | 사용 위치 |
|---|---|---|
| HTML | dx_esc_html($str) |
<div><?= dx_esc_html($input) ?></div> |
| HTML 속성 | dx_esc_attr($str) |
<input value="<?= dx_esc_attr($input) ?>"> |
| JavaScript | dx_esc_js($str) |
var x = "<?= dx_esc_js($input) ?>" |
| CSS | dx_esc_css($str) |
style="color: <?= dx_esc_css($color) ?>" |
| URL | dx_esc_url($url) |
<a href="<?= dx_esc_url($url) ?>"> |
| JSON | dx_json_encode_safe($data) |
<script>var d = <?= dx_json_encode_safe($data) ?></script> |
왜 컨텍스트별로 다른 함수가 필요한가
HTML 컨텍스트: < → < 는 완전한 방어
JS 컨텍스트: < → < 는 불완전 (</script> 우회 가능)
< → \u003C 가 올바른 방어
URL 컨텍스트: blocked: 를 별도로 차단해야 함
CSS 컨텍스트: expression() 를 별도로 차단해야 함
6. 훅 실행 타이밍
[모든 요청]
↓
DxExtend::runTop()
└── dx_extend_top (priority 20): [XS1] CSP Nonce 헤더 교체
└── dx_extend_top (priority 16): [XS6] WAF XSS 패턴 강화
[게시물 저장]
boards/handler.php
└── DxSanitizer::editorContent() (코어)
└── dx_after_sanitize_content (priority 1): [XS2] 추가 정화
└── dx_before_post_save (priority 1): [XS2] content 추가 정화
[관리자 HTML 저장]
└── dx_before_admin_html_save (priority 1): [XS5] 관리자 검증
[JSON API 응답]
└── dx_before_json_response (priority 1): [XS3] XSS 패턴 감지
7. 보안 로그 읽는 법
이벤트 타입
| 타입 | 의미 |
|---|---|
XSS:EXTRA_SANITIZE_APPLIED |
DxSanitizer 이후 추가 정화 적용 |
XSS:CONTENT_EXTRA_CLEANED |
게시물 저장 전 추가 정화 |
XSS:ENTITY_ENCODING_BYPASS |
HTML Entity 우회 탐지 |
XSS:WHITESPACE_BYPASS |
공백 우회 blocked: 탐지 |
XSS:URL_INJECTION_BLOCKED |
위험 URL 프로토콜 차단 |
XSS:JSON_XSS_PATTERN |
JSON 응답에서 XSS 패턴 감지 |
XSS:ADMIN_HTML_SCRIPT_TAG |
관리자 영역 script 태그 감지 |
XSS:ADMIN_HTML_EVENT_HANDLER |
관리자 영역 이벤트 핸들러 감지 |
XSS:ADMIN_HTML_DANGEROUS_PROTOCOL |
관리자 영역 위험 프로토콜 감지 |
XSS:WAF_XSS_PATTERN |
WAF 추가 XSS 패턴 탐지 |
로그 예시
# HTML Entity 우회 탐지
[2026-09-01 09:00:01][XSS:ENTITY_ENCODING_BYPASS][IP:45.33.x.x][...][UID:5][URI:/board/free/write]
html_entity_decode attack detected
# 관리자 영역 script 태그
[2026-09-01 14:22:10][XSS:ADMIN_HTML_SCRIPT_TAG][IP:121.131.x.x][...][UID:1][URI:/admin/menu]
script tag in admin HTML area
# WAF DOM XSS 패턴
[2026-09-01 16:45:03][XSS:WAF_XSS_PATTERN][IP:58.29.x.x][...][UID:0][URI:/api/comment]
source=POST key=content pattern=/document\s*\.\s*(cookie|...
8. 보안 강점 요약
XSS 공격 테스트 결과
| 공격 | 결과 | 방어 레이어 |
|---|---|---|
<script>alert(1)</script> |
✅ 방어 | DxSanitizer (코어) |
<img> |
✅ 방어 | DxSanitizer (코어) |
<svg><script>alert(1)</script></svg> |
✅ 방어 | DxSanitizer + XS2 |
javascript:alert(1) |
✅ 방어 | XS2 Entity 우회 탐지 |
j a v a s c r i p t : |
✅ 방어 | XS2 공백 우회 탐지 |
<img srcset="blocked:..."> |
✅ 방어 | XS2 srcset 제거 |
JSON의 <script> |
✅ 방어 | XS3 json_encode_safe |
blocked: URL |
✅ 방어 | XS4 URL Escape |
| DOM XSS | ✅ 탐지 | XS6 WAF 탐지 |
| 관리자 영역 script | ✅ 방어 | XS5 |
| 인라인 XSS (CSP 없는 경우) | ✅ 방어 | XS1 CSP nonce |
9. 방어하지 못하는 영역
| 취약점 | 이유 | 추가 대책 |
|---|---|---|
| DOM XSS (서버 미통과) | PHP 서버 측 필터링 불가 | DOMPurify 클라이언트 라이브러리 |
| CSP 엄격 모드 적용 전 기존 스크립트 | 테마 수정 필요 | 테마의 모든 <script>에 nonce 추가 |
| 저장된 XSS가 이미 DB에 있는 경우 | 기존 데이터 소급 적용 불가 | DB 전체 재정화 필요 |
| HttpOnly 없는 쿠키 | 다른 플러그인/설정 | 코어 Secure.php 설정 확인 |
10. 테마 개발자 — CSP Nonce 적용
엄격 모드 활성화 후 테마 수정 방법
plugin.php에서:
define('DX_XSS_STRICT_CSP', true);
테마의 모든 인라인 <script> 태그에 nonce 추가:
<!-- 수정 전 -->
<script>
var config = <?= json_encode($config) ?>;
</script>
<!-- 수정 후 -->
<script nonce="<?= dx_csp_nonce() ?>">
var config = <?= dx_json_encode_safe($config) ?>;
</script>
인라인 <style> 태그도 동일:
<style nonce="<?= dx_csp_nonce() ?>">
.example { color: red; }
</style>
11. 개발자 API 가이드
출력 이스케이프
// HTML 출력
echo '<div>' . dx_esc_html($userInput) . '</div>';
// HTML 속성
echo '<input value="' . dx_esc_attr($userInput) . '">';
// JavaScript 변수에 PHP 값 주입
echo '<script nonce="' . dx_csp_nonce() . '">';
echo 'var name = "' . dx_esc_js($userName) . '";';
echo '</script>';
// URL
echo '<a href="' . dx_esc_url($url) . '">링크</a>';
// JSON 출력 (script 태그 안)
echo '<script nonce="' . dx_csp_nonce() . '">';
echo 'var data = ' . dx_json_encode_safe($data) . ';';
echo '</script>';
에디터 콘텐츠 수동 정화
// 기존 코어 방식
$clean = DxSanitizer::editorContent($rawHtml);
// 이 플러그인 추가 정화 포함
$clean = DxSanitizer::editorContent($rawHtml);
$args = dx_run_hook('dx_after_sanitize_content', array('content' => $clean));
$clean = isset($args['content']) ? $args['content'] : $clean;
12. 자주 묻는 질문
Q. CSP를 활성화하면 외부 스크립트(jQuery CDN)가 차단되나요?
아니요. CSP에 https: 가 포함되어 있어 외부 HTTPS 스크립트는 허용됩니다. <script src="https://code.jquery.com/jquery.min.js"> 는 정상 동작합니다.
Q. 엄격 모드를 사용하려면 테마를 전부 수정해야 하나요?
네. 테마의 인라인 <script> 태그 모두에 nonce="<?= dx_csp_nonce() ?>" 를 추가해야 합니다. 수정하지 않으면 해당 스크립트가 차단됩니다. 먼저 호환 모드(기본)로 운영하면서 테마를 수정한 후 엄격 모드로 전환하세요.
Q. DxSanitizer가 이미 잘 되어 있는데 추가 정화가 필요한가요?
DxSanitizer는 매우 잘 설계되어 있습니다. 이 플러그인은 알려진 우회 기법(HTML Entity, 공백 우회, srcset, Mutation XSS 등)을 추가로 막습니다. 보안은 레이어가 많을수록 좋습니다.
DX Security — XSS 방어 v1.0.0 — DesignOneX
https://designonex.com
라이선스
디자인원엑스 라이선스 참고
https://designonex.com/notice/view/1787577858493247