All case studies

An administrator token in the URL, archived by a crawler and still valid

An unauthenticated user ended up with administrator access. The application carried the security token inside the URL, tokens never expired, and one of those addresses had already been archived by a public crawler.

The engagement

Two bad practices that are tolerated on their own and open the door together: the token travels in the URL, and the token never expires.

From a parameter in the address to an administrator account open to the internet

The first thing the review surfaced was not a coding mistake but a design decision: every request to the API carried the security token inside the address itself, as just another parameter. A call looked like this: http://api.example.com/me?token=<your_token>. The token is the key to the session, and it was travelling in plain sight of anyone able to read that address.

A token in the URL does not stay in the URL. It ends up written into server logs, saved in browser history and indexed by search engines, and it leaks from any of those three places. The CWE catalogue records the class as CWE-598, "Use of HTTP Request With Sensitive Query String". The OWASP Top 10:2025 puts it under A07, "Authentication Failures", among the conditions that make an application vulnerable: one that "exposes session identifier in the URL, a hidden field, or another insecure location that is accessible to the client".

The second finding was that those tokens never expired. Once issued, a token was good forever: no time limit, no invalidation after inactivity. On its own the defect opens no doors, which is why it tends to be parked behind more urgent work; it is CWE-613, "Insufficient Session Expiration", and the same OWASP category takes in applications that do not correctly invalidate sessions and authentication tokens on logout or after a period of inactivity, a line OWASP writes with single sign-on tokens mainly in mind. Combined with the first finding it stops being a detail, because it turns any old copy of an address into a credential that still works.

To find out whether this was theory or had already happened, the team watched the publicly reachable parts of the internet where an address of that kind could have been recorded. That is where the proof turned up: an API address with a real user’s token inside it, of the form http://api.example.com/analytics?token=<token>, already crawled and stored by a third-party web archive, the sort that keeps historical copies of public pages.

The token was tested against the application. It was still valid, it belonged to a real customer, and it carried administrator privileges. Put another way: anyone on the internet who reached that archived copy walked in as an administrator, without authenticating at any point and without typing a password.

RECONSTRUCTION · NO CLIENT DATA

How a token in the address became an administrator account

  • CWE-598
  • CWE-613
  1. The design decision

    GET /me?token=EXAMPLE-TOKEN-A1B2C3D4 HTTP/1.1
    Host: api.example.com
    Accept: application/json

    The session key, written into the request line.

    Every call carried it. Not a coding mistake: a design decision.

  2. A URL does not stay in the URL

    • SERVER LOG "GET /me?token=EXAMPLE-TOKEN-A1B2C3D4 ..."
    • BROWSER HISTORY api.example.com/me?token=EXAMPLE-TOKEN-A1B2C3D4
    • PUBLIC CRAWLER api.example.com/analytics?token=EXAMPLE-TOKEN-A1B2C3D4

    It leaks from any of the three. The third one is outside the client's control.

  3. The archived copy

    THIRD-PARTY WEB ARCHIVE

    http://api.example.com/analytics?token=EXAMPLE-TOKEN-A1B2C3D4

    A historical copy of a public page. It cannot be withdrawn at will.

  4. Replayed

    GET /analytics?token=EXAMPLE-TOKEN-A1B2C3D4 HTTP/1.1
    Host: api.example.com
    Accept: application/json
    issued

    NO EXPIRY · NO INACTIVITY TIMEOUT (CWE-613)

    Time does not close this door.

Still valid. Real customer. Administrator.

No exploitation technique, no tooling. Opening an address that was already published was enough.

What we recommended

GET /me?token=EXAMPLE-TOKEN-A1B2C3D4 HTTP/1.1

GET /me HTTP/1.1
Host: api.example.com
Authorization: Bearer EXAMPLE-TOKEN-N7P4Q2R8

in the header it is neither logged nor indexed

Tokens get an expiry and a revocation path. Every token that has ever travelled inside a URL is treated as compromised and invalidated.

Reconstruction of the finding, not a screenshot. The token travels as a query parameter (CWE-598), so it is written into logs, history and public archives; because it never expires (CWE-613), an archived copy stays a working administrator credential. Hosts and tokens shown are fictitious.

With that access, an attacker could add, edit or delete users of the system; read analytics and sensitive business data; insert malicious or fraudulent data; and read the users’ activity logs. No exploitation technique and no tooling were required: opening an address that was already published was enough.

The root cause is two decisions, and both are fixable: passing the token as a GET parameter, and giving it no expiry. What we recommended was to take the token out of the address and move it to the request’s authorisation header, where it is neither logged nor indexed; to give tokens an expiry and a revocation path; and, since a copy archived by a third party cannot be withdrawn at will, to treat as compromised and invalidate every token that has already travelled inside a URL.

Your company could be next.

Talk to a senior consultant: the person who runs the test, from the first call. One conversation to see whether we are a fit and what an engagement would look like.