Authorization disclaimer. This walkthrough is exclusively for the intentionally vulnerable PortSwigger Web Security Academy lab linked below. Only test systems you own or are explicitly authorized to assess.

Overview

The account endpoint uses GUID-like identifiers rather than predictable usernames or integers. That makes guessing harder, but the application publishes Carlos’s identifier in an author link. Once discovered, the GUID can be supplied to the unprotected account endpoint to retrieve his synthetic API key.

The lab replaces usernames with GUID identifiers but still requires discovery of the GUID for Carlos to retrieve and submit his API key.
The lab replaces usernames with GUID identifiers but still requires discovery of the GUID for Carlos to retrieve and submit his API key.

Lab objective/setup

Sign in as wiener:peter, discover Carlos’s user identifier, access his account record, and submit the synthetic API key.

What I observed

The authenticated account URL contained a long identifier, preventing simple enumeration. Elsewhere on the site, blog posts linked authors through /blogs?userId=<GUID>. Finding a post by Carlos therefore disclosed the exact identifier needed by /my-account?id=<GUID>.

The normal account request demonstrates the GUID-shaped selector in `/my-account?id=[wiener-guid]`.
The normal account request demonstrates the GUID-shaped selector in `/my-account?id=[wiener-guid]`.
A public post identifies `carlos` as its author, providing a link whose destination contains his user identifier.
A public post identifies `carlos` as its author, providing a link whose destination contains his user identifier.
Following the Carlos author link exposes his GUID in `/blogs?userId=[carlos-guid]`.
Following the Carlos author link exposes his GUID in `/blogs?userId=[carlos-guid]`.

Why it is vulnerable

Identifiers answer which object is requested, not whether the requester may access it. GUID entropy can reduce blind guessing but cannot survive disclosure through links, logs, analytics, or other application features. The account handler must compare the requested object with the authenticated principal’s permissions.

Walkthrough in the authorized PortSwigger lab

  1. Log in as wiener and note the GUID-based account URL.
  2. Browse the blog and locate a post authored by carlos.
  3. Copy Carlos’s GUID from the author link.
  4. Supply it as the id value on /my-account and retrieve the synthetic API key.
  5. Submit the key through the lab interface.
Using the disclosed GUID as the `/my-account?id=` value loads the Carlos account and reveals its API key.
Using the disclosed GUID as the `/my-account?id=` value loads the Carlos account and reveals its API key.
Submitting the key obtained through Carlos's leaked GUID marks the unpredictable-user-ID lab solved.
Submitting the key obtained through Carlos's leaked GUID marks the unpredictable-user-ID lab solved.

Verification

Carlos’s identifier is obtained from a public blog relationship rather than guessed. Using that value on the account endpoint returns his protected data, and the Academy accepts the exposed key.

Impact and defensive lesson

Real-world variants expose personal records, invoices, tokens, or tenant data whenever an identifier leaks. Treat opaque IDs as defense in depth only; enforce object-level authorization for every read and write, regardless of identifier format.

References