The Private Album With No Lock on the Door
Every customised photo project got its own URL — and that URL was the only thing standing between a stranger and your family photos.
Target anonymized · a print-on-demand photo service
The target was a print-on-demand photo service — the kind of product where people upload the most personal images they own: kids, weddings, passports they mean to print for a visa. When someone customises a product, the app spins up a "project" at its own URL and quietly trusts that URL to be secret.
The spark
I made a project, uploaded a test photo, and copied the link out of the address bar. Then, out of habit, I pasted it into a fresh incognito window — no cookies, no login. It loaded. My photo, my project name, right there.
Digging in
My first thought was the boring one: maybe it just renders a public preview and the real data is gated. So I tried to edit the project from that logged-out window. It let me. That killed the "harmless preview" theory.
The second objection was better: the project ID was a long, unguessable-looking token, so even without an auth check, could an attacker actually find someone else's? That felt like a dead end until I stopped trying to guess IDs and started looking for leaked ones — and they were everywhere. Search-engine dorks, archive snapshots, links shared in support threads. The IDs didn't need guessing; the internet was handing them out.
The whole vulnerability was a category error: the team treated an identifier as if it were a secret.
The exploit
There was nothing to weaponise — that was the point. Take any leaked project URL, open it in a clean browser, and you're looking at (and editing) a stranger's private upload:
GET /project/{leaked-project-id} HTTP/2
Host: app.example.com
# no Cookie header, no Authorization — and it still returns the project
Impact
Anyone who came across a project link — deliberately or by scraping archives — could view and modify other people's private photos and order details. For a product built entirely on people trusting it with personal images, that's about as bad as the impact gets short of payment data.
The fix
Require a session, and check on the server that the session owns the project before returning or mutating anything. The unguessable ID is fine as a convenience; it just can't be the only thing in the way.
All identifiers, targets and payloads in this post are anonymized or defanged. Findings were reported and resolved through responsible disclosure.