본문으로 건너뛰기
yutils

파일이 PNG 인지 어떻게 알까? (magic numbers)

OS 와 브라우저가 파일의 첫 몇 byte 로 type 을 판단하는 방식 — PNG·JPG·PDF·ZIP 서명, 확장자만 믿으면 안 되는 이유, content-sniffing 의 강력함과 위험.

약 8분 읽기

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 / 의미
PNG89 50 4E 47 0D 0A 1A 0A‰PNG\r\n\x1a\n
JPEGFF D8 FF(SOI marker)
GIF47 49 46 38 37 61 / 47 49 46 38 39 61GIF87a / GIF89a
WebP52 49 46 46 ?? ?? ?? ?? 57 45 42 50RIFF....WEBP
AVIF?? ?? ?? ?? 66 74 79 70 + 'avif' at offset 8...ftypavif
PDF25 50 44 46 2D%PDF-
ZIP / DOCX / XLSX / APK50 4B 03 04PK..
RAR52 61 72 21 1A 07Rar!\x1a\x07
gzip1F 8B
MP3FF FB / 49 44 33(or ID3 tag)
MP4 / MOV?? ?? ?? ?? 66 74 79 70 at offset 4...ftyp...
Windows EXE / DLL4D 5AMZ (Mark Zbikowski)
ELF (Linux 실행)7F 45 4C 46\x7fELF
Mach-O (macOS 실행)FE ED FA CE / FE ED FA CF
Java classCA 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....WEBP

RIFF 가 컨테이너 형식 — 안에 어떤 데이터 (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) 다른 magic

2. 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
→ XSS

defense — 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 으로 시작.
가이드 목록으로