When you log in, the shop hands you a session cookie. It looks like a proper signed token: a payload and a signature, split by a dot. The trouble is the shop only ever reads the payload. It never checks the signature. So you can rewrite the payload, leave any old signature on the end, and the shop believes you. This is an unverified token, and it lets you become anyone.
New to this? What is a signed session token?
A session token is how a site remembers who you are between requests. A good one is signed: the server attaches a signature it can check later, so a tampered token is rejected.
The bug here is that the shop generates a signature but never verifies it on the way back in. A token with a valid-looking shape but a wrong signature sails straight through, which is as good as no signature at all.
First, log in with a grey-box account
(alice@example.com / Password123!). Then open your browser’s
developer tools: right-click anywhere on the page and choose Inspect,
or press F12 on Windows / Cmd+Option+I on a Mac.
1 · Find it
In dev tools, open the Application tab (Chrome or Edge) or the
Storage tab (Firefox), expand Cookies in the left-hand list, and
click http://localhost:3000. You will see a cookie named ff_session. Its
value is two chunks separated by a dot, something like eyJ1aWQi... then
. then a longer string.
That first chunk is base64url: an everyday way of packing text so it is safe to sit in a cookie. It is not encryption, so anyone can read it. To decode it, the simplest tool that works on any computer is CyberChef, a free website: paste the chunk into the input box and drag a From Base64 block into the recipe. (On a Mac or Linux terminal you could instead run the line below.)
echo 'eyJ1aWQiOjQsImVtYWlsIjoiYWxpY2VAZXhhbXBsZS5jb20ifQ' | base64 -d
Either way, out comes the payload: {"uid":4,"email":"alice@example.com"}. Your
identity is sitting in the cookie as a plain number, uid. Alice is user 4.
2 · Exploit it
The shop owner (the admin) is user 6. So make the return journey: take the text
{"uid":6} and encode it back into base64url. In CyberChef, drag in
a To Base64 block and set its alphabet to the URL-safe one
(A-Za-z0-9-_). On a Mac or Linux terminal:
printf '{"uid":6}' | base64 | tr '+/' '-_' | tr -d '='
That gives you eyJ1aWQiOjZ9. A real token has a signature after the dot, but the shop
never checks it, so any text works. Your forged cookie, in full, is this exact line:
eyJ1aWQiOjZ9.anything
Now set it. Back in dev tools under Cookies, double-click the value of
ff_session, delete what is there, paste the line above, and press Enter.
Reload the page, then visit http://localhost:3000/admin.
You are in the staff back office, without ever knowing the admin’s password.
Why it matters: a forgeable session token is total account takeover for every user at once. Change the number, be anyone.
3 · Understand it
The shop splits the token, decodes the payload, and trusts the id. The signature is never looked at:
const [payload] = token.split('.'); // signature ignored
const { uid } = JSON.parse(base64decode(payload));
req.user = loadUser(uid); // trusts a number you control 4 · Fix it
Recompute the signature over the payload and reject the token if it does not match, before trusting anything inside it:
const [payload] = token.split('.');
const { uid } = decode(payload); const [payload, sig] = token.split('.');
const good = hmac(payload, KEY);
if (sig !== good) throw new Error('bad token');
const { uid } = decode(payload); Use a strong, secret key, compare in constant time, and prefer a vetted library (a real JWT library, or signed cookies) over a hand-rolled token.
The general rule: never trust a value the client can edit. A token is only as good as the check you run on it every single request.