Skip to content

Session identifier in the URL

CWE-598CWE-200OWASP A07:2021Updated September 4, 20265 min read

If the session identifier sits in the URL, it is written into server logs, proxy logs, browser history and bookmarks, and travels through the referrer header to external sites. One shared link is then enough to give someone else access to the account, without any password involved.

A session identifier is functionally equal to a password: whoever holds the value is the user. Yet it happens that this value sits plainly visible in the address bar, where it is then recorded in a surprising number of places. It leaks along several routes, and HTTPS does not solve the problem.

Why does a session ID not belong in the URL?

An application has to know who the user is on every request. For that, the browser sends a session identifier along, normally in a cookie. If that identifier sits in the URL, as a query parameter or as part of the path, it becomes part of the address of the page, and therefore part of everything that happens to that address.

That is the essential difference. A cookie is a piece of hidden housekeeping between browser and server: it appears nowhere on screen, is not shared and does not end up in history. A URL is designed to be visible, copyable and shareable. A secret in a URL is a secret inside a structure built to be passed on.

It amounts to writing your PIN on the outside of your bank card. It works perfectly as long as nobody looks, but you have moved the protection to exactly the place where everyone can see it.

Where does the session leak to?

The number of routes along which a URL is recorded is larger than most developers suspect.

Vulnerable:

https://portal.example/case/overview?jsessionid=A3F91C7D4B2E8056

This single URL ends up in, among other places, the access log of the web server, the logs of every intermediate proxy or load balancer, the browser history of the user, their bookmarks if they save the page, and monitoring or error reporting systems that record requests. If the page also contains an external resource or a link to the outside, the following travels to that third party:

GET /analytics.js HTTP/1.1
Host: statistics.example
Referer: https://portal.example/case/overview?jsessionid=A3F91C7D4B2E8056

This user’s session now sits in the log files of a party that has nothing to do with it. And the most everyday scenario requires no attacker at all: the user copies the link from the address bar and sends it to a colleague, to show them “this page for a second”. That colleague thereby opens the sender’s account.

Safe:

HTTP/1.1 200 OK
Set-Cookie: sid=A3F91C7D4B2E8056; HttpOnly; Secure; SameSite=Strict; Path=/
https://portal.example/case/overview

The identifier now sits in a cookie and disappears from the address. The URL can be shared, saved and logged without concern. HttpOnly additionally keeps the value out of reach of JavaScript, Secure prevents transmission over an unencrypted connection, and SameSite=Strict ensures the cookie does not travel with requests originating from a foreign site.

With a framework that offers this behaviour by default, switch it off explicitly:

<!-- Java Servlet: cookies only, never URL rewriting -->
<session-config>
  <tracking-mode>COOKIE</tracking-mode>
  <cookie-config>
    <http-only>true</http-only>
    <secure>true</secure>
  </cookie-config>
</session-config>
This applies equally to other secrets in the URL: password reset tokens, invitation codes, API keys and one-time download links. Anything that grants access and sits in an address leaks along the same routes. A strict Referrer-Policy limits one of those routes but does not solve the underlying problem.

What is the impact of a session ID in the URL?

The severity runs from medium to high, depending on how easily a third party can reach the URL. Unlike many other session problems, no attacker with a network position or an injection is needed here: the most likely cause is a user forwarding a link themselves.

If someone does obtain a valid session identifier, the takeover is complete. To the application, the recipient is indistinguishable from the rightful user: they have the same rights, see the same data and can perform the same actions. No password is involved and two-factor authentication is not requested again, because authentication has already taken place.

There is also a side that is hard to remedy. Session identifiers that have ended up in log files often sit there for years, spread across systems with broader access than the application itself: administrators, suppliers and analysis platforms. Even after the vulnerability is fixed, those traces remain, and they should be treated with the same care as stored passwords.

How do you detect a session ID in the URL?

While walking through the application, a tester watches for any parameter that looks like an identifier: names such as sid, sessionid, jsessionid, phpsessid, token or auth in the query or in the path. The clearest signal is a session value that appears in the URL on login and then keeps travelling along.

They then check whether the value really grants access, by opening the same URL in a clean browser without cookies. If that works, the session is fully contained in the URL. They also look for frameworks that fall back on URL rewriting automatically when cookies are absent, behaviour that is sometimes only visible if you disable cookies. Error pages, PDF exports and emails get attention too, because full URLs regularly end up there. AssistSec assesses the referrer configuration and the presence of external resources alongside this, because together they determine how far a leaked identifier actually travels.

How do you prevent a session ID in the URL?

  • Carry session identifiers in cookies only, and explicitly disable URL rewriting in your framework.
  • Set HttpOnly, Secure and SameSite on the session cookie.
  • Never place tokens, reset codes or API keys in a query parameter or in the path.
  • Use a dedicated, explicit sharing function with its own limited token for sharing content.
  • Set Referrer-Policy: strict-origin-when-cross-origin or stricter as an additional layer.
  • Filter session identifiers out of log files and monitoring data, or log only the path without the query.
  • Revoke all existing sessions when you fix this, because earlier identifiers must count as leaked.
  • Renew the session identifier on login, so a previously shared value grants no rights.

Sources

Frequently asked questions

Is it safe if the URL only travels over HTTPS?

No. HTTPS protects the URL in transit, but not in the places it subsequently lands: the web server access log, browser history, bookmarks, a shared screenshot and the referrer header to external resources. Encryption solves none of those routes.

Why did applications ever do this?

As a fallback for visitors who had disabled cookies, which was still common around the turn of the century. Many frameworks could do it automatically and that behaviour sometimes survives in old configurations. There is no legitimate reason for it today; cookies are universally available.

Does this apply to API tokens in the URL too?

Yes, and the problem is identical. An access token in a query parameter ends up in the same log files and history. Always send tokens in an Authorization header or a cookie, never as part of the address.

How do I recover if this has already happened?

Revoke all existing sessions, so leaked identifiers become worthless. Then disable the behaviour, and assess whether there are log files containing old session identifiers; those must be treated with the same care as passwords and cleaned up where possible.

Related articles

Press / to search · Esc