# XML External Entity (XXE) Injection via defusedxml Bypass

Language: Python
Severity: Critical
CWE: CWE-611

## Source
9

## Flow
9-11

## Sink
11

## Vulnerable Code
```python
from lxml import etree
import defusedxml.ElementTree as dxml
def parse_iot_telemetry(raw_payload, use_safe=True):
    if use_safe:
        try:
            root = dxml.fromstring(raw_payload)
            return {child.tag: child.text for child in root}
        except Exception:
            use_safe = False
    parser = etree.XMLParser(resolve_entities=True, no_network=False)
    tree = etree.fromstring(raw_payload.encode() if isinstance(raw_payload, str) else raw_payload, parser)
    device_data = {node.tag: node.text for node in tree.iter()}
    return device_data
```

## Explanation

The function accepts raw_payload as untrusted input and attempts safe parsing with defusedxml, but silently falls back to lxml's etree.fromstring() with resolve_entities=True and no_network=False when any exception occurs. An attacker can craft a payload that deliberately triggers an exception in defusedxml (e.g., a DTD-triggering payload that defusedxml blocks via exception), causing the code to re-parse the same malicious payload using lxml with full XXE capabilities enabled.

## Remediation

The fix removes the entire unsafe fallback path that used lxml with resolve_entities=True and no_network=False, eliminating the XXE vulnerability completely. The function now exclusively uses defusedxml for parsing, and if parsing fails, the exception propagates to the caller rather than silently falling back to an insecure parser. The lxml import and the use_safe parameter (which enabled the fallback logic) have been removed entirely to prevent any possibility of re-introducing the unsafe path.

## Secure Code
```python
import defusedxml.ElementTree as dxml

def parse_iot_telemetry(raw_payload):
    """Parse IoT telemetry XML payload safely using defusedxml.
    
    Raises an exception if the payload cannot be parsed safely.
    No fallback to unsafe parsing is permitted.
    """
    root = dxml.fromstring(raw_payload)
    return {child.tag: child.text for child in root}
```
