The QR Code That Fed Me Other People's Photos
A convenience feature quietly pointed its trust arrow the wrong way — and a single scan could route a stranger's private photos straight into my account.
Target anonymized · a print-on-demand retail service
The target was a print-on-demand retail service — the kind of site where you drag in a photo, pick a poster or a sticker sheet, and a warehouse somewhere spits out a physical object a few days later. I was poking at the project editor, the part of the app where your uploads live before you commit to printing them. Most of my attention was on the usual suspects: signed URLs, object storage keys, the account boundary around a project. And then I clicked a little tab I'd been ignoring, labeled something like My Devices, and a QR code bloomed onto the screen.
The spark
The pitch was friendly enough: scan this with your phone to upload photos from your camera roll. A neat little bridge between desktop and mobile — no cable, no email-to-self. But QR codes are just URLs wearing a costume, and any time a URL is doing authentication work, my ears prick up. So I decoded it.
Out fell something shaped like this:
https://app.example.com/media/mobile_uploads/signin?token=eyJhbGciOi...<jwt>
That eyJ prefix is a dead giveaway — a JWT, base64url, riding in a query string. A token in a URL that literally has the word signin in its path. The phone doesn't ask me to log in when I open it; the token is the login. My first instinct was the boring one: replay it, forge it, see if it leaks account data.
Digging in
I decoded the JWT payload. It bound the token to my session and my active project — the poster I'd just created. My first hypothesis was IDOR: if the token names a project, maybe I can swap the project reference and read someone else's uploads. Dead end. The endpoint behind the QR was write-only. It let a device push files into the referenced project, but it exposed nothing to read back. I couldn't enumerate, couldn't pull another user's images, couldn't do the classic "increment the ID" dance. For a while I sat there thinking the feature was actually kind of well-scoped. The token was mine, tied to my session, and it only let files flow inward.
Then I re-read my own notes and the whole thing inverted.
It only lets files flow inward. Into the account that generated the token. And nothing — nothing at all — tied the phone that scans it to the human who owns that account. The token was a portable, bearer-grade write credential, and I'd been so busy trying to read data out that I'd missed the far simpler move: get someone else to write data in.
The trust arrow was pointing the wrong way. Everyone assumes a QR handoff carries data toward the person holding the phone — but this one carried data toward whoever minted the code. The victim scans, uploads, and never sees a single pixel of context telling them the photos are landing in a stranger's project.
I opened the mobile URL fresh. The page said, in cheerful sans-serif, "Upload photos from your phone." That was it. No account name. No "these will be added to Marc's Adhesive Poster." No warning, no destination, nothing. The phone had no idea — and neither would any human holding it — that it was pouring files into an account it had never authenticated to.
The exploit
The whole attack is depressingly short. I generate the QR in my own session, lift the URL out of it, and hand that URL to someone else through any channel I like — a chat message, a "here, upload your event photos here" pretext, a lookalike page.
POST /media/mobile_uploads/upload?token=eyJhbGciOi...<jwt> HTTP/2
Host: app.example.com
Content-Type: multipart/form-data; boundary=----x
------x
Content-Disposition: form-data; name="file"; filename="victim_private.jpg"
Content-Type: image/jpeg
<...victim's raw image bytes...>
------x--
The victim's phone fires that request believing it's their upload flow. Meanwhile my desktop session, still holding the same project open, polls for new media and — moments later — their photo materializes in my gallery. I didn't touch their account. I didn't crack anything. I just held the door open and let the app carry their files to me.
Impact
This is a privacy and data-collection vector, not a server takeover, and I want to be honest about that. But the human cost is real: someone's personal photographs — family, documents, whatever they meant to print — end up in an attacker-controlled project, ready to be downloaded or ordered as physical prints. It only takes a plausible pretext ("scan this to send me the pictures") because the app itself removes every cue that would let a careful person notice something's off. The UI's silence is the exploit.
The fix
Two changes kill this cleanly. First, bind the session to the receiving device — issue a short-lived token that the mobile page must exchange for a device-bound session, or require the desktop to confirm the pairing before uploads are accepted, so a token lifted out of the QR is inert on any phone but the intended one. Second, and cheaper, show the destination on the phone. The mobile page must render whose account and which project the files are about to enter: "Uploading to Marc's Adhesive Poster." The moment the receiving human can see where their photos are going, the social-engineering pretext collapses. Convenience features earn their keep by hiding complexity — but they must never hide the answer to "who is this for?"
All identifiers, targets and payloads in this post are anonymized or defanged. Findings were reported and resolved through responsible disclosure.