# XML External Entity (XXE) Injection

Language: Python
Severity: Critical
CWE: CWE-611

## Source
6

## Flow
6-7-8

## Sink
8

## Vulnerable Code
```python
from lxml import etree
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/iot/device/config', methods=['POST'])
def parse_device_telemetry():
    raw_payload = request.data
    xml_parser = etree.XMLParser(resolve_entities=True, no_network=False)
    try:
        device_tree = etree.fromstring(raw_payload, xml_parser)
        sensor_vals = {node.tag: node.text for node in device_tree.iter()}
        cfg_blob = device_tree.find('.//config')
        result_map = {'status': 'ok', 'cfg': cfg_blob.text if cfg_blob is not None else None, 'sensors': sensor_vals}
        return jsonify(result_map)
    except etree.XMLSyntaxError as parse_err:
        return jsonify({'error': str(parse_err)}), 400
```

## Explanation

The raw HTTP request body (line 6) is passed directly to etree.fromstring() (line 8) using an XMLParser configured with resolve_entities=True and no_network=False (line 7). This allows an attacker to embed external entity declarations (XXE) in the XML payload, enabling local file disclosure (e.g., /etc/passwd) or Server-Side Request Forgery (SSRF) to internal metadata endpoints.

## Remediation

The fix secures the XMLParser by setting resolve_entities=False (preventing expansion of external and internal entity references), no_network=True (blocking any network-based entity resolution), dtd_validation=False, and load_dtd=False (preventing DTD loading entirely). Together, these settings eliminate the XXE attack surface by ensuring no external entities can be resolved from the filesystem or network.

## Secure Code
```python
from lxml import etree
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/iot/device/config', methods=['POST'])
def parse_device_telemetry():
    raw_payload = request.data
    xml_parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False)
    try:
        device_tree = etree.fromstring(raw_payload, xml_parser)
        sensor_vals = {node.tag: node.text for node in device_tree.iter()}
        cfg_blob = device_tree.find('.//config')
        result_map = {'status': 'ok', 'cfg': cfg_blob.text if cfg_blob is not None else None, 'sensors': sensor_vals}
        return jsonify(result_map)
    except etree.XMLSyntaxError as parse_err:
        return jsonify({'error': str(parse_err)}), 400
```
