# XPath Injection via lxml.etree.XPath on Untrusted Input

Language: Python
Severity: High
CWE: CWE-643

## Source
1

## Flow
1-4-5-6

## Sink
4

## Vulnerable Code
```python
from lxml import etree

def fetch_iot_device_telemetry(device_xml, sensor_id):
    doc = etree.fromstring(device_xml.encode())
    query = f"//device/sensors/sensor[@id='{sensor_id}']/readings/temperature"
    xpath_eval = etree.XPath(query)
    results = xpath_eval(doc)
    if results:
        return [float(node.text) for node in results]
    return []
```

## Explanation

The function accepts untrusted user input via the sensor_id parameter and directly concatenates it into an XPath query string without sanitization or validation. This allows attackers to inject malicious XPath expressions that can extract unauthorized data from the XML document or alter query logic.

## Remediation

The fix uses lxml's built-in XPath variable substitution ($sensor_id) instead of string concatenation, which prevents injection by treating the parameter as a literal value rather than part of the XPath expression. Additionally, an input validation check using a whitelist regex is applied as defense-in-depth to reject any sensor_id containing unexpected characters.

## Secure Code
```python
from lxml import etree
import re

def fetch_iot_device_telemetry(device_xml, sensor_id):
    # Validate sensor_id to only allow safe characters (alphanumeric, hyphens, underscores)
    if not re.match(r'^[a-zA-Z0-9_\-]+$', sensor_id):
        raise ValueError("Invalid sensor_id: only alphanumeric characters, hyphens, and underscores are allowed.")
    
    doc = etree.fromstring(device_xml.encode())
    # Use XPath variable substitution to prevent injection
    query = "//device/sensors/sensor[@id=$sensor_id]/readings/temperature"
    xpath_eval = etree.XPath(query)
    results = xpath_eval(doc, sensor_id=sensor_id)
    if results:
        return [float(node.text) for node in results]
    return []
```
