{"title":"XML Entity Expansion DoS via Billion Laughs in lxml XMLParser","language":"Python","severity":"High","cwe":"CWE-776","source_lines":[3],"flow_lines":[3,5,6],"sink_lines":[6],"vulnerable_code":"from lxml import etree\nimport io\n\ndef process_iot_device_telemetry(telemetry_xml_stream):\n    parser = etree.XMLParser(resolve_entities=True, no_network=False)\n    try:\n        telemetry_doc = etree.parse(io.BytesIO(telemetry_xml_stream.encode('utf-8')), parser)\n        root_element = telemetry_doc.getroot()\n        device_metrics = {}\n        for sensor in root_element.findall('.//sensor'):\n            metric_id = sensor.get('id')\n            metric_value = sensor.find('reading').text\n            device_metrics[metric_id] = metric_value\n        return {'status': 'processed', 'metrics': device_metrics}\n    except Exception as err:\n        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":"from lxml import etree\nimport io\n\ndef process_iot_device_telemetry(telemetry_xml_stream):\n    parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False, huge_tree=False)\n    try:\n        telemetry_doc = etree.parse(io.BytesIO(telemetry_xml_stream.encode('utf-8')), parser)\n        root_element = telemetry_doc.getroot()\n        device_metrics = {}\n        for sensor in root_element.findall('.//sensor'):\n            metric_id = sensor.get('id')\n            metric_value = sensor.find('reading').text\n            device_metrics[metric_id] = metric_value\n        return {'status': 'processed', 'metrics': device_metrics}\n    except Exception as err:\n        return {'status': 'error', 'message': str(err)}"}