Axe:ploit
← Back to posts

Session IDs in Cookies: The Default That Only Stays Safe If You Ignore the URL

By Jason Miller

On most modern stacks, the session identifier lives in an HTTP cookie by default. That is a reasonable default. A cookie can be marked HttpOnly so JavaScript cannot read it, Secure so it only goes over HTTPS, and SameSite so simple cross-site requests do not attach it blindly. None of that makes you bulletproof, but it is a sane baseline.

The catch is in the second half of the sentence you sometimes see in older docs: security holds only if your session system never treats the session id as valid when it arrives in GET or POST.

If your app accepts ?sessionid=... or a hidden form field as an alternate carrier, you just reopened doors the cookie was meant to keep shut.

Why the cookie is the "moderate" option

Cookies are not magic. They are just a channel the browser sends back on matching requests. The moderate part is real: you can tighten the channel, you can rotate the id after login, and you can keep the id out of places humans copy and paste.

Compare that to putting the session id in the query string. URLs leak constantly: referer headers, browser history, server logs, analytics tools, screenshots, Slack messages. Once the id is in the URL, you are betting that none of those sinks will ever expose a live session. That is a bad bet.

So frameworks moved toward cookies as the default transport. Good. The mistake is stopping there while still allowing the same id through other inputs "for compatibility" or because one endpoint forgot to migrate.

What goes wrong when GET or POST still count

Session fixation is the classic classroom example. An attacker obtains or sets a session id the victim will use, then waits for the victim to authenticate so that now-authenticated session is the one the attacker already knows. Transport and acceptance rules matter here. If the id can be planted via a link (?sid=attacker_chosen), you have made fixation much easier than it needs to be.

Leaks and replay show up in quieter ways. A user shares a "help me debug this" URL. Your logs store the full query string. A misconfigured proxy logs request bodies. Any path where the id leaves the cookie header is a path where "moderate" security assumptions stop applying.

CSRF and cross-site behavior interact too. Cookies participate in CSRF defenses and request models in specific ways. When you add parallel ways to present the same secret, you increase the chance that some code path treats a request as authenticated when you did not mean to. Complexity is the enemy.

What "disregard GET/POST" means in practice

It is not enough that your happy path uses cookies. Your session middleware or your framework configuration must reject or ignore session identifiers from query parameters and form bodies for session establishment, unless you have a very narrow, audited exception (most apps do not).

Concretely, check these when you ship:

  • Framework defaults. Some stacks allow a session key in the URL for debugging or legacy apps. Turn that off in production.
  • Reverse proxies and gateways. Ensure nothing strips cookies and then injects the session into query strings for "compatibility."
  • Your own redirects. Never append the session id to URLs when you send someone to checkout, OAuth callbacks, or downloads.
  • Logout and rotation. Invalidate server-side state on logout; rotate the session id on privilege changes (especially after login). That limits the damage when a id does leak.

Cookies give you a controllable place to store the session handle. GET and POST scatter it across surfaces you do not control.

The honest bottom line

A cookie-backed session is a solid default for web apps, not because cookies are unbreakable, but because they keep the identifier out of the worst leakage channels if you do not duplicate the same trust model in the URL or the body.

If your session id is valid in more than one shape, you are not really using "cookie sessions." You are using "session id wherever the client felt like putting it," and that is a weaker system than the first line of your framework docs promised.

Tighten the channel, rotate on login, and refuse alternate carriers unless you can name exactly why you need them. Your future incident postmortem will be shorter.

---

Sign up for automated security testing built for builders who move fast: https://panel.axeploit.com/signup

Integrate Axe:ploit into your workflow today!

Session IDs in Cookies: The Default That Only Stays Safe If You Ignore the URL