{"title":"XPath Injection via Untrusted lxml XPath Predicate","language":"Python","severity":"High","cwe":"CWE-643","source_lines":[5,6],"flow_lines":[5,8,9],"sink_lines":[9],"vulnerable_code":"from lxml import etree\nfrom flask import request, jsonify\n\ndef query_iot_device_metrics(xml_data):\n    device_id = request.args.get('device_id', '')\n    metric_type = request.args.get('metric', 'temperature')\n    doc = etree.fromstring(xml_data.encode())\n    xpath_query = f\"//device[@id='{device_id}']/sensors/sensor[@type='{metric_type}']/reading\"\n    results = doc.xpath(xpath_query)\n    metrics = [{'value': r.text, 'timestamp': r.get('timestamp')} for r in results]\n    return jsonify({'device': device_id, 'metrics': metrics})","explanation":"User-controlled input from request parameters 'device_id' and 'metric' are directly interpolated into an XPath query string without sanitization. An attacker can inject malicious XPath syntax to bypass intended query logic, extract unauthorized data, or cause denial of service.","remediation":"The fix uses lxml's built-in XPath parameterization (XPath variables via $variable_name syntax) which safely passes user input as string parameters rather than interpolating them into the query string, completely preventing XPath injection. Additionally, a whitelist validation function rejects any input containing characters outside of alphanumerics, hyphens, underscores, and dots as a defense-in-depth measure.","secure_code":"import re\nfrom lxml import etree\nfrom flask import request, jsonify, abort\n\ndef _validate_identifier(value, field_name):\n    \"\"\"Validate that the input is a safe identifier (alphanumeric, hyphens, underscores, dots).\"\"\"\n    if not value:\n        abort(400, description=f\"Missing required parameter: {field_name}\")\n    if not re.match(r'^[a-zA-Z0-9_\\-\\.]+$', value):\n        abort(400, description=f\"Invalid characters in parameter: {field_name}\")\n    return value\n\ndef query_iot_device_metrics(xml_data):\n    device_id = request.args.get('device_id', '')\n    metric_type = request.args.get('metric', 'temperature')\n    \n    # Validate inputs to prevent XPath injection\n    device_id = _validate_identifier(device_id, 'device_id')\n    metric_type = _validate_identifier(metric_type, 'metric')\n    \n    doc = etree.fromstring(xml_data.encode())\n    \n    # Use parameterized XPath via XPath class with variables to prevent injection\n    xpath_expr = etree.XPath(\n        \"//device[@id=$device_id]/sensors/sensor[@type=$metric_type]/reading\"\n    )\n    results = xpath_expr(doc, device_id=device_id, metric_type=metric_type)\n    \n    metrics = [{'value': r.text, 'timestamp': r.get('timestamp')} for r in results]\n    return jsonify({'device': device_id, 'metrics': metrics})"}