{"title":"Billion Laughs XML Entity Expansion DoS via lxml.etree.fromstring","language":"Python","severity":"High","cwe":"CWE-776","source_lines":[5],"flow_lines":[5,6],"sink_lines":[6],"vulnerable_code":"from lxml import etree\nimport base64\n\ndef process_iot_device_telemetry(encoded_payload):\n    raw_xml = base64.b64decode(encoded_payload)\n    telemetry_doc = etree.fromstring(raw_xml)\n    device_id = telemetry_doc.find('.//deviceID').text\n    metrics = {elem.tag: elem.text for elem in telemetry_doc.findall('.//metric')}\n    temperature = metrics.get('temp', 'N/A')\n    humidity = metrics.get('humidity', 'N/A')\n    return {'device': device_id, 'temp': temperature, 'humidity': humidity}","explanation":"The code decodes base64-encoded XML and parses it using lxml.etree.fromstring() without disabling entity expansion. An attacker can send XML containing recursive entity definitions (Billion Laughs attack) that exponentially expand in memory, causing denial of service through resource exhaustion.","remediation":"The fix creates a custom XMLParser with resolve_entities=False to prevent entity expansion, no_network=True to block external network access, and load_dtd=False to prevent loading of DTD definitions. This neutralizes Billion Laughs attacks by refusing to expand recursive entity definitions in the XML payload.","secure_code":"from lxml import etree\nimport base64\n\ndef process_iot_device_telemetry(encoded_payload):\n    raw_xml = base64.b64decode(encoded_payload)\n    parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False)\n    telemetry_doc = etree.fromstring(raw_xml, parser=parser)\n    device_id = telemetry_doc.find('.//deviceID').text\n    metrics = {elem.tag: elem.text for elem in telemetry_doc.findall('.//metric')}\n    temperature = metrics.get('temp', 'N/A')\n    humidity = metrics.get('humidity', 'N/A')\n    return {'device': device_id, 'temp': temperature, 'humidity': humidity}"}