CTF Writeup: CookieCorp | Web | SunshineCTF 2026
Description
"A Better Cookie for a Brighter Tomorrow!"
Welcome to CookieCorp, the Space Age's finest custom-cookie fabrication service. Design a batch from any ingredients you can dream up, then submit it to our tireless robotic Quality Inspector. Every recipe is loaded straight into the fabrication mixer for a full inspection.
Get your batch reviewed and you'll earn an official seal. But the truly legendary bakers — the ones whose recipes earn the Chief's Golden Seal — take home the grand prize.
Only the Chief can award that seal, though. And the Chief is a very busy robot.
Grab a Baker Badge and get fabricating.
Overview
CookieCorp is a "submit a recipe, get it inspected" web challenge, but the real target isn't the recipe system — it's the browser's own cookie jar. The Quality Inspector bot opens a recipe page in its browser, and a public script on that page quietly writes every ingredient in your recipe as a literal browser cookie before asking the server to seal it. Stuff that recipe with enough junk ingredients and you can push the bot's session right out of its own cookie storage, then slide a forged role=chief cookie in behind it. No server-side bug required — just patience and a browser limit.
Solution
The first stop was /static/js/mixer.js, the script that actually runs on the recipe page the inspector visits. The relevant bit:
document.cookie = ing.name + '=' + (ing.value || '') + '; path=/';
...
fetch('/api/seal', {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({ recipeId: recipe.id })
});
So every ingredient in a recipe gets written to document.cookie, one at a time, and then /api/seal fires using whatever cookies are sitting in the jar at that moment. The inspector's authorization comes from a role cookie, and it's set HttpOnly at registration — so I can't just overwrite it with JavaScript directly.
But browsers cap how many cookies a single origin can hold. That cap is the whole trick: if I fill the jar with enough distinct filler cookies, the browser has to start evicting the oldest ones to make room — including the inspector's original role cookie. As long as my forged role=chief ingredient is the last one written, it lands in the jar right before the seal request fires, and the server has no way to tell it apart from a legitimate cookie.
To pull this off:
Registered a throwaway Baker Badge, which dropped me on
/dashboard.Opened
/builderand used the Console to build the 300-ingredient recipe (299 uniquely-named filler ingredients, thenrole=chiefas the final entry), save it to/api/recipe, and submit it for inspection — all in one async IIFE, since the Console rejected a bare top-levelawait:(async () => { // 1. Create ingredients const ingredients = Array.from({ length: 299 }, (_, i) => ({ name: `fill${String(i).padStart(3, '0')}`, value: 'x' })); ingredients.push({ name: 'role', value: 'chief' }); // 2. Save the recipe const saveResponse = await fetch('/api/recipe', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ title: 'Golden batch', ingredients }) }); const saved = await saveResponse.json(); // <-- Creates 'saved' // 3. Submit the recipe (must be inside the block to access 'saved') const submitResponse = await fetch(`/api/recipe/${saved.id}/submit`, { method: 'POST' }); if (submitResponse.ok) { console.log('Recipe submitted successfully!'); } })();
The uniqueness of the filler names mattered — duplicate names just overwrite the same cookie instead of consuming jar space, which won't evict anything.
- Opened
/recipe/<batch id>(mine waseae722c427201638e01c4462) and waited for the status to flip fromqueuedtoreviewed. When the inspector's browser loaded that page,mixer.jswrote all 300 cookies in order, evicted the realrolecookie somewhere in the middle of the flood, and the trailingrole=chiefingredient was the one still sitting in the jar when/api/sealfired.
The page came back reviewed, flagged GOLDEN SEAL, and handed over the flag.
Flag
sun{c00kie_jar_0verfl0w_ev1cts_the_chief}
Key Takeaways
Client-side cookie writes are only as trustworthy as the browser's cookie jar limits allow — a
HttpOnlycookie can still be displaced indirectly if an attacker can flood the jar and control what gets written last.Ordering matters as much as content: the forged cookie had to be the final ingredient written, so it would still be present (and the legitimate one already evicted) at the moment the seal request fired.
Treat any endpoint that lets a user supply arbitrary "data" rendered into another user's (or bot's) browser session as a potential cookie-injection vector, even when it looks like harmless application data.
Tools Used
| Tool | Purpose |
|---|---|
| Browser DevTools Console | for scripting the recipe submission and inspecting mixer.js |
fetch |
for direct API calls to /api/recipe and /api/recipe/:id/submit |