CodeQL 文档

XPath 注入

ID: java/xml/xpath-injection
Kind: path-problem
Security severity: 9.8
Severity: error
Precision: high
Tags:
   - security
   - external/cwe/cwe-643
Query suites:
   - java-code-scanning.qls
   - java-security-extended.qls
   - java-security-and-quality.qls

点击查看 CodeQL 代码库中的查询

如果使用字符串串联构建 XPath 表达式,并且串联的组件包含用户输入,则用户很容易创建恶意的 XPath 表达式。

建议

如果用户输入必须包含在 XPath 表达式中,则应清理数据或预编译查询并使用变量引用来包含用户输入。

还可以通过使用 XQuery 来防止 XPath 注入。

示例

在最初三个示例中,代码接受用户指定的名称和密码,并在 XPath 表达式中使用此未验证且未清理的值。这容易受到用户提供特殊字符或字符串序列的影响,这些字符或字符串序列会更改 XPath 表达式的含义以搜索不同的值。

在第四个示例中,代码使用 setXPathVariableResolver,它可以防止 XPath 注入。

最后两个示例用于 dom4j。它们展示了 XPath 注入的示例以及一种防止它的方法。

final String xmlStr = "<users>" + 
                        "   <user name=\"aaa\" pass=\"pass1\"></user>" + 
                        "   <user name=\"bbb\" pass=\"pass2\"></user>" + 
                        "</users>";
try {
    DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
    domFactory.setNamespaceAware(true);
    DocumentBuilder builder = domFactory.newDocumentBuilder();
    //Document doc = builder.parse("user.xml");
    Document doc = builder.parse(new InputSource(new StringReader(xmlStr)));

    XPathFactory factory = XPathFactory.newInstance();
    XPath xpath = factory.newXPath();

    // Injectable data
    String user = request.getParameter("user");
    String pass = request.getParameter("pass");
    if (user != null && pass != null) {
        boolean isExist = false;

        // Bad expression
        String expression1 = "/users/user[@name='" + user + "' and @pass='" + pass + "']";
        isExist = (boolean)xpath.evaluate(expression1, doc, XPathConstants.BOOLEAN);
        System.out.println(isExist);

        // Bad expression
        XPathExpression expression2 = xpath.compile("/users/user[@name='" + user + "' and @pass='" + pass + "']");
        isExist = (boolean)expression2.evaluate(doc, XPathConstants.BOOLEAN);
        System.out.println(isExist);

        // Bad expression
        StringBuffer sb = new StringBuffer("/users/user[@name=");
        sb.append(user);
        sb.append("' and @pass='");
        sb.append(pass);
        sb.append("']");
        String query = sb.toString();
        XPathExpression expression3 = xpath.compile(query);
        isExist = (boolean)expression3.evaluate(doc, XPathConstants.BOOLEAN);
        System.out.println(isExist);

        // Good expression
        String expression4 = "/users/user[@name=$user and @pass=$pass]";
        xpath.setXPathVariableResolver(v -> {
        switch (v.getLocalPart()) {
            case "user":
                return user;
            case "pass":
                return pass;
            default:
                throw new IllegalArgumentException();
            }
        });
        isExist = (boolean)xpath.evaluate(expression4, doc, XPathConstants.BOOLEAN);
        System.out.println(isExist);


        // Bad Dom4j 
        org.dom4j.io.SAXReader reader = new org.dom4j.io.SAXReader();
        org.dom4j.Document document = reader.read(new InputSource(new StringReader(xmlStr)));
        isExist = document.selectSingleNode("/users/user[@name='" + user + "' and @pass='" + pass + "']") != null;
        // or document.selectNodes
        System.out.println(isExist);

        // Good Dom4j
        org.jaxen.SimpleVariableContext svc = new org.jaxen.SimpleVariableContext();
        svc.setVariableValue("user", user);
        svc.setVariableValue("pass", pass);
        String xpathString = "/users/user[@name=$user and @pass=$pass]";
        org.dom4j.XPath safeXPath = document.createXPath(xpathString);
        safeXPath.setVariableContext(svc);
        isExist = safeXPath.selectSingleNode(document) != null;
        System.out.println(isExist);
    }
} catch (ParserConfigurationException e) {

} catch (SAXException e) {

} catch (XPathExpressionException e) {

} catch (org.dom4j.DocumentException e) {

}

参考资料

  • ©GitHub, Inc.
  • 条款
  • 隐私