T09 · Insecure Skill Coding Practices
- Location
extract_comments.py:32- Finding
Unbounded Extraction of an Untrusted DOCX Archive
- Content
View full analysis
Vulnerability Details
File Location:
extract_comments.py:32-33
Vulnerability Type: Unrestricted archive extraction
Risk Level: MediumVulnerable Code
python with zipfile.ZipFile(docx_path, 'r') as zf: zf.extractall(output_dir)Technical Analysis
The skill treats a user-supplied DOCX file as a ZIP archive and extracts every member without enforcing limits on:
- The number of archive members
- The compressed or uncompressed size of each member
- The total expanded size
- Compression ratios
- Extraction duration
- The set of files required by the application
The code validates only that the supplied file is recognized as a ZIP archive. A malicious document can therefore contain a ZIP bomb or a large number of entries. Calling
extractall()on such an archive can consume substantial disk space, CPU time, and I/O resources.Although modern supported Python versions perform path sanitization in
ZipFile.extractall(), the implementation should still explicitly validate member paths and reject symbolic-link-like or otherwise unexpected entries as a defense-in-depth measure.Attack Path
- An attacker creates a DOCX-compatible ZIP archive containing highly compressed data, oversized members, or an excessive number of entries.
- The attacker provides the crafted document to a user or workflow that invokes this skill.
zipfile.is_zipfile()accepts the file because it has a valid ZIP structure.zf.extractall(output_dir)expands every archive member into the temporary directory.- Extraction consumes local disk space, CPU time, and I/O capacity until the process fails or the host becomes resource constrained.
- The temporary directory is removed in a
finallyblock when possible, but this does not prevent resource exhaustion during extraction and may not recover space promptly if the process or host is forcibly terminated.
Impact Assessment
Successful exploitation can cause a local denial of service in ...[truncated 339 chars]
- Remediation
View remediation
Remediation Suggestions
Avoid extracting the entire archive. Read only the two required members,
word/comments.xmlandword/document.xml, and enforce explicit limits before and during processing.Recommended controls include:
- Inspect every relevant
ZipInfoobject before reading or extracting it. - Reject archives exceeding an approved member-count limit.
- Enforce maximum compressed and uncompressed sizes per member.
- Enforce a maximum cumulative uncompressed size.
- Reject suspicious compression ratios and encrypted entries.
- Normalize and validate member paths to ensure they remain inside the intended temporary directory.
- Reject unexpected link-like entries and non-regular files.
- Parse XML incrementally or enforce XML input-size limits.
- Apply execution time, disk quota, and memory limits at the process or sandbox level.
A safer design is:
python required_members = { "word/comments.xml", "word/document.xml", } with zipfile.ZipFile(docx_path, "r") as zf: infos = {info.filename: info for info in zf.infolist()} for name in required_members: info = infos.get(name) if info is None: raise ValueError(f"Required DOCX member is missing: {name}") if info.file_size > MAX_XML_SIZE: raise ValueError(f"DOCX member is too large: {name}") if info.compress_size == 0 and info.file_size > 0: raise ValueError(f"Suspicious compressed member: {name}") if info.compress_size and info.file_size / info.compress_size > MAX_RATIO: raise ValueError(f"Suspicious compression ratio: {name}") target = os.path.abspath(os.path.join(output_dir, name)) root = os.path.abspath(output_dir) + os.sep if not target.startswith(root): raise ValueError(f"Unsafe archive path: {name}") os.makedirs(os.path.dirname(target), exist_ok=True) with zf.open(info) as src, open(target, "wb") as dst: shutil.copyfileobj ...[truncated 157 chars]- Inspect every relevant
