T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/fix_font_transparency.py:119
- Finding
- Unbounded Decompression and Parsing of Attacker-Controlled PPTX Archives<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fix_font_transparency.py`, lines 119-130 **Vulnerability Type**: Uncontrolled resource consumption through ZIP decompression **Risk Level**: Medium ### Vulnerable Code ```python for item in zin.infolist(): data = zin.read(item.filename) if (item.filename.startswith("ppt/slides/slide") and item.filename.endswith(".xml")): stats["slides_total"] += 1 new_data, count = process_slide_xml(data, transparency_pct) if count > 0: stats["slides_modified"] += 1 stats["runs_modified"] += count data = new_data zout.writestr(item, data) ``` ### Technical Analysis A PPTX file is a ZIP archive whose members and compression properties can be controlled by the user. The script enumerates every archive member and calls `zin.read(item.filename)`, decompressing the entire member into memory before determining whether it is relevant to the transformation. The implementation does not enforce limits on: - The number of archive members - The uncompressed size of an individual member - The aggregate uncompressed archive size - The ratio between compressed and uncompressed sizes - The size of slide XML passed to `ElementTree` - The processing time or output size Consequently, a highly compressed archive member can consume substantial memory when read. A large number of members can also cause excessive CPU and I/O consumption. For matching slide entries, the decompressed XML is parsed into an in-memory element tree, further increasing memory usage. The vulnerable operation applies to every member, including media and embedded objects that the script does not need to inspect. ### Attack Path 1. An attacker creates a syntactically valid PPTX/ZIP archive containing one or more highly compressed members with very large uncompressed sizes. 2. The attacker submits the crafted PPTX for processing through the documented Skill workflow. 3. Th ...[truncated 1046 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Validate the archive before decompressing any member: 1. Set strict limits for archive member count, individual uncompressed size, total declared uncompressed size, and compression ratio. 2. Reject entries with invalid, negative, inconsistent, or unexpectedly large metadata. 3. Apply a separate conservative size limit to slide XML before parsing it. 4. Stream-copy unchanged archive members in bounded chunks rather than loading each member entirely into memory. 5. Process modified XML under explicit memory and execution-time limits. 6. Run the conversion in an isolated worker with operating-system memory, CPU, temporary-storage, and timeout limits. 7. Remove partial output files when validation or processing fails. Example validation logic should inspect `ZipInfo.file_size` and `ZipInfo.compress_size` before calling `read()`: ```python MAX_MEMBERS = 5000 MAX_MEMBER_SIZE = 50 * 1024 * 1024 MAX_TOTAL_SIZE = 500 * 1024 * 1024 MAX_XML_SIZE = 10 * 1024 * 1024 MAX_COMPRESSION_RATIO = 100 items = zin.infolist() if len(items) > MAX_MEMBERS: raise ValueError("Archive contains too many members") total_size = 0 for item in items: if item.file_size > MAX_MEMBER_SIZE: raise ValueError(f"Archive member is too large: {item.filename}") total_size += item.file_size if total_size > MAX_TOTAL_SIZE: raise ValueError("Archive expands beyond the permitted size") compressed_size = max(item.compress_size, 1) if item.file_size / compressed_size > MAX_COMPRESSION_RATIO: raise ValueError(f"Suspicious compression ratio: {item.filename}") is_slide = ( item.filename.startswith("ppt/slides/slide") and item.filename.endswith(".xml") ) if is_slide and item.file_size > MAX_XML_SIZE: raise ValueError(f"Slide XML is too large: {item.filename}") ``` Metadata checks should be combined with runtime resource controls because ZIP metadata alone should not be treated as a complet ...[truncated 24 chars]
