[시니어의 시선] 파일 확장자의 거짓말에 속지 않는 법: 구글 Magika가 libmagic의 시대를 끝낼 수 있을까?
솔직히 말씀드리죠. 10년 가까이 백엔드 시스템을 설계하면서, 사용자로부터 파일을 업로드받는 구간은 언제나 제게 ‘지뢰밭’이었습니다.
프론트엔드에서 accept=".jpg, .png"로 막아둔다 한들, 악의적인 사용자는 버프스위트(Burp Suite) 한 번 켜서 확장자만 쓱 바꿔치기해 서버로 던집니다. 백엔드에서 Content-Type 헤더를 믿는 건 주니어 시절에나 하는 순진한 실수죠. 그래서 우리는 수십 년간 시스템 유틸리티인 file 명령어의 근간, 즉 libmagic에 의존해 왔습니다. 파일의 헤더에 있는 매직 넘버(Magic Number)를 까보고 이 파일의 진짜 정체를 판별하는 방식 말입니다.
하지만 이 방식, 정말 안전하고 완벽했을까요? 폴리글랏(Polyglot) 파일 공격, 즉 유효한 이미지 파일이면서 동시에 실행 가능한 악성 스크립트인 기형적인 파일을 마주했을 때 libmagic은 무력하게 무너지는 경우가 허다했습니다. 게다가 대규모 데이터 파이프라인에서 수천만 개의 파일을 스캔할 때 발생하는 병목 현상은 또 다른 골칫거리였죠.
이런 답답한 상황 속에서 구글이 던진 새로운 패가 바로 Magika(매지카)입니다. 처음 이 도구를 접했을 때 속으로 ‘또 무거운 AI 모델 하나 내놨나 보네’라고 생각하며 꽤 회의적이었습니다. 하지만 아키텍처를 뜯어보고 벤치마크를 돌려본 순간, 생각은 완전히 달라졌습니다.
TL;DR (The Core)
Magika는 무거운 휴리스틱(Heuristic) 룰셋을 버리고, 단 1MB 크기의 초경량 딥러닝 모델을 통해 단일 CPU 코어에서 5ms 이내에 99% 이상의 정확도로 파일 타입을 추론해 내는 패러다임 체인저입니다.
Deep Dive: Under the Hood (핵심 아키텍처 심층 분석)
기존 libmagic과 Magika는 파일을 바라보는 관점 자체가 다릅니다. 기능 나열은 접어두고 내부 원리를 파헤쳐 보겠습니다.
libmagic은 방대한 규칙(Rule)의 집합입니다. 특정 오프셋에 특정 바이트 시그니처가 있는지를 순차적으로 검사하죠. 파일 포맷이 복잡해질수록 규칙도 기하급수적으로 복잡해지며, 텍스트 파일의 경우 인코딩을 판별하기 위해 전체를 스캔하기도 합니다. 당연히 속도가 들쭉날쭉할 수밖에 없습니다.
반면 Magika는 고도로 최적화된 커스텀 Keras 딥러닝 모델을 사용합니다. 놀라운 점은 파일 전체를 읽어 들이지 않는다는 것입니다. Magika의 동작 원리는 다음과 같습니다.
- 고정 크기 청크 샘플링: 파일의 시작(Head), 중간(Middle), 끝(Tail)에서 각각 512바이트씩만 추출합니다.
- 1536 바이트 어레이 생성: 추출한 데이터를 합쳐 정확히 1536바이트의 1차원 배열로 만듭니다. (파일이 작으면 패딩을 추가합니다.)
- 신경망 추론 (ONNX): 이 고정된 바이트 배열을 사전에 학습된 밀집 신경망(Dense Neural Network)에 통과시킵니다.
- 확률 분포 반환: 최종적으로 이 파일이 어떤 타입일 확률이 높은지 Softmax 배열을 반환합니다.
입력 크기가 언제나 1536바이트로 고정되어 있기 때문에, 행렬 곱셈 연산이 극단적으로 빠릅니다. 백엔드에서 무거운 GPU를 할당할 필요도 없이, 평범한 CPU 하나로 밀리초 단위의 추론이 가능한 이유가 바로 이 아키텍처 덕분입니다.
📊 libmagic vs Magika 비교 분석
| 비교 항목 | 전통적인 libmagic | 구글 Magika |
|---|---|---|
| 판별 방식 | 휴리스틱 룰 및 시그니처 (매직 넘버) 매칭 | Keras 기반 딥러닝 (ONNX 런타임) |
| 검사 속도 | 파일 크기 및 포맷 복잡도에 따라 가변적 (때로 매우 느림) | 파일 크기 무관하게 일정함 (CPU에서 약 1~5ms) |
| 정확도 | 폴리글랏 파일 및 모호한 텍스트 파일에서 오탐률 존재 | 100만 개 벤치마크 기준 99% 이상의 압도적 정확도 |
| 처리 데이터량 | 포맷에 따라 파일 전체를 스캔해야 할 수도 있음 | 파일의 Head, Mid, Tail 딱 1536 Bytes만 스캔 |
| 의존성 | libmagic C 라이브러리 및 룰셋 파일 | ONNX 런타임 및 1MB 크기의 모델 파일 |
이론은 이쯤 해두고, 실제 코드 레벨에서 어떻게 돌아가는지 살펴봅시다. 아래는 제가 실무에서 FastAPI 기반의 파일 업로드 마이크로서비스에 Magika를 적용한다고 가정하고 작성한 비동기 스니펫입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import asyncio
from fastapi import FastAPI, UploadFile, HTTPException
from magika import Magika
app = FastAPI()
magika_instance = Magika()
# 허용할 파일 타입의 Magika Content-Type 라벨 목록
ALLOWED_FILE_TYPES = {"jpeg", "png", "pdf"}
@app.post("/upload/")
async def upload_file(file: UploadFile):
# 1. 파일의 Head, Middle, Tail을 커버할 수 있도록 충분한 버퍼만 읽습니다.
# 전체 파일을 메모리에 올리지 않아 OOM(Out of Memory)을 방지합니다.
content_bytes = await file.read(8192)
# 2. Magika를 이용한 초고속 추론
# 내부적으로 1536바이트만 샘플링하여 ONNX 모델로 추론을 수행합니다.
result = magika_instance.identify_bytes(content_bytes)
predicted_type = result.output.ct_label
confidence = result.output.score
# 3. 확률(Confidence) 기반의 방어 로직
# 딥러닝 모델이므로 '확률'을 반환합니다. 임계치(Threshold) 미만이면 거부합니다.
if confidence < 0.85:
raise HTTPException(status_code=400, detail="파일 형식이 모호하거나 변조가 의심됩니다.")
if predicted_type not in ALLOWED_FILE_TYPES:
raise HTTPException(status_code=415, detail=f"지원하지 않는 파일 형식입니다: {predicted_type}")
# 유효성 검증 완료. S3 등으로 스트리밍 업로드 진행...
return {"filename": file.filename, "verified_type": predicted_type}
단순히 identify_bytes() 메서드 하나 호출했을 뿐인데, 내부적으로는 딥러닝 모델이 폴리글랏 파일의 미세한 바이트 패턴까지 분석해 가장 확률이 높은 타입을 뱉어냅니다. 실무자 입장에서 이보다 간편할 수는 없죠.
Pragmatic Use Cases (실무 적용 시나리오)
‘그래서 이걸 내 프로젝트에 어떻게 쓰는데?’라고 물으신다면, 다음과 같은 실무 시나리오를 제시하고 싶습니다.
1. 대규모 데이터 레이크(Data Lake) 인제스천 최적화 과거 웹 크롤링이나 외부 연동을 통해 하루 수백만 개의 정제되지 않은 파일을 S3에 적재하는 파이프라인을 구축한 적이 있습니다. 이때 각 파일의 확장자가 유실되거나 엉망인 경우가 많았죠. 이 파일들을 스파크(Spark)나 하둡(Hadoop) 에코시스템에서 파싱하려면 정확한 타입을 알아야 하는데, libmagic을 거치면 CPU 사용률이 폭주하고 병목이 생겼습니다. Magika는 단일 코어에서도 초당 수천 개의 파일을 판별할 수 있습니다. 람다(Lambda) 함수나 워커 노드의 앞단에 배치하여 라우팅하는 역할로 쓰기에 완벽합니다.
2. WAF 및 사내 보안 게이트웨이 고도화 앞서 언급한 폴리글랏(Polyglot) 공격 방어입니다. 악성 해커들은 이미지 파일 속에 PHP 코드나 악성 매크로를 교묘하게 숨깁니다. libmagic은 헤더의 매직 넘버만 보고 “이건 이미지네!” 하고 통과시키는 경우가 많습니다. 하지만 Magika는 수백만 개의 파일 데이터셋으로 학습되었기에, 파일 구조 전반(Head, Mid, Tail)에 걸친 이상 패턴을 감지하고 분류해 냅니다. 보안 장비의 1차 필터링 엔진으로 도입하면 시스템의 방어력을 비약적으로 끌어올릴 수 있습니다.
Honest Review & Trade-offs (진짜 장단점과 한계)
이쯤 되면 Magika가 만능 은탄환(Silver Bullet)처럼 보이겠지만, 산전수전 다 겪은 엔지니어로서 냉정하게 짚고 넘어가야 할 트레이드오프가 존재합니다.
첫째, 무시할 수 없는 ONNX 런타임 의존성 모델 자체는 1MB로 매우 작습니다. 구글이 이걸 엄청나게 자랑하죠. 하지만 함정이 있습니다. 파이썬 환경에서 이 모델을 돌리려면 onnxruntime 패키지가 필수적입니다. 이 패키지의 용량은 플랫폼에 따라 수십 MB에 달합니다. AWS Lambda 같이 배포 패키지 용량에 민감한 서버리스 환경이나 초소형 엣지 디바이스에서는 기존 C 라이브러리인 libmagic보다 오히려 배포 사이즈가 커지는 배보다 배꼽이 더 큰 상황이 발생할 수 있습니다.
둘째, ‘결정론(Deterministic)’에서 ‘확률론(Probabilistic)’으로의 전환 기존 룰 기반 시스템은 틀리더라도 명확한 이유(룰 매칭)가 있습니다. 하지만 Magika는 AI 모델입니다. 추론 결과가 0.51의 확률로 특정 포맷이라고 나왔을 때, 이 결과를 신뢰할 것인지 거부할 것인지에 대한 임계치(Threshold) 설정의 책임이 온전히 개발자에게 넘어옵니다. 모델이 왜 그런 판단을 했는지 설명 불가능(Black-box)하다는 점은 엔터프라이즈 환경에서 보안 감사를 받을 때 꽤 골치 아픈 문제일 수 있습니다.
셋째, 커스텀 파일 포맷 확장성의 부재 우리 회사에서만 쓰는 독자적인 파일 포맷(.mycorp_data)이 있다고 가정해 봅시다. 기존 libmagic은 텍스트로 된 룰셋 파일에 몇 줄만 추가하면 쉽게 커스텀이 가능했습니다. 하지만 Magika는 사전에 학습된 모델입니다. 현재로서는 일반 사용자가 자신의 독자적인 파일 포맷을 모델에 추가 학습(Fine-tuning)시킬 수 있는 공식적이고 쉬운 파이프라인이 제공되지 않습니다.
Closing Thoughts
이러한 한계점들에도 불구하고, 구글 Magika의 등장은 매우 상징적인 사건입니다. 수십 년간 굳건했던 OS 레벨의 저수준 유틸리티 영역조차 이제 ‘휴리스틱 스크립트’에서 ‘경량 AI 모델’로 패러다임이 넘어가고 있음을 시사하기 때문입니다.
현업 실무자로서 우리의 스탠스는 명확합니다. 내일 당장 시스템의 모든 libmagic을 Magika로 뜯어고칠 필요는 없습니다. 하지만 대규모 트래픽이 몰리거나 고도의 보안 검증이 필요한 경계(Boundary) 지점에서는 Magika를 우선 검증 로직으로 도입하고, 실패하거나 확률이 낮을 때 기존 libmagic을 폴백(Fallback)으로 사용하는 하이브리드 전략을 취한다면, 그 어떤 백엔드 시스템보다 견고하고 우아한 아키텍처를 완성할 수 있을 것입니다.
파일 확장자의 거짓말에 더 이상 속고 싶지 않다면, 이제 여러분의 프로젝트에도 ‘마법(Magika)’을 도입해 볼 때입니다.
References
- https://github.com/google/magika
- https://opensource.googleblog.com/2024/02/magika-ai-powered-fast-and-efficient-file-type-identification.html
- https://pypi.org/project/magika/
