# XML External Entity (XXE) Injection

Language: Python
Severity: Critical
CWE: CWE-611

## Source
9

## Flow
9-11

## Sink
11

## Vulnerable Code
```python
from xml.parsers import expat
def parse_iot_device_config(raw_xml_payload):
    discovered_entities = {}
    def handle_entity_decl(name, is_param, val, sysid, pubid, notation):
        discovered_entities[name] = sysid or val
    def capture_char_data(chunk):
        parse_iot_device_config.last_data = getattr(parse_iot_device_config, 'last_data', '') + chunk
    p = expat.ParserCreate()
    p.EntityDeclHandler = handle_entity_decl
    p.CharacterDataHandler = capture_char_data
    p.ParseParamEntityExternals = 1
    p.Parse(raw_xml_payload, 1)
    return getattr(parse_iot_device_config, 'last_data', ''), discovered_entities
```

## Explanation

The function accepts raw XML input (`raw_xml_payload`) from an untrusted source (IoT device agent over MQTT) and passes it directly to the expat parser with `ParseParamEntityExternals = 1` enabled. This setting explicitly allows the parser to resolve external parameter entities, enabling XXE attacks where an attacker can craft a malicious XML payload referencing external resources like local files (`/etc/passwd`) or internal network services. The `EntityDeclHandler` and `CharacterDataHandler` then capture and return the resolved entity content to the caller.

## Remediation

The fix applies three defensive measures: (1) `SetParamEntityParsing` is set to `XML_PARAM_ENTITY_PARSING_NEVER` to prevent the parser from resolving external parameter entities, replacing the dangerous `ParseParamEntityExternals = 1` setting. (2) The `EntityDeclHandler` now explicitly rejects any entity declaration that includes a system ID or public ID (i.e., external entities), raising a `ValueError` to abort parsing. (3) Discovered external entities are no longer collected and returned, eliminating the data exfiltration path. The character data accumulation was also refactored to use a list instead of a mutable function attribute for cleaner design.

## Secure Code
```python
from xml.parsers import expat


def parse_iot_device_config(raw_xml_payload):
    captured_data = []

    def handle_entity_decl(name, is_param, val, sysid, pubid, notation):
        # Reject any external entity declarations (those with a system or public ID)
        if sysid is not None or pubid is not None:
            raise ValueError(
                f"External entity declarations are not allowed: entity '{name}'"
            )

    def capture_char_data(chunk):
        captured_data.append(chunk)

    p = expat.ParserCreate()
    p.EntityDeclHandler = handle_entity_decl
    p.CharacterDataHandler = capture_char_data

    # Disable resolution of external parameter entities
    p.SetParamEntityParsing(expat.XML_PARAM_ENTITY_PARSING_NEVER)

    p.Parse(raw_xml_payload, 1)
    return ''.join(captured_data), {}
```
