Technique · sanitized

Cookie Value Manipulation

Here is the one sentence the whole thing rests on: a cookie is data your server sent to a computer you do not control, and it will hand you back exactly what it decides to. Every cookie is a round trip through the user’s browser, and the browser is a place where the value can be read, decoded, changed, and sent back looking untouched. If your application trusts what comes back, you have moved a security decision onto the attacker’s machine.

This is one of the first things worth learning well, because it is everywhere and the fix is conceptual, not a library you bolt on.

Sanitized · lab-safe

This is a technique explainer, not a target write-up. There is no live system described anywhere on this page — every cookie, value and role string below is a generic, lab-safe illustration of a class of flaw, published so you can learn to build against it. No flag appears here.

The habit it teaches is defensive: when you ship something, ask of every value the client hands back, “what happens if they changed this?” If the honest answer is “something bad,” the value belongs on the server.

The two piles · Opaque handles vs meaningful values

Which parts are dangerous

Open DevTools → Application → Cookies on any site and you will see rows like these:

CookieValueWhat it is
session9f2a1c…opaque, long, random
themedarkmeaningful
cart%7B%22id%22%3A42%7Dmeaningful (URL-encoded JSON)
roleusermeaningful — carries its own authority

Sort them into two piles:

  • Opaque handles. session=9f2a1c... is a pointer. It means nothing on its own; the server looks it up in its own store to find out who you are. Changing it just points at nothing (or, if you can guess a valid one, at someone else — a different bug, session hijacking). This pile is mostly fine, if the values are long and random.
  • Meaningful values. role=user, cart={"id":42}, theme=dark, isPremium=false. These carry their own meaning. And this is the dangerous pile, because the meaning is sitting on the client, in plaintext or something you can trivially decode, waiting to be edited.

The whole technique is one line: find a meaningful value the server trusts, change it, and see if the server believes you.

Section 2

The move, step by step

Read · decode · change · re-encode · send

Five steps · The skill is recognising the load-bearing value

Read it, change it, send it back

  1. Read it. In DevTools, or a proxy like Burp / mitmproxy, capture the Cookie: header.
  2. Decode it. Meaningful values are rarely raw. Common wrappers, in order of how often you see them:
    • URL-encoding — %7B%22id%22%3A42%7D is just {"id":42}.
    • Base64 — eyJyb2xlIjoidXNlciJ9 decodes to {"role":"user"}. (A string ending in = or made of A–Za–z0–9+/ is your tell.)
    • JSON, nested inside either of the above.
  3. Change it. {"role":"user"}{"role":"admin"}. isPremium=falsetrue. cart={"id":42,"price":9.99}"price":0.01. user_id=10071008 (that last one is a cookie-flavoured IDOR — the account you see is the number in the cookie).
  4. Re-encode it the same way you found it — re-JSON, re-base64, re-URL-encode — so the shape matches what the server expects.
  5. Send it back and watch the response. If the app now thinks you are an admin, or the cart totals a penny, the server trusted a value the client controls. That is the finding.

The findingThe server made an authorization or pricing decision from a value the client can freely edit.

Tooling is deliberately boring: browser DevTools to edit a value in place, an extension like Cookie-Editor, or a proxy to intercept and rewrite on the fly. The skill is not the tool; it is recognising which value is load-bearing.

Section 3

Why it works

The trust boundary, drawn wrong

The architectural mistake · The user’s pocket is not a safe

State the server let the client hold

Every one of these bugs is the same architectural mistake: the server made a decision based on state it let the client hold. role=admin in a cookie is the server saying “I’ll just keep the answer to ‘is this person an admin’ in the user’s pocket and read it back later.” The user’s pocket is not a safe.

SameSite, Secure and HttpOnly do not save you here. Those flags stop other sites and scripts from stealing the cookie; they do nothing to stop the legitimate user from editing their own cookie in their own browser. HttpOnly on role=admin protects the value from XSS and still lets the account’s owner set it to admin by hand.

FlagWhat it actually protects againstDoes NOT stop
HttpOnlya script (XSS) reading the cookie valuethe owner editing their own cookie
Secureinterception of the cookie over plaintext HTTPthe owner editing their own cookie
SameSiteanother site sending the cookie for you (CSRF)the owner editing their own cookie

Read the table down its last column: every flag defends against a third party. None of them defends against the person holding the cookie, and the person holding the cookie is exactly who edits role=admin.

Section 4

How to build it so this never happens

One decision kills the entire class

Recommendations · Keep meaning on the server

Build against it

  • Keep meaning on the server. Put only a random handle in the cookie. session=<128 bits of randomness> and look up the role, the cart, the price, the entitlements in your own store. The client holds a ticket stub, not the ticket’s contents. This single decision kills the entire class.
  • If a value MUST live client-side, sign it. A signed cookie or a JWT carries an HMAC / signature the server verifies, so a tampered value is rejected. But signing has its own footguns — the alg:none JWT bug (a token that says “I’m not signed, trust me”) is a whole family of failures, and a signed price is still a price you should have looked up server-side anyway. Sign integrity-critical values you truly cannot avoid shipping; do not use signing as an excuse to ship trust-bearing data to the client.
  • Never authorize off a client value. “Is this user an admin?” is answered by the server’s record of who they are, every request, not by a field they can type.
  • Set HttpOnly, Secure, SameSite anyway — for the other threats (XSS theft, CSRF, transport). Just do not mistake them for a defense against the owner editing their own value.

Section 5

Why we care, at ECHO

The line between “I found a bug” and “I understand the system”

Takeaway · Ask it of every value the client sends back

A finding, and a defensive habit

This is on the short list of things that separate “I found a bug” from “I understand the system.” Cookie tampering shows up in nearly every intro web CTF (BrunnerCTF’s CakeSearch was a cousin of it — a forged role claim in a client-minted token), and it maps one-to-one onto a real defensive habit: when you build something, ask of every value the client sends back, “what happens if they changed this?” If the honest answer is “something bad,” the value belongs on the server.

A lab to practice on, safely, is a good follow-up — a tiny Flask app that trusts role= in a cookie, so you can break it and then fix it. Ask in the club channel if you want the exercise.

ECHO · Technique write-ups · Web fundamentals

Sanitized technique explainer by Digital Orukami · lab-safe, no live target · educational use only.