T09 · Insecure Skill Coding Practices
- Location
zotero_docx.py:614- Finding
Unbounded DOCX Decompression Enables Memory-Exhaustion Denial of Service
- Content
View full analysis
Vulnerability Details
File Location:
zotero_docx.py, lines 614–616
Vulnerability Type: Unbounded decompression and memory consumption
Risk Level: MediumVulnerable Code
python with zipfile.ZipFile(src) as zf: infos = zf.infolist() items = {i.filename: zf.read(i.filename) for i in infos} before_inv = _field_inventory(zf, ns) before_prefs = _prefs_of(zf)Additional archive-controlled reads occur during precheck and validation without limits, including
zf.read(...)calls at lines 138, 144, 149, and 163.Technical Analysis
A DOCX file is a ZIP archive. The implementation accepts a user-supplied DOCX and reads every archive member into memory through
ZipFile.read(). The decompressed contents are retained simultaneously in theitemsdictionary.No limits are enforced on:
- The number of ZIP members
- The uncompressed size of an individual member
- The aggregate uncompressed archive size
- The ratio between compressed and uncompressed sizes
- The amount of data read into process memory
Consequently, a small but highly compressible DOCX can expand to a very large size when processed. The initial precheck also decompresses selected XML members before the full archive-loading operation, but it does not apply size limits either.
This flaw does not provide code execution or additional system privileges. It enables resource exhaustion in the security context of the process handling the document.
Attack Path
- An attacker creates a ZIP archive with a
.docxextension. - The archive includes the minimum valid OOXML and Zotero field structures required to pass early format checks.
- The attacker adds one or more members containing extremely large, highly compressible data.
- The crafted document is supplied to
process()as the source file. - The code enumerates all members and executes
zf.read(i.filename)for each entry. - The decompressed data for every member is retained in the
itemsdictiona ...[truncated 729 chars]
- Remediation
View remediation
Remediation Suggestions
Apply archive limits before calling
ZipFile.read():- Inspect every
ZipInfoentry and reject archives that exceed a defined member-count limit. - Reject any individual member whose
file_sizeexceeds a safe maximum. - Sum all declared uncompressed sizes and reject archives exceeding an aggregate limit.
- Reject suspicious compression ratios, accounting safely for zero-byte compressed entries.
- Apply the checks before precheck functions read archive members.
- Read only the OOXML parts needed for the requested operation instead of loading every member into a dictionary.
- Use bounded streaming for large non-XML members where preservation is necessary.
- Run document processing under explicit memory and execution-time limits as defense in depth.
- Add regression tests covering:
- A single high-compression-ratio member
- Excessive aggregate uncompressed size
- Excessive member count
- Misleading declared archive structures
- Boundary values at and immediately above each configured limit
A centralized validation function should be called immediately after opening the ZIP archive and before any member is decompressed.
- Inspect every
