Unblur Me
Unblur Me
Platform: Broncoctf2026 | Category: Web | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-07-11 | Status: Solved Techniques: client_side_gate_bypass, css_blur_bypass, direct_endpoint_access, page_source_analysis
Summary
Task: Flask web page hides a flag inside a CSS-blurred image, gated behind solving 500 calculus problems tracked only in client-side JavaScript. Solution: read the page source to find the unauthenticated /api/v1/internal/fetch-config-blob endpoint that serves the original unblurred PNG on page load, then curl it directly to read the flag from the image.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
broncoctf2026| ID:20260711_broncoctf2026_unblur_me - Tags: flask, source_code_analysis, werkzeug, web, client_side_access_control, direct_object_access, css_blur_bypass, hidden_api_endpoint, image_flag
- Indicators: blurred flag image gated by client-side JS counter, filter: blur(20px) applied via CSS, fetch() loads secret resource on page load before any check, internal API path /api/v1/internal/fetch-config-blob in source, Werkzeug/Flask server
- Source:
20260711_broncoctf2026_unblur_me.md
Foothold
Vulnerability / Misconfiguration
- Client_side_gate_bypass
- Css_blur_bypass
- Direct_endpoint_access
- Page_source_analysis
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- client_side_gate_bypass
- css_blur_bypass
- direct_endpoint_access
- page_source_analysis
- Tags: flask, source_code_analysis, werkzeug, web, client_side_access_control, direct_object_access, css_blur_bypass, hidden_api_endpoint, image_flag
Original Writeup
<details><summary>Click to expand original content</summary>Unblur Me — broncoctf2026
Description
My friend tried to motivate me to review my derivatives by telling that me that I can unlock a top-secret image after I solve 500 challenges on this website. Unfortunately for her, I'm a firm believer in work smarter not harder, so I wonder if there's a way I can get the flag without actually doing any math?
A Flask web app ("Calculus Review") shows a blurred flag image and asks you to solve 500 derivative problems to "unblur" it. The goal is to read the flag without solving 500 math problems. The description's "work smarter not harder" is a direct hint that the gate is fake.
Analysis
Recon of https://broncoctf-unblur-me.chals.io/:
Server: Werkzeug/3.1.8 Python/3.14.6 (Flask)
The single HTML page contains all the logic inline. Three things stand out:
- The blur is pure CSS, not a server-side render:
#flag-image { filter: blur(20px); ... }
Removing this one CSS property reveals the original image.
-
The 500-problem gate lives entirely in client-side JavaScript —
checkAnswer()increments a localcorrectCountand only callsflag.style.filter = "none"whencorrectCount >= 500. There is no server-side verification of progress. -
The secret image is fetched immediately on page load, before any question is answered, from an internal API endpoint:
function loadSecretImage() {
fetch('/api/v1/internal/fetch-config-blob')
.then(r => r.blob())
.then(blob => { img.src = URL.createObjectURL(blob); });
}
loadSecretImage(); // runs at load time
The endpoint /api/v1/internal/fetch-config-blob is unauthenticated and returns the original (unblurred) PNG regardless of progress. The only thing the browser does afterwards is optionally blur it via CSS. So the entire access control is client-side theater — direct object access defeats it.
Solution
Step 1 — grab the page and read the source:
curl -sk https://broncoctf-unblur-me.chals.io/
This reveals the internal endpoint /api/v1/internal/fetch-config-blob in the inline <script>.
Step 2 — hit the endpoint directly to download the unblurred image:
curl -sk -o secret.png https://broncoctf-unblur-me.chals.io/api/v1/internal/fetch-config-blob
The response is application/octet-stream, ~325477 bytes, a valid PNG.
Step 3 — open secret.png. The flag is rendered as text inside the image:
BRONCO{REDACTED}
Normalize the wrapper to the required lowercase bronco{...} format.
(Alternative, fully in the browser: open DevTools and either delete the filter: blur(20px) CSS rule on #flag-image, or paste document.getElementById('flag-image').style.filter='none' in the console — no math required.)
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR