{"title":"XXE via lxml.etree.fromstring() with External Entities","language":"Python","severity":"High","cwe":"CWE-611","source_lines":[14],"flow_lines":[14,15,6,7],"sink_lines":[7],"vulnerable_code":"from lxml import etree\nimport boto3\n\ndef process_iot_device_telemetry(telemetry_xml):\n    s3_client = boto3.client('s3')\n    parser = etree.XMLParser(resolve_entities=True, no_network=False)\n    device_data = etree.fromstring(telemetry_xml.encode(), parser)\n    device_id = device_data.find('.//deviceId').text\n    sensor_readings = device_data.find('.//readings').text\n    metadata = {'device': device_id, 'timestamp': device_data.get('timestamp')}\n    s3_client.put_object(Bucket='iot-telemetry-lake', Key=f'devices/{device_id}/data.json', Body=sensor_readings, Metadata=metadata)\n    return {'status': 'processed', 'device': device_id, 'readings_count': len(sensor_readings)}\n\ntelemetry_payload = request.get_data(as_text=True)\nresult = process_iot_device_telemetry(telemetry_payload)","explanation":"The code accepts untrusted XML input from an HTTP request and parses it using lxml with resolve_entities=True and no_network=False, enabling XML External Entity (XXE) attacks. An attacker can inject malicious XML with external entity declarations to read local files, perform SSRF attacks, or cause denial of service.","remediation":"The fix secures the XML parser by setting resolve_entities=False to prevent external entity expansion, no_network=True to block network access during parsing, and explicitly disabling DTD loading and validation. These changes prevent attackers from exploiting XXE vulnerabilities to read local files, perform SSRF, or cause denial of service.","secure_code":"from lxml import etree\nimport boto3\n\ndef process_iot_device_telemetry(telemetry_xml):\n    s3_client = boto3.client('s3')\n    parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False, load_dtd=False)\n    device_data = etree.fromstring(telemetry_xml.encode(), parser)\n    device_id = device_data.find('.//deviceId').text\n    sensor_readings = device_data.find('.//readings').text\n    metadata = {'device': device_id, 'timestamp': device_data.get('timestamp')}\n    s3_client.put_object(Bucket='iot-telemetry-lake', Key=f'devices/{device_id}/data.json', Body=sensor_readings, Metadata=metadata)\n    return {'status': 'processed', 'device': device_id, 'readings_count': len(sensor_readings)}\n\ntelemetry_payload = request.get_data(as_text=True)\nresult = process_iot_device_telemetry(telemetry_payload)"}