{"title":"XML Entity Expansion (Billion Laughs) DoS via lxml XMLParser(resolve_entities=True)","language":"Python","severity":"High","cwe":"CWE-776","source_lines":[4],"flow_lines":[4,6,7,8],"sink_lines":[8],"vulnerable_code":"from lxml import etree\nimport io\n\ndef process_iot_device_telemetry(xml_payload):\n    parser = etree.XMLParser(resolve_entities=True, no_network=False)\n    try:\n        telemetry_stream = io.BytesIO(xml_payload.encode('utf-8'))\n        device_tree = etree.parse(telemetry_stream, parser)\n        root = device_tree.getroot()\n        device_id = root.find('.//deviceId').text\n        sensor_data = {}\n        for metric in root.findall('.//metric'):\n            sensor_data[metric.get('name')] = metric.text\n        return {'device': device_id, 'readings': sensor_data, 'status': 'processed'}\n    except Exception as e:\n        return {'error': str(e), 'status': 'failed'}\n\ndef ingest_telemetry_batch(devices_xml):\n    results = []\n    for device_xml in devices_xml:\n        result = process_iot_device_telemetry(device_xml)\n        results.append(result)\n    return results","explanation":"The function accepts untrusted XML input (xml_payload) and parses it with resolve_entities=True, which enables external entity processing. This allows attackers to craft malicious XML with recursive entity definitions (Billion Laughs attack) or external entity references that can cause denial of service through exponential memory/CPU consumption or data exfiltration.","remediation":"The fix disables entity resolution by setting resolve_entities=False, prevents network access with no_network=True, and disables DTD loading/validation with dtd_validation=False and load_dtd=False. Additionally, huge_tree=False is set to enforce default size limits, preventing memory exhaustion from excessively large documents. These settings together prevent both Billion Laughs (entity expansion) attacks and XXE (external entity) attacks.","secure_code":"from lxml import etree\nimport io\n\ndef process_iot_device_telemetry(xml_payload):\n    parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False, huge_tree=False)\n    try:\n        telemetry_stream = io.BytesIO(xml_payload.encode('utf-8'))\n        device_tree = etree.parse(telemetry_stream, parser)\n        root = device_tree.getroot()\n        device_id = root.find('.//deviceId').text\n        sensor_data = {}\n        for metric in root.findall('.//metric'):\n            sensor_data[metric.get('name')] = metric.text\n        return {'device': device_id, 'readings': sensor_data, 'status': 'processed'}\n    except Exception as e:\n        return {'error': str(e), 'status': 'failed'}\n\ndef ingest_telemetry_batch(devices_xml):\n    results = []\n    for device_xml in devices_xml:\n        result = process_iot_device_telemetry(device_xml)\n        results.append(result)\n    return results"}