# XML External Entity (XXE) via lxml.etree.fromstring with DTD Enabled

Language: Python
Severity: High
CWE: CWE-611

## Source
1

## Flow
1-3-4

## Sink
4

## Vulnerable Code
```python
from lxml import etree
import boto3

def process_iot_device_telemetry(xml_payload):
    s3_client = boto3.client('s3')
    parser = etree.XMLParser(dtd_validation=True, load_dtd=True, resolve_entities=True)
    telemetry_doc = etree.fromstring(xml_payload.encode(), parser)
    device_id = telemetry_doc.find('.//deviceId').text
    sensor_data = telemetry_doc.find('.//readings').text
    s3_client.put_object(Bucket='iot-telemetry-lake', Key=f'devices/{device_id}/data.xml', Body=sensor_data)
    return {'status': 'uploaded', 'device': device_id, 'data_size': len(sensor_data)}
```

## Explanation

The code creates an XML parser with DTD validation, DTD loading, and entity resolution all enabled (line 6), then parses untrusted XML input from IoT devices using etree.fromstring() (line 7). This allows attackers to inject malicious XML with external entity references that can read local files, perform SSRF attacks, or cause denial of service through entity expansion attacks.

## Remediation

The fix disables DTD validation, DTD loading, and entity resolution in the XML parser, and additionally sets no_network=True to prevent any network access during parsing. This prevents attackers from injecting external entity references that could read local files, perform SSRF attacks, or cause denial of service through entity expansion.

## Secure Code
```python
from lxml import etree
import boto3

def process_iot_device_telemetry(xml_payload):
    s3_client = boto3.client('s3')
    parser = etree.XMLParser(dtd_validation=False, load_dtd=False, resolve_entities=False, no_network=True)
    telemetry_doc = etree.fromstring(xml_payload.encode(), parser)
    device_id = telemetry_doc.find('.//deviceId').text
    sensor_data = telemetry_doc.find('.//readings').text
    s3_client.put_object(Bucket='iot-telemetry-lake', Key=f'devices/{device_id}/data.xml', Body=sensor_data)
    return {'status': 'uploaded', 'device': device_id, 'data_size': len(sensor_data)}
```
