# Billion Laughs XML Entity Expansion Denial of Service

Language: Python
Severity: High
CWE: CWE-776

## Source
7

## Flow
7-8-9

## Sink
9

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

app = Flask(__name__)

@app.route('/iot/device/config', methods=['POST'])
def update_device_configuration():
    device_xml = request.data.decode('utf-8')
    parser = ET.XMLParser()
    config_tree = ET.fromstring(device_xml, parser=parser)
    device_id = config_tree.find('device_id').text
    settings = {child.tag: child.text for child in config_tree.find('settings')}
    return jsonify({'status': 'configured', 'device': device_id, 'applied_settings': settings})
```

## Explanation

The code uses xml.etree.ElementTree to parse untrusted XML data from the POST request without disabling entity expansion. This allows an attacker to craft malicious XML with nested entity definitions that exponentially expand during parsing, consuming excessive memory and CPU resources, leading to a Denial of Service (Billion Laughs attack).

## Remediation

The fix replaces the standard xml.etree.ElementTree module with defusedxml.ElementTree, which automatically prevents entity expansion attacks (Billion Laughs), external entity processing (XXE), and other XML-based attacks. Additionally, error handling and input validation were added to gracefully handle malformed XML and missing elements.

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

app = Flask(__name__)

@app.route('/iot/device/config', methods=['POST'])
def update_device_configuration():
    device_xml = request.data.decode('utf-8')
    try:
        config_tree = ET.fromstring(device_xml)
    except ET.ParseError:
        return jsonify({'status': 'error', 'message': 'Invalid XML payload'}), 400
    device_id_elem = config_tree.find('device_id')
    settings_elem = config_tree.find('settings')
    if device_id_elem is None or settings_elem is None:
        return jsonify({'status': 'error', 'message': 'Missing required XML elements'}), 400
    device_id = device_id_elem.text
    settings = {child.tag: child.text for child in settings_elem}
    return jsonify({'status': 'configured', 'device': device_id, 'applied_settings': settings})
```
