{"title":"XML External Entity (XXE) Injection","language":"Python","severity":"Medium","cwe":"CWE-611","source_lines":[9],"flow_lines":[9,11],"sink_lines":[11],"vulnerable_code":"from xml.parsers import expat\ndef parse_iot_telemetry(raw_payload):\n    collected = []\n    def grab_data(name, attrs):\n        collected.append(('start', name, attrs))\n    def grab_chars(chunk):\n        collected.append(('chars', chunk))\n    p = expat.ParserCreate()\n    p.StartElementHandler = grab_data\n    p.CharacterDataHandler = grab_chars\n    p.Parse(raw_payload, 1)\n    return collected","explanation":"The function accepts raw_payload as untrusted external input from IoT devices and passes it directly to expat.ParserCreate()'s Parse() method without applying any hardened XML parsing controls. While Python's raw expat parser does not resolve external entities by default, using the bare xml.parsers.expat without explicit protections leaves the code one configuration change away from exploitation, and does not guard against other XML abuse vectors such as entity expansion (billion laughs / XML bomb attacks). The absence of a safe parsing wrapper means there is no defense-in-depth against malicious XML payloads crafted by compromised or rogue IoT edge devices.","remediation":"The fix replaces the direct use of xml.parsers.expat.ParserCreate() with defusedxml.expat.ParserCreate(), which is a hardened wrapper around the standard expat parser that explicitly disables external entity processing, DTD processing, and entity expansion by default. The defusedxml library is the recommended approach for safe XML parsing in Python and maintains the same callback-based API, requiring no other code changes. The unused imports present in the original patch proposal (defusedxml.expatbuilder, DefusedExpatParser, defusedxml.sax, BytesIO, StringIO) have been removed to keep the fix clean and minimal.","secure_code":"import defusedxml.expat as safe_expat\n\ndef parse_iot_telemetry(raw_payload):\n    collected = []\n    def grab_data(name, attrs):\n        collected.append(('start', name, attrs))\n    def grab_chars(chunk):\n        collected.append(('chars', chunk))\n    p = safe_expat.ParserCreate()\n    p.StartElementHandler = grab_data\n    p.CharacterDataHandler = grab_chars\n    p.Parse(raw_payload, 1)\n    return collected"}