# XML Entity Expansion DoS via Billion Laughs in lxml XMLParser

Language: Python
Severity: High
CWE: CWE-776

## Source
3

## Flow
3-5-6

## Sink
6

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

def process_iot_device_telemetry(telemetry_xml_stream):
    parser = etree.XMLParser(resolve_entities=True, no_network=False)
    try:
        telemetry_doc = etree.parse(io.BytesIO(telemetry_xml_stream.encode('utf-8')), parser)
        root_element = telemetry_doc.getroot()
        device_metrics = {}
        for sensor in root_element.findall('.//sensor'):
            metric_id = sensor.get('id')
            metric_value = sensor.find('reading').text
            device_metrics[metric_id] = metric_value
        return {'status': 'processed', 'metrics': device_metrics}
    except Exception as err:
        return {'status': 'error', 'message': str(err)}
```

## Explanation

The function accepts untrusted XML input (telemetry_xml_stream) and parses it with lxml's XMLParser configured with resolve_entities=True, which enables XML entity expansion. This allows attackers to craft malicious XML with recursive entity definitions (Billion Laughs attack) that exponentially expand during parsing, causing memory exhaustion and denial of service.

## Remediation

The fix disables entity resolution by setting resolve_entities=False, which prevents the XML parser from expanding entity definitions and stops Billion Laughs attacks. Additionally, no_network=True prevents external entity fetching, load_dtd=False and dtd_validation=False prevent DTD processing, and huge_tree=False limits document size to prevent resource exhaustion.

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

def process_iot_device_telemetry(telemetry_xml_stream):
    parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False, huge_tree=False)
    try:
        telemetry_doc = etree.parse(io.BytesIO(telemetry_xml_stream.encode('utf-8')), parser)
        root_element = telemetry_doc.getroot()
        device_metrics = {}
        for sensor in root_element.findall('.//sensor'):
            metric_id = sensor.get('id')
            metric_value = sensor.find('reading').text
            device_metrics[metric_id] = metric_value
        return {'status': 'processed', 'metrics': device_metrics}
    except Exception as err:
        return {'status': 'error', 'message': str(err)}
```
