# XXE via lxml.etree.fromstring() with External Entities

Language: Python
Severity: High
CWE: CWE-611

## Source
14

## Flow
14-15-6-7

## Sink
7

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

def process_iot_device_telemetry(telemetry_xml):
    s3_client = boto3.client('s3')
    parser = etree.XMLParser(resolve_entities=True, no_network=False)
    device_data = etree.fromstring(telemetry_xml.encode(), parser)
    device_id = device_data.find('.//deviceId').text
    sensor_readings = device_data.find('.//readings').text
    metadata = {'device': device_id, 'timestamp': device_data.get('timestamp')}
    s3_client.put_object(Bucket='iot-telemetry-lake', Key=f'devices/{device_id}/data.json', Body=sensor_readings, Metadata=metadata)
    return {'status': 'processed', 'device': device_id, 'readings_count': len(sensor_readings)}

telemetry_payload = request.get_data(as_text=True)
result = process_iot_device_telemetry(telemetry_payload)
```

## Explanation

The code accepts untrusted XML input from an HTTP request and parses it using lxml with resolve_entities=True and no_network=False, enabling XML External Entity (XXE) attacks. An attacker can inject malicious XML with external entity declarations to read local files, perform SSRF attacks, or cause denial of service.

## Remediation

The fix secures the XML parser by setting resolve_entities=False to prevent external entity expansion, no_network=True to block network access during parsing, and explicitly disabling DTD loading and validation. These changes prevent attackers from exploiting XXE vulnerabilities to read local files, perform SSRF, or cause denial of service.

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

def process_iot_device_telemetry(telemetry_xml):
    s3_client = boto3.client('s3')
    parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False)
    device_data = etree.fromstring(telemetry_xml.encode(), parser)
    device_id = device_data.find('.//deviceId').text
    sensor_readings = device_data.find('.//readings').text
    metadata = {'device': device_id, 'timestamp': device_data.get('timestamp')}
    s3_client.put_object(Bucket='iot-telemetry-lake', Key=f'devices/{device_id}/data.json', Body=sensor_readings, Metadata=metadata)
    return {'status': 'processed', 'device': device_id, 'readings_count': len(sensor_readings)}

telemetry_payload = request.get_data(as_text=True)
result = process_iot_device_telemetry(telemetry_payload)
```
