T09 · Insecure Skill Coding Practices
- Location
analyze.py:55- Finding
Unbounded ZIP Decompression and XML Tree Parsing
- Content
View full analysis
Vulnerability Details
File Location:
analyze.py, lines 55–70
Vulnerability Type: Uncontrolled resource consumption when processing untrusted archives
Risk Level: MediumVulnerable Code
python def load_xml_root(zip_path: str) -> ET.Element: with zipfile.ZipFile(zip_path, "r") as zf: xml_name = next( (n for n in zf.namelist() if n.endswith("export.xml") and "export_cda" not in n), None, ) if not xml_name: raise ValueError( "在 ZIP 中找不到 export.xml。" "请确认使用的是苹果健康官方导出文件(设置 → 健康数据 → 导出)。" ) print(f"[解析] {xml_name} …", file=sys.stderr) with zf.open(xml_name) as f: return ET.parse(f).getroot()Technical Analysis
The application treats a user-supplied ZIP archive as trusted input. It opens the matching
export.xmlentry and parses the complete decompressed document into an in-memory ElementTree without enforcing limits on:- The entry's uncompressed size
- Its compression ratio
- Total archive size or number of entries
- XML nesting depth
- XML element or health-record count
- Processing time or memory consumption
A maliciously compressed archive can have a small on-disk size while expanding into an extremely large XML document. Furthermore,
ET.parse(f).getroot()constructs the entire XML tree before metric collection begins. This substantially increases peak memory use compared with streaming parsing.The XML parser does not provide an external-entity expansion path in this code, so this finding specifically concerns resource exhaustion rather than file disclosure.
Attack Path
- An attacker creates a ZIP archive containing an entry whose name ends with
export.xml. - The entry contains highly compressible or exceptionally large, structurally valid XML with many elements or deeply nested content.
- The attacker supplies the archive as an Apple Health export.
- The skill in ...[truncated 909 chars]
- Remediation
View remediation
Remediation Suggestions
- Inspect the selected entry's
ZipInfobefore opening it and reject files whose uncompressed size exceeds a conservative, documented limit. - Reject suspicious compression ratios by comparing
file_sizewithcompress_size, handling zero-byte compressed sizes safely. - Limit the archive's entry count and aggregate declared uncompressed size.
- Replace full-tree parsing with
xml.etree.ElementTree.iterparse()and clear each processed element immediately after extracting required fields. - Track and cap the number of parsed records and, where feasible, XML nesting depth.
- Run parsing under explicit memory and execution-time limits in an isolated worker process.
- Catch
ET.ParseError,OSError,MemoryError, and relevant decompression errors, then fail closed with a non-sensitive error message. - Validate that the selected document has the expected Apple Health export structure before processing all records.
- Add regression tests using oversized entries, extreme compression ratios, excessive record counts, deeply nested XML, malformed XML, and truncated archives.
Example size validation should occur before
zf.open(xml_name):python info = zf.getinfo(xml_name) MAX_XML_SIZE = 512 * 1024 * 1024 MAX_COMPRESSION_RATIO = 100 if info.file_size > MAX_XML_SIZE: raise ValueError("The export XML exceeds the permitted size.") compressed_size = max(info.compress_size, 1) if info.file_size / compressed_size > MAX_COMPRESSION_RATIO: raise ValueError("The archive has a suspicious compression ratio.")These checks should supplement, not replace, streaming XML parsing and operating-system resource limits.
- Inspect the selected entry's
