XPath injection occurs when an XML document is used for data storage in a manner similar to a relational database. This attack is similar to SQL injection or XML injection (see VOID IDS07the appropriate parts of IDS00-J. Prevent SQL InjectionSanitize untrusted data passed across a trust boundary) wherein an attacker can enter valid SQL or XML constructs in the data fields of the query in use. Typically, the conditional field of the query resolves to a tautology or gives the attacker access to privileged information. This guideline is a specific example of the broadly scoped void Filter data that passes through a trust boundary IDS52-J. Prevent code injection.
XML Path Injection Example
Consider the following XML schema.
Code Block |
---|
<users>
<user>
<login>Utah</login>
<password>C^f3</password>
</user>
<user>
<login>Bohdi</login>
<password>C@fe</password>
</user>
<user>
<login>Busey</login>
<password>cAf3</password>
</user>
</users>
|
Untrusted code may attempt to retrieve user details from this file with an XPath statement constructed dynamically from user input.
Code Block |
---|
//users/user[LoginID/text()='&LOGIN&' and password/text()='&PASSWORD&' ]
|
If an attacker knows that Utah
is a valid login ID, he or she can specify an input login ID such as:
Code Block |
---|
Utah' or '1'='1
|
This would yield the following query string:
Code Block |
---|
//users/user[login/text()='Utah' or '1'='1' and password/text()='xxxx' ]
|
Because the '1'='1'
is automatically true, the password is never validated. Consequently, the attacker is falsely logged in as user Utah
without having to know the password.
To comply with MSC05 MSC51-J. Store passwords using a hash function, the passwords would have to be encrypted. Unfortunately, on many small systems, they are not, so the password text added in the query string would match precisely what the user enters. An attacker could supply a password such as:
Code Block |
---|
' or '1'='1
|
This would yield the following query string:
Code Block |
---|
//users/user[password/text()='xxxx' and login/text()='' or '1'='1' ]
|
...
In this noncompliant code example, a user name and password are read from the user and used to construct the query string. The password is passed as a char array, and then hashed, all to comply with MSC05with MSC51-J. Store passwords using a hash function and MSC56-J. Limit the lifetime of sensitive data.
If passed the attack string for login
described previously, the evaluate()
method call returns the corresponding login node in the XML file, causing the login()
method to return true
and bypass any authorization.
Code Block | ||
---|---|---|
| ||
private boolean doLogin(String loginID, char[] password)
throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true);
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document doc = builder.parse("users.xml");
String pwd = hashPassword( password);
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
XPathExpression expr = xpath.compile("//users/user[login/text()='" +
loginID + "' and password/text()='" + pwd + "' ]");
Object result = expr.evaluate(doc, XPathConstants.NODESET);
NodeList nodes = (NodeList) result;
// Print first names to the console
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i).getChildNodes().item(1).getChildNodes().item(0);
System.out.println( "Authenticated: " + node.getNodeValue());
}
return (nodes.getLength() >= 1);
}
|
...
- Treat all user input as untrusted and perform appropriate sanitization.
- When sanitizing user input, verify the correctness of the data type, length, format, and content. For example, use a regular expression that checks for XML tags and special characters in user input. This corresponds to input sanitization. See void Filter data that passes through a trust boundary See IDS52-J. Prevent code injection for additional details.
- In a client-server application, perform validation at both the client and the server side.
- Extensively test applications that supply, propagate, or use user input.
...
Input File: login.qry
Code Block |
---|
declare variable $loginID as xs:string external;
declare variable $password as xs:string external;
//users/user[@loginID=$loginID and @password=$password]
|
This compliant solution uses a query specified in a text file by reading the file in the required format and then entering values for the user name and password in a Map
. The XQuery
library constructs the XML query from these elements.
Code Block | ||
---|---|---|
| ||
private boolean doLogin(String loginID, String pwd)
throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true);
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document doc = builder.parse("users.xml");
XQuery xquery = new XQueryFactory().createXQuery(new File("login.xry"));
Map queryVars = new HashMap();
queryVars.put("loginid", loginID);
queryVars.put("password", pwd);
NodeList nodes = xquery.execute(doc, null, queryVars).toNodes();
// Print first names to the console
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i).getChildNodes().item(1).getChildNodes().item(0);
System.out.println( node.getNodeValue());
}
return (nodes.getLength() >= 1);
}
|
...
Guideline | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
IDS09IDS50-J | medium | probable | medium | P8 | L2 |
...