September 3, 2026
I Logged In as Literally Nobody and Walked Off With 53,000 Strangers’ Data
A bug bounty story about a “development only” setting that made it to production, one anonymous login, and ~53,000 people’s private data…
By Fauzi Agusti
4 min read
A bug bounty story about a "development only" setting that made it to production, one anonymous login, and ~53,000 people's private data politely handing itself over for which a very nice person paid me $$$.
The obligatory grown-up note: everything here is anonymized. The target is a popular iOS app that ran an authorized program I'll call it Redacted. App name, bundle ID, Firebase project, tokens, and every scrap of user data are faked or blacked out. The bug was reported and fixed. This is a "learn from it" story, not a treasure map.
TL;DR
- An iOS app shipped a Firebase backend where anonymous sign-in was on, and the storage rules only checked _"are you logged in?"_not "are you allowed?"
- Anyone could mint an anonymous login with the app's public API key, and suddenly count as "authenticated."
- That unlocked a storage bucket full of ~53,000 subscriber recordsreal IP addresses, device IDs, prices, refunds — plus write and delete access to the app's live video files.
- CVSS 9.8. From zero to everyone's data with curl and a coffee.
- Bounty $3,000. Not bad for a request that took less time than the coffee.
The trap a 403 that says "all good here"
First thing I did was knock on the storage bucket's front door with no credentials, expecting the usual bash.
curl -s -o /dev/null -w 'status=%{http_code}\n' \
"https://firebasestorage.googleapis.com/v0/b/<redacted>.appspot.com/o"
# status=403curl -s -o /dev/null -w 'status=%{http_code}\n' \
"https://firebasestorage.googleapis.com/v0/b/<redacted>.appspot.com/o"
# status=403
- Locked. Case closed, right? This is exactly where most people pack up and go home and exactly why this bug survived to production. The door is locked. It's just that the lock only asks one question, and it's the wrong one.
Here's the thing about Firebase Storage rules. A shocking number of tutorials (and, allegedly, one line in Google's own docs marked "development only") suggest this:
allow read, write: if request.auth != null;allow read, write: if request.auth != null;Translation: "let anyone in, as long as they're logged in." Sounds reasonable. It is a disaster the moment anonymous sign-in is enabled because to Firebase, an anonymous user is a perfectly valid logged-in user. request.auth != null becomes request.auth != please_come_in.
So the door isn't locked. It just requires a wristband. And the app hands out wristbands to anyone who asks.
Getting a wristband
Every iOS app ships its Firebase config inside the .plist bundled in the IPA including the API key. That key isn't a secret it's designed to be public. What matters is what it can do. And with anonymous auth on, it can mint you a real Firebase ID token with a single POST:
curl -s -X POST \
"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=<redacted>" \
-H 'Content-Type: application/json' -d '{"returnSecureToken":true}' | jq -r .idTokencurl -s -X POST \
"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=<redacted>" \
-H 'Content-Type: application/json' -d '{"returnSecureToken":true}' | jq -r .idToken
No email. No password. No verification. Just… here's a token, welcome aboard. Let's decode it to be sure it's the golden ticket
There it is provider_id: anonymous. I am, officially, Nobody. And Nobody, it turns out, has full access.
What Nobody could see
I re-ran the exact same bucket listing this time with Authorization: Firebase <token> and the 403 turned into a very generous 200
That's a month of daily transaction exports the kind a subscription-analytics platform spits out sitting in the bucket. I pulled exactly one file, unzipped it, and looked at the header row.
advertising and device identifiers (the stable kind, great for tracking people across apps thanks), the price they paid, whether they got refunded, and which country they're in. Multiply by ~29 daily files and you're looking at roughly 53,000 records across 58 countries.
And because the rule said read, write, I confirmed carefully, on a throwaway test object I deleted immediately that I could also write (HTTP 200) and delete (HTTP 204). The bucket also held the app's .mp4 explainer videos, fetched live by every install. Overwrite one of those and you've got content injection into everybody's app. I did not do that. I just noted, politely, that I could.
Why this is a 9.8 and not a shrug
Stack it up mass PII exposure (IPs and device IDs are personal data under GDPR and friends), financial intelligence a competitor would pay for (prices, refund rates, per-country conversion), and content injection into a live app all reachable by literally anyone, with a token the app mints for free. That combination is why this lands at critical, not "nice find."
Lessons, minus the sermon
A 403 is not a clean bill of health. It answers "is this door locked," not "is the building secure." Always ask what the lock is actually checking.
"Development only" settings have a way of graduating to production. If a config is dangerous in prod, it shouldn't be the copy-paste default anywhere.
Public API key ≠ harmless API key. It's public by design the security has to live in the rules behind it, not in hoping nobody finds the key.
Anonymous auth means anonymous == authenticated. If your rules can't tell those apart, they're not rules, they're decoration.
Reported through the app's program and fixed and rewarded with a $3,000 bounty, which is a lot of coffee. If you ship a mobile app on Firebase: go read your storage rules right now, and if you see if request.auth != null guarding anything private, assume a stranger is already logged in.
Thanks for reading. 🔥