# XML Billion Laughs Denial of Service via Entity Expansion

Language: Python
Severity: High
CWE: CWE-776

## Source
7

## Flow
7-8

## Sink
8

## Vulnerable Code
```python
import xml.etree.ElementTree as ET
from flask import Flask, request, jsonify

iot_app = Flask(__name__)

@iot_app.route('/device/telemetry', methods=['POST'])
def ingest_sensor_telemetry():
    telemetry_payload = request.data
    device_tree = ET.fromstring(telemetry_payload)
    sensor_readings = {}
    for metric in device_tree.findall('.//reading'):
        sensor_readings[metric.get('type')] = metric.text
    return jsonify({'status': 'processed', 'metrics': sensor_readings})
```

## Explanation

The endpoint receives untrusted XML data via request.data and directly parses it using ET.fromstring() without disabling external entity processing or entity expansion. This allows an attacker to craft a malicious XML payload with recursive entity definitions (Billion Laughs attack) that exponentially expands in memory, causing denial of service.

## Remediation

The fix replaces the standard `xml.etree.ElementTree` module with `defusedxml.ElementTree`, which automatically prevents XML entity expansion attacks (Billion Laughs), external entity processing (XXE), and other XML-based attacks. Additionally, error handling was added to gracefully handle malformed or malicious XML payloads instead of crashing the application.

## Secure Code
```python
import defusedxml.ElementTree as ET
from flask import Flask, request, jsonify

iot_app = Flask(__name__)

@iot_app.route('/device/telemetry', methods=['POST'])
def ingest_sensor_telemetry():
    telemetry_payload = request.data
    try:
        device_tree = ET.fromstring(telemetry_payload)
    except ET.ParseError:
        return jsonify({'status': 'error', 'message': 'Invalid XML payload'}), 400
    except Exception as e:
        return jsonify({'status': 'error', 'message': 'XML processing error'}), 400
    sensor_readings = {}
    for metric in device_tree.findall('.//reading'):
        sensor_readings[metric.get('type')] = metric.text
    return jsonify({'status': 'processed', 'metrics': sensor_readings})
```
