WaF - Web Challenge Writeup
Challenge: WaF
Category: Web
Challenge Description
You can’t get the /flag.txt ever.
Link: http://45.56.66.96:7789/
Hint from Discord:
- Author: “no need for any parameters”
Initial Reconnaissance
Accessing the Web Application
When we visit the main page, we see a simple HTML form:
<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>Bee</title></head><body> <p>Input your name:</p> <form action="/huraaaaa.html" method="GET"> <input a="{a}" type="text" required> <button type="submit">Submit</button> </form>
<!-- @app.after_request def index(filename: str = "index.html"): if ".." in filename or "%" in filename: return "No no not like that :(" --></body></html>Key Observations
- HTML Comment Leak: The source code reveals a Flask
@app.after_requestfunction that filters requests - WAF Rules: The application blocks any filename containing
".."or"%" - Interesting Response: Direct access to
/flag.txtreturns"Something wrong!!"
Testing Basic Paths
# Direct flag accesscurl http://45.56.66.96:7789/flag.txt# Returns: Something wrong!!
# Known working endpointscurl http://45.56.66.96:7789/index.html # Workscurl http://45.56.66.96:7789/hello.html # WorksAnalysis
WAF Bypass Research
The challenge title “WaF” suggests we need to bypass a Web Application Firewall. The HTML comment reveals:
if ".." in filename or "%" in filename: return "No no not like that :("This means:
- Path traversal with
..is blocked - URL encoding with
%is blocked - The filter checks the literal strings
".."and"%"
The Hint
The author’s Discord hint was crucial: “no need for any parameters”
This suggests:
- We shouldn’t use query parameters like
?file=flag.txt - The bypass must be in the URL path itself
- We need to find a way to traverse directories without using
..or%
The Breakthrough
Curly Brace Expansion Bypass
In some web servers and frameworks (like Flask with certain configurations), curly braces {} can be used for glob pattern matching or path expansion:
{.}can expand to.(single dot){.}{.}expands to..(two dots) without containing the literal string ”..”
This bypasses the WAF because:
- The string
"{.}{.}"doesn’t contain".." - The server processes it AFTER the WAF check
- The expansion happens at the filesystem level
Path Traversal Without ”..”
To reach the root directory and access flag.txt, we can use:
/{.}{.}/{.}{.}/flag.txtThis expands to:
/../../flag.txtBut crucially, the WAF only sees {.}{.} which doesn’t match its filter!
Exploitation
Final Payload
curl http://45.56.66.96:7789/{.}{.}/{.}{.}/flag.txtResponse
KCTF{7fdbbcd6c3cee0ae65c5ca327c14a25f6e473d1c}Technical Deep Dive
Why This Works
- Flask Static File Handling: Flask’s
send_file()or similar functions process the path - Glob Pattern Expansion: The
{.}pattern is expanded by the underlying filesystem or glob library - Order of Operations:
Request → WAF Check (sees {.}{.}) → Path Expansion (becomes ..) → File Access
Alternative Bypass Attempts (Failed)
We tried many other techniques that didn’t work:
-
URL Encoding Variations:
%2e%2e(blocked by%filter)- Double encoding
%252e(still contains%)
-
Unicode Normalization:
- Fullwidth dot
.(U+FF0E) - One dot leader
․(U+2024) - These didn’t expand properly
- Fullwidth dot
-
Path Variations:
/./flag.txt(no traversal)//flag.txt(no traversal)/proc/self/cwd/flag.txt(incorrect path)
-
HTTP Header Tricks:
X-Original-URLX-Rewrite-URL- Different HTTP methods (POST, PUT, HEAD)
-
Query Parameters:
- Author explicitly said “no need for any parameters”
- All attempts with
?file=,?path=, etc. returned errors
Key Takeaways
- Read Error Messages Carefully: The HTML comment was a huge hint
- Listen to Hints: “no need for any parameters” was the key insight
- WAF Bypass Creativity: Sometimes the bypass is about character expansion/interpretation
- Order of Operations: Understanding when filters apply vs when expansion happens
- Glob Patterns:
{.}expansion is a lesser-known technique for bypassing string-based filters
Flag
KCTF{7fdbbcd6c3cee0ae65c5ca327c14a25f6e473d1c}References
Author: MR. Umair
Date: January 21, 2026
Competition: KnightCTF 2026