logo.jpg 파일을 텍스트 에디터로 열어 보면 첫 줄에ÿØÿà 같은 이상한 글자. image.png 의 첫 byte 는 89 50 4E 47. 이게 magic number — 파일 type 의 자가 식별 서명. OS / 브라우저 / 라이브러리가 확장자 무시하고 이 서명으로 type 판단. 이 가이드는 자주 보는 magic number, 확장자 신뢰의 함정, content-sniffing 의 보안 의미를 정리한다.
왜 magic number 인가
파일 확장자 (.jpg, .pdf) 는 단순 사용자 힌트. 누가 임의로 변경 가능:
mv malware.exe vacation.jpg
→ 확장자는 .jpg 지만 내용은 실행 파일
Windows 가 확장자만 보고 .exe 로 실행하면 → 보안 사고Magic number = 파일 내부의 정직한 서명. 확장자 무시하고 안전하게 type 판단.
자주 보는 magic numbers
| 파일 type | 첫 byte (hex) | ASCII / 의미 |
|---|---|---|
| PNG | 89 50 4E 47 0D 0A 1A 0A | ‰PNG\r\n\x1a\n |
| JPEG | FF D8 FF | (SOI marker) |
| GIF | 47 49 46 38 37 61 / 47 49 46 38 39 61 | GIF87a / GIF89a |
| WebP | 52 49 46 46 ?? ?? ?? ?? 57 45 42 50 | RIFF....WEBP |
| AVIF | ?? ?? ?? ?? 66 74 79 70 + 'avif' at offset 8 | ...ftypavif |
25 50 44 46 2D | %PDF- | |
| ZIP / DOCX / XLSX / APK | 50 4B 03 04 | PK.. |
| RAR | 52 61 72 21 1A 07 | Rar!\x1a\x07 |
| gzip | 1F 8B | |
| MP3 | FF FB / 49 44 33 | (or ID3 tag) |
| MP4 / MOV | ?? ?? ?? ?? 66 74 79 70 at offset 4 | ...ftyp... |
| Windows EXE / DLL | 4D 5A | MZ (Mark Zbikowski) |
| ELF (Linux 실행) | 7F 45 4C 46 | \x7fELF |
| Mach-O (macOS 실행) | FE ED FA CE / FE ED FA CF | |
| Java class | CA FE BA BE | (famous) |
확인 — Hex 인코딩 / 디코딩 가 file 의 hex 출력. 또는 unix file 명령:
$ file mystery.bin
mystery.bin: PNG image data, 1920 x 1080, 8-bit/color RGBA
$ xxd mystery.bin | head -1
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR특이한 magic — 컨테이너 안 컨테이너
ZIP 기반 형식 — DOCX / XLSX / APK / JAR
50 4B 03 04 ... ← ZIP 의 magic
DOCX:
- 확장자는 .docx
- 실제로는 ZIP 안에 XML 묶음
- 압축 해제하면 word/document.xml 등
APK (Android app):
- ZIP + AndroidManifest.xml + classes.dex 등
JAR (Java archive):
- ZIP + META-INF/MANIFEST.MF 등file 명령 또는 unzip -l 로 내부 확인. DOCX 파일 깨졌으면 unzip 으로 풀어 일부 복구도 가능.
RIFF 기반 — WebP / WAV / AVI
RIFF magic: 52 49 46 46 (4 byte) + size (4 byte) + format (4 byte)
WAV: RIFF....WAVE
AVI: RIFF....AVI
WebP: RIFF....WEBPRIFF 가 컨테이너 형식 — 안에 어떤 데이터 (audio / video / image) 가 들었는지 5-8 byte 의 form type 으로 구분.
MIME type — magic number 의 친구
Web 서버 응답의 Content-Type header. 브라우저가 파일 처리 방식 결정:
Content-Type: image/png → <img> 로 렌더링
Content-Type: text/html → HTML 파싱
Content-Type: application/pdf → PDF viewer
Content-Type: application/octet-stream → "다운로드" prompt서버가 잘못된 Content-Type 보내면 브라우저는 일부 "snifing" — magic number 보고 실제 type 추측. 보안 이슈의 원인.
MIME sniffing 의 위험
서버: Content-Type: text/plain
실제 파일: 첫 byte 가 <html>
옛 IE / Chrome: "어, HTML 같네" → text/html 로 처리
→ 사용자가 업로드한 .txt 파일이 HTML 으로 렌더링
→ 사용자 입력 XSS 사고해결 — 서버가 X-Content-Type-Options: nosniff 헤더 박음. 브라우저가 sniffing 비활성, declared Content-Type 만 신뢰. 모던 사이트 default.
File upload 검증 — 확장자 X, magic number 도 함께
사용자가 .jpg 만 업로드 가능한 form:
// Bad — 확장자만 검증
if (!file.name.endsWith(".jpg")) reject();
// attacker: malware.exe → malware.jpg
// Better — magic number 검증
const buffer = await file.slice(0, 4).arrayBuffer();
const bytes = new Uint8Array(buffer);
if (bytes[0] !== 0xFF || bytes[1] !== 0xD8 || bytes[2] !== 0xFF) {
reject("Not a JPEG");
}
// Best — 둘 다 + image library 가 실제 디코드 시도
sharp(file).metadata() // 실패 시 reject그러나 magic 만으로 100% 안전 X — polyglot 파일 (PHP/JPG 혼합) 가능. 보안 중요 환경은 sandbox 안 디코드.
흥미로운 magic numbers 의 역사
PNG 의 정교한 magic
89 50 4E 47 0D 0A 1A 0A
│ └──── "PNG" ────┘
│ │
│ ├─ CR LF (Windows 줄바꿈)
│ ├─ 1A = DOS EOF 표시
│ └─ LF (Unix 줄바꿈)
│
└─ 0x89 = 첫 byte 의 high bit 1 (text 가정 깸)
목적 — 텍스트 변환·전송 시 깨짐 즉시 감지DOS / Unix / Mac 의 줄바꿈 차이로 옛 FTP 가 자주 깨뜨림. PNG 의 magic 이 이걸 즉시 감지하도록 설계.
Java class 의 CAFEBABE
CA FE BA BE. Java 만든 James Gosling 의 team 이 카페 에서 일하던 시절 농담. 의미 X, 그냥 hex 로 단어가 되는 4 byte.
EXE 의 MZ
Mark Zbikowski — Microsoft 의 엔지니어 이름. MS-DOS 1.0 (1983) 부터 박힘. 현대 Windows .exe 도 이 magic 유지 (NT 호환 + MZ + DOS stub + PE header).
File detection 라이브러리
- libmagic (Unix
file명령의 backend) — 수천 개 패턴 DB. 단순 magic 외 정교한 추론. - file-type (Node) — magic number + filename extension 결합. Buffer 또는 stream 인식.
- python-magic — libmagic 의 Python bindings.
Base64 의 magic — image preview 만들기
Data URI:
data:image/png;base64,iVBORw0KGgoAAAA...
│
└─ base64 의 "iVBORw0K..." 가
decode 하면 PNG 의 magic "89 50 4E 47 ..."
시작과 일치
브라우저가 Data URI 받으면:
1. base64 decode
2. 첫 byte 보고 type 추측 (또는 mime 신뢰)
3. img 로 렌더이미지 → Base64 (Data URI) 가 file ↔ data URI 변환. 변환 결과의 첫 부분이 항상 magic number 의 base64 encoding.
흔한 함정
1. UTF-8 BOM 의 magic 충돌
EF BB BF ← UTF-8 BOM
↓
파일 type 감지 라이브러리가 이걸 무시하지 못하면:
"BOM 박힌 CSV" 의 첫 byte = EF → "이 파일 PDF?" (PDF = 25 50 44 46) 다른 magic2. ZIP-based file 의 type 모호함
DOCX / XLSX / APK / JAR 모두 PK.. magic. 정확한 type 알려면 내부 파일 확인. file 명령이 이 작업.
3. polyglot file
Same byte stream 이 두 type 으로 valid:
GIF/JS polyglot:
GIF89a/*...*/=1;script=...
↓
- GIF parser: valid 1×1 GIF
- JS parser: valid JavaScript
→ image upload field 로 JS 업로드, browser 가 <script src> 로 load
→ XSSdefense — Content-Type + nosniff + sandbox.
4. SVG 의 magic 부재
SVG = XML 텍스트, 고정 magic byte 없음 (<?xml 또는 <svg 시작). text 라 binary 검증 패턴 안 통함. SVG 안 <script> 가 XSS 위험.
5. ImageIO 의 type vs magic 불일치
Java / .NET 이미지 라이브러리가 확장자 신뢰하고 type 추측 → 실제 파일이 다른 type → 디코드 실패. magic 검증 후 처리.
참고 자료
- List of file signatures — Wikipedia
- libmagic (file command backend) — Linux man
- PNG specification (magic 설명) — W3C
- MIME sniffing — WHATWG
요약
- Magic number = 파일 내부의 자가 식별 서명. 확장자보다 신뢰.
- PNG (89 50 4E 47), JPEG (FF D8 FF), PDF (%PDF-), ZIP (PK\x03\x04), MZ (EXE), CAFEBABE (Java class) 등.
- ZIP 기반 형식 (DOCX/XLSX/APK/JAR) 은 같은 PK magic. 내부 구조로 구분.
- RIFF 컨테이너 — WebP/WAV/AVI 의 공통 형식.
- MIME sniffing 의 위험 —
X-Content-Type-Options: nosniff로 비활성. - File upload 검증 = 확장자 + magic + sandbox 디코드 3 단.
- polyglot file (GIF/JS) 가능 — magic 만으로 100% 안전 X.
- 실험 — Hex 인코딩 / 디코딩 으로 파일 첫 byte 확인. 이미지 → Base64 (Data URI) 의 data URI 가 magic 의 base64 encoding 으로 시작.