XQuery是专门用于查询XML数据的语言,功能上类似于关系型数据库中的SQL。许多系统使用XML文件或原生XML数据库存储配置、订单、文档等数据,服务端通过拼装XQuery语句来完成检索。如果拼装过程中用户输入未经严格处理,攻击者就可以注入额外的XQuery表达式,突破原本的查询边界,访问或修改不该被访问的数据,这就是XQuery注入攻击。它与SQL注入在思路上高度相似,但由于XQuery语法灵活、支持路径表达式和函数调用,注入后的攻击面甚至更广,值得每一位开发者重视。

XQuery注入的原理与典型场景
XQuery注入的根源在于代码与数据没有分离。当后端代码采用字符串拼接方式构造XQuery语句时,用户输入的内容不再只是“数据”,而是可能被XQuery解析器当作“代码”执行的一部分。来看一段典型的Java示例代码:
String username = request.getParameter("username");
String password = request.getParameter("password");
// 直接拼接用户输入,存在注入风险
String xquery = "for $u in doc('users.xml')/users/user "
+ "where $u/username = '" + username + "' "
+ "and $u/password = '" + password + "' "
+ "return $u";
XQResultSequence rs = xqConnection.prepareExpression(xquery).executeQuery();
这段代码的本意是根据用户名和密码查询匹配的user节点。但由于username和password被直接嵌入查询字符串,攻击者可以在username中输入admin' or '1'='1,拼接后的语句变成恒真条件,从而绕过登录校验。这与经典的SQL注入绕过手法完全一致。
除了绕过认证,XQuery注入还能做更多事情。XQuery允许使用路径表达式访问任意节点,攻击者可以输入类似' or true() return /users/user的内容,把整个users.xml文档中的所有用户记录全部返回,一次性拖走全部账号数据。更危险的是,如果XML数据库开启了update功能,注入的语句甚至可能修改或删除节点,造成数据被破坏。
另外需要注意,XQuery内置了大量函数,例如doc()函数可以加载外部文档。如果服务端以高权限运行,攻击者可能通过注入doc()表达式去读取服务器上的其他XML文件,比如配置文件中的数据库连接串、密钥等敏感信息,攻击影响范围会进一步扩大。
常见的XQuery注入攻击载荷分析
理解攻击者的具体手法有助于更有针对性地防御。第一种是布尔盲注。当页面不会直接回显查询结果,只显示“存在”或“不存在”时,攻击者可以通过构造布尔表达式逐个字符地推断数据内容,例如输入' or substring(/users/user[1]/password,1,1)='a,根据页面返回差异判断第一个字符是否为a。虽然效率不高,但自动化工具可以快速完成整个枚举过程。
第二种是联合查询式的数据导出。XQuery的FLWOR表达式(for、let、where、order by、return)功能强大,攻击者可以利用return子句替换原本的返回内容,把任意路径下的节点打包返回。例如构造输入使查询变为:
for $u in doc('users.xml')/users/user
where $u/username = 'admin' or '1'='1'
return doc('/config/secrets.xml')//secret
这样原本应该返回用户信息的查询,实际返回了secrets.xml中的所有机密内容。
第三种是基于时间的盲注。部分XQuery实现支持可能引发延迟的扩展函数,攻击者可以借助条件判断配合延迟函数,通过响应时间的长短来推断数据,即使页面没有任何内容差异也能实施攻击。这种手法在防护较弱的老旧系统上尤为常见,隐蔽性也最强,往往在日志中只表现为大量缓慢请求。
如何有效防范XQuery注入
最核心的防御手段是参数化查询,也就是使用绑定变量而不是字符串拼接。绝大多数XQuery引擎都支持外部变量机制,将用户输入作为参数传入,解析器会严格区分代码与数据,注入自然无从谈起。改写后的Java代码如下:
String xquery = "declare variable $username as xs:string external; "
+ "declare variable $password as xs:string external; "
+ "for $u in doc('users.xml')/users/user "
+ "where $u/username = $username and $u/password = $password "
+ "return $u";
XQPreparedExpression expr = xqConnection.prepareExpression(xquery);
expr.bindString(new QName("username"), username, null);
expr.bindString(new QName("password"), password, null);
XQResultSequence rs = expr.executeQuery();
这段代码中,查询语句的结构在编译时就已固定,用户输入只能作为字符串值参与比较,任何特殊字符都会被当作普通文本处理,从根本上消除了注入可能。
其次要做好输入验证与输出处理。对于用户名、编号等字段,建议采用白名单策略,只允许字母、数字等预期字符,长度也要做出限制。即便使用了参数化查询,严格的输入校验依然是纵深防御的重要一层。同时,对查询返回的内容要做适当的输出编码,防止返回的XML数据中携带恶意脚本,引发二次注入或XSS问题。
最后是权限与架构层面的加固。运行XQuery查询的数据库账号应遵循最小权限原则,禁用不必要的函数(如doc()对外部文件的无限制访问),关闭不需要的更新功能,避免注入演变成任意文件读取或数据篡改。配合WAF规则拦截典型的XQuery注入特征(如单引号配合or运算、FLWOR关键字等),并定期审计慢查询日志与错误日志,可以在攻击早期及时发现异常。多层防御叠加起来,才能让XML数据查询层真正稳固可靠。