T09 · Insecure Skill Coding Practices
- Location
scripts/wechat_file_index.py:133- Finding
Unbounded Decompression and XML Parsing of Untrusted Office Documents
- Content
View full analysis
Vulnerability Details
File Location:
scripts/wechat_file_index.py:133-154
Vulnerability Type: Uncontrolled resource consumption through unbounded archive decompression and XML parsing
Risk Level: MediumVulnerable Code
python def extract_docx(path: Path) -> tuple[str, str]: with zipfile.ZipFile(path) as archive: xml = archive.read("word/document.xml") root = ET.fromstring(xml) parts = [node.text for node in root.iter() if node.tag.endswith("}t") and node.text] return "\n".join(parts)[:MAX_CONTENT_CHARS], "extracted" def extract_xlsx(path: Path) -> tuple[str, str]: parts: list[str] = [] with zipfile.ZipFile(path) as archive: names = archive.namelist() if "xl/sharedStrings.xml" in names: root = ET.fromstring(archive.read("xl/sharedStrings.xml")) parts.extend(node.text for node in root.iter() if node.tag.endswith("}t") and node.text) for name in names: if re.fullmatch(r"xl/worksheets/sheet\d+\.xml", name): root = ET.fromstring(archive.read(name)) parts.extend(node.text for node in root.iter() if node.text and node.tag.endswith(("}v", "}t"))) if sum(len(item) for item in parts) >= MAX_CONTENT_CHARS: break return "\n".join(parts)[:MAX_CONTENT_CHARS], "extracted"Technical Analysis
DOCX and XLSX files are ZIP archives containing XML members. The extraction functions use
ZipFile.read()to decompress each selected member completely into memory and then pass the complete byte string toElementTree.fromstring().The implementation does not check:
- The member's uncompressed size.
- The ratio between compressed and uncompressed sizes.
- The total amount of data decompressed from an archive.
- The number or aggregate size of worksheet members.
- The time or memory consumed by XML parsing.
`MAX_CONTEN ...[truncated 2310 chars]
- Remediation
View remediation
Remediation Suggestions
-
Inspect every relevant
ZipInfoentry before extraction and reject members whose declared uncompressed size exceeds a conservative limit. -
Enforce limits for both individual members and the whole archive, including:
- Maximum uncompressed bytes per member.
- Maximum aggregate uncompressed bytes.
- Maximum number of worksheet members.
- Maximum compression ratio.
-
Replace unbounded
archive.read(name)calls with bounded streaming reads. Stop reading and report a dedicated status such asarchive_member_too_largeonce the byte limit is exceeded. -
Parse XML incrementally with
ElementTree.iterparse()or an equivalently constrained parser, clearing processed elements as parsing proceeds. Stop once the text or byte budget is reached. -
Perform all size checks before XML parsing. Do not rely on truncating the final extracted string because that occurs after the expensive operations.
-
Run document extraction in an isolated worker process with memory and execution-time limits. Terminate the worker cleanly if either limit is exceeded so the main indexing operation can continue.
-
Catch resource-limit and malformed-archive failures per file, store an explicit
content_status, and continue indexing other files. -
Add regression tests using highly compressed Office fixtures, oversized XML members, excessive worksheet counts, malformed archives, and files near each configured limit.
-
