=== How to fix it in the Python Standard Library

The following noncompliant code is vulnerable to XPath injection because untrusted data is
concatenated to an XPath query without prior validation. Although `xml.etree.ElementTree` only
supports a subset of XPath syntax, exploitation can still be possible.

==== Noncompliant code example

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

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

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

////
The above example would be exploitable by using e.g. `username=”admin][.!=”` and `password=”][.!='XXXXX”`
This creates the following query: `./users/user[@name='admin][.!='][@pass='][.!='XXXXX']`,
where `[.!='][@pass=']` and `[.!='XXXXX']` should match on everything, resulting in authentication as admin.
////

==== Compliant solution

[source,python,diff-id=1,diff-type=compliant]
----
import re
from xml.etree import ElementTree
from flask import request

tree = ElementTree.parse('resources/users.xml')

@app.route('/authenticate')
def authenticate():
    username = request.args['username']
    password = request.args['password']

    if re.match("^[a-zA-Z0-9]*$", username) is None or re.match("^[a-zA-Z0-9]*$", password) is None:
        return "Username or password contains invalid characters", 400

    expression = "./users/user[@name='" + username + "'][@pass='" + password + "']"
    tree = ElementTree.parse('resources/users.xml')

    if tree.find(expression) 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[]

It is not possible to construct parameterized queries using only the Python Standard Library. +
Please use https://pypi.org/project/lxml/[lxml] instead, which allows for parameterized queries using the `ElementTree.xpath()` method.

include::../../common/fix/validation.adoc[]

In the example, we ensure that the username and password only contain alphanumeric characters by doing a regex match before executing the XPath query.
