# XML External Entity (XXE) Injection

Language: Python
Severity: Medium
CWE: CWE-611

## Source
9

## Flow
9-11

## Sink
11

## Vulnerable Code
```python
from xml.parsers import expat
def parse_iot_telemetry(raw_payload):
    collected = []
    def grab_data(name, attrs):
        collected.append(('start', name, attrs))
    def grab_chars(chunk):
        collected.append(('chars', chunk))
    p = expat.ParserCreate()
    p.StartElementHandler = grab_data
    p.CharacterDataHandler = grab_chars
    p.Parse(raw_payload, 1)
    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
```python
import defusedxml.expat as safe_expat

def parse_iot_telemetry(raw_payload):
    collected = []
    def grab_data(name, attrs):
        collected.append(('start', name, attrs))
    def grab_chars(chunk):
        collected.append(('chars', chunk))
    p = safe_expat.ParserCreate()
    p.StartElementHandler = grab_data
    p.CharacterDataHandler = grab_chars
    p.Parse(raw_payload, 1)
    return collected
```
