Skip to content

Cookie Monster Secret Recipe

Platform: PicoCTF Category: Web Exploitation Environment: Standalone

Goal: a launched web instance fronted by a login form that turns every credential away. The flag never appears on a page, it is in a cookie the exchange leaves behind in the browser.

The form takes a username and a password and turns down whatever pair it is given, and the refusal is the delivery rather than a dead end. The page that comes back stays in character: Cookie Monster needs no password and only wants cookies, and the line beneath it asks whether you have checked yours lately. Nothing was set while the login page was on screen, so there is nothing to find until the form has been refused once. After that the Cookie Editor extension lists one cookie for the site, called secret_recipe.

secret_recipe
cGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzX0U2MzRERkJCfQ%3D%3D

The tail gives it away before the body does. % is not a character in the base64 alphabet, so the six characters closing this value are not part of the encoding they sit on the end of. %3D is percent-encoded =, which means two wrappers are stacked here and the outer one is URL encoding. Undo that and the value ends in the == that base64 pads with. The rest decodes to text.

Flag picoCTF{c00k1e_m0nster_l0ves_c00kies_E634DFBB}

The extension was convenient rather than necessary, since a browser’s own developer tools list the same cookie with nothing installed. The eight characters closing the flag are not the same on every copy of this challenge.

A cookie is state the server sets and the browser stores, and the browser hands it back on every request to that site, so whoever holds the browser can always read it. Neither wrapper on this value was concealing anything, and the outer one was not even required: RFC 6265 permits = inside a cookie value, and the same section tells servers to encode arbitrary data before putting it in one, naming base64 as its example.

The arithmetic accounts for the tail exactly. The flag is 46 bytes, which is 15 whole groups of three with one byte left over, and that remainder is what puts two = on the end of the encoding, which is what the server escaped into %3D%3D. Those two characters carry no information, because the 62 in front of them already determine all 46 bytes. It is why a decoder handed the value raw can print the whole flag and still call the input invalid: every byte has been flushed before it ever reaches the first %.