MediumIDOR· 2 min read

Guessable Tokens on Private Uploads

The image URL looked random. Part of it wasn't — and that part was enough to walk into other people's uploads.

Target anonymized · an online photo-printing service

A short one to finish, and a common one: a media endpoint that leaned entirely on its URLs looking unguessable, when part of them wasn't.

The spark

Images were served from a multi-segment path. Most of it looked random — but one segment was a plain, incrementing number. A single predictable component in an otherwise "random" identifier is usually the whole ballgame.

Digging in

I ran the gate: a fabricated path returned an error, so the endpoint genuinely resolved the identifier rather than ignoring it. Then I took a real path and nudged the sequential segment to a neighbouring value. Back came a different user's uploaded image — no login, no ownership check.

The other segments were window dressing. The security of the whole URL collapsed to the one part an attacker could count.

The exploit

GET /i/{fixed}/{fixed}/{sequential±1}/image HTTP/2
Host: img.example.com
# no auth — returns another user's upload

Iterate the predictable segment and you scrape strangers' private uploads.

Impact

Unauthorised disclosure of private, user-uploaded images — the personal photos people trusted the service to keep to themselves.

The fix

Require authorization on media access, and make resource references fully unguessable and access-controlled — the unguessability is a convenience, the ownership check is the control.

idorbroken-access-controlprivacy

All identifiers, targets and payloads in this post are anonymized or defanged. Findings were reported and resolved through responsible disclosure.