# XML External Entity (XXE) Injection

Language: Python
Severity: High
CWE: CWE-611

## Source
6

## Flow
6-7-8-9

## Sink
7

## Vulnerable Code
```python
from xml.etree import ElementTree as ET
import flask
app = flask.Flask(__name__)
@app.route('/iot/device/config', methods=['POST'])
def parse_device_config():
    raw_payload = flask.request.data
    try:
        cfg_tree = ET.fromstring(raw_payload)
        dev_id = cfg_tree.findtext('deviceId')
        fw_ver = cfg_tree.findtext('firmwareVersion')
        return flask.jsonify({'device': dev_id, 'firmware': fw_ver})
    except ET.ParseError as pe:
        return flask.jsonify({'err': str(pe)}), 400
```

## Explanation

The raw HTTP POST body (raw_payload) is read directly from the untrusted network request on line 6 and immediately passed to ET.fromstring() on line 7 without any sanitization or XXE-hardening. Python's xml.etree.ElementTree does not fully protect against all XXE attacks, and combined with no use of defusedxml or explicit feature-flag hardening, a crafted DOCTYPE with external entity references can be injected to exfiltrate local files (e.g., /etc/passwd) or internal metadata endpoints.

## Remediation

The fix replaces the import of the standard library's xml.etree.ElementTree with defusedxml.ElementTree, which is a drop-in replacement that disables external entity resolution, DTD processing, and entity expansion by default. This prevents any XXE payloads embedded in crafted DOCTYPE declarations from being processed, blocking file exfiltration and SSRF attacks while maintaining identical API behavior for legitimate XML payloads.

## Secure Code
```python
import defusedxml.ElementTree as ET
import flask
app = flask.Flask(__name__)
@app.route('/iot/device/config', methods=['POST'])
def parse_device_config():
    raw_payload = flask.request.data
    try:
        cfg_tree = ET.fromstring(raw_payload)
        dev_id = cfg_tree.findtext('deviceId')
        fw_ver = cfg_tree.findtext('firmwareVersion')
        return flask.jsonify({'device': dev_id, 'firmware': fw_ver})
    except ET.ParseError as pe:
        return flask.jsonify({'err': str(pe)}), 400
```
