== How to fix it in lxml

=== Code examples

The following noncompliant code is vulnerable to XPath injection because untrusted data is
concatenated to an XPath query without prior validation.

==== Noncompliant code example

[source,python,diff-id=1,diff-type=noncompliant]
----
from flask import request
from lxml import etree

@app.route('/authenticate')
def authenticate():
    username = request.args['username']
    password = request.args['password']
    expression = "./users/user[@name='" + username + "' and @pass='" + password + "']"
    tree = etree.parse('resources/users.xml')

    if tree.find(expression) is None:
        return "Invalid credentials", 401
    else:
        return "Success", 200
----

==== Compliant solution

[source,python,diff-id=1,diff-type=compliant]
----
from flask import request
from lxml import etree

@app.route('/authenticate')
def authenticate():
    username = request.args['username']
    password = request.args['password']
    expression = "./users/user[@name=$username and @pass=$password]"
    tree = etree.parse('resources/users.xml')

    if tree.xpath(expression, username=username, password=password) is None:
        return "Invalid credentials", 401
    else:
        return "Success", 200
----

=== How does this work?

As a rule of thumb, the best approach to protect against injections is to
systematically ensure that untrusted data cannot break out of the initially
intended logic.

include::../../common/fix/parameterized-queries.adoc[]

In the example, the username and password are passed as https://lxml.de/xpathxslt.html#:~:text=The%20xpath()%20method%20has%20support%20for%20XPath%20variables%3A[XPath variables] rather than concatenated to the XPath query. By using a parameterized query, injection is successfully prevented.
