Tea continued - Unauthenticated access to 150+ Firebase databases, storage buckets and secrets
Introducing OpenFirebase - Time to clean up the Firebase mess
TL;DR
Think the Tea data breach was bad? Multiply it by about 150, and most likely by 4,800. That is the current state of the Firebase landscape, which about 80% of mobile apps use in one way or another. I reviewed the ~400 most popular (worldwide) mobile apps from only 3 app categories (~1200 in total) and was able to gain unauthenticated access to 150+ different Firebase services. This included access to Realtime Databases, Storage Buckets, Firestore (databases), and secrets defined in Remote Configs.
What do these Firebase services have in common? All of them can easily be left exposed and often contain critical data. If you can think of a type of data, I probably came across it: everything from payment details, user data, millions of IDs, private messages, cleartext passwords, user prompts, GitHub and AWS tokens with the highest privileges and much more.
These aren't just random mobile apps with a few hundred or thousand downloads. Most of them have over 100K+, 1M+, 5M+, 10M+, 50M+, or even 100M+ downloads (Tea app only has 500K+ downloads).
So why did I mention that about 4,800 Firebase services can most likely be accessed without any authentication and contain sensitive data? Because there are 32/34 app categories in total, depending on where you look and excluding gaming categories.
How is this possible? Test mode and other weak Firebase security rules! It also doesn't help that existing Firebase security tools are limited and often miss many issues. That is why I created OpenFirebase, which can extract Firebase items from a single or multiple APK files and scan for weak read and write permissions across multiple services in various formats, fully automated.
In part II, I'll talk about how I also implemented authenticated scanning for all services and how it is possible to get even more access by bypassing Google API key restrictions (will release very soon).
Just here for the OpenFirebase tool?
🔥 OpenFirebase
Automated Firebase security scanner to check for unauthorized read and write access on firestore, realtime databases, storage buckets and remote configs
Why?
When the Tea mobile app had its user data leaked, I figured that this might actually be a wider spread problem, similar to how other cloud storage services are occasionally misconfigured. So what was the issue? The Tea app and the majority of mobile apps use Google Firebase, which is a platform that provides services for building mobile and web applications. It offers services like real-time databases, cloud storage, authentication, and hosting.
The Tea app was using the Firebase storage service to store its user data. This storage bucket was misconfigured to allow anyone to access it. This means that as long as you know the Project ID then you can find and access the bucket. The project ID isn't some long and random secret, it's just a simple string like my-app-dev. It's not supposed to be a secret.
The next question was: Is this just limited to Firebase storage buckets? As you can probably guess, the answer is no. I found almost as many open Firebase realtime databases as open storage buckets. After scanning those two services, I thought I was done but then I also had a look at the Remote Config and Firestore services and found similar results.
Other Firebase related security tools...miss a lot...
There are some very popular scripts/tools available that can be used to detect or scan for unauthenticated Firebase services, but how do they actually work? It turns out that these scripts typically only check one Firebase service at a single URL using just one permission method.
What do I mean? The most common check I have seen in these scripts is testing read permissions on <PROJECT_ID>.firebaseio.com/.json
, which can be used to check unauth realtime database read access. However, as you will see below, multiple Firebase services can expose data and secrets. It is not just limited to the realtime database service.
In addition, some of these services, including firebaseio.com (realtime database), support different formats. This means that even for this single service, you will miss databases or storage buckets that allow unauthenticated access. And that is assuming they scan at all; some only extract data, and even then, it is in a very limited way.
- APKLeaks (only extracts firebaseio.com links)
- MobSF (only checks read access on one of the realtime database formats + read check on remote config)
- Insecure Firebase Exploit (you have to supply the URLs yourself and only checks firebaseio.com/.json with one format)
- Firebase Exploiter (you have to supply the URLs yourself)
- Firebase Scanner (only checks firebaseio.com/.json)
Function ↓ / Tool → | Openfirebase | APKLeaks | MobSF | Insecure Firebase Exploit | Firebase Exploiter | Firebase Scanner |
---|---|---|---|---|---|---|
1. Extract Firebase project IDs and items from APK(s) | ✅ | ⚠️ only Real time DB service in one format | ⚠️ (Limited) | ❌ | ❌ | ❌ |
2. Read permission checks on all services* | ✅ | ❌ | ⚠️ only Realtime DB in one format + Remote Config | ⚠️ only for Real time DB in one format | ⚠️ (only for Real time DB in one format) | ⚠️ (only for Real time DB in one format) |
3. Write permission checks on all services* | ✅ | ❌ | ❌ | ⚠️ only for Real time DB in one format | ⚠️ only for Real time DB in one format | ❌ |
4. Check on multiple formats/domains per service | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
5. Scan with project ID(s) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
6. Allows mass extracting and scanning | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
7. Authenticated scanning and bypass Google API key restrictions (see blog part II for more info) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
*Not including the Remote Config service.
That’s why I decided to create OpenFirebase which checks multiple Firebase services across various formats and permissions. The best part? It can do all of this automatically by simply providing a single mobile app (APK file), multiple APKs, a single project ID, or multiple project IDs.
So is the project ID all we need? It is for the Realtime Database and Storage services. For Remote Config and Firestore checks, we need more data, which we can only get from the APK file.
Mass reviewing APks
An APK, or Android Package Kit, is the file format used by the Android operating system to distribute and install mobile apps. It contains all the necessary components, such as code, resources, and metadata, for an app to run on an Android device.
This blog is not about how to download thousands of APK files, so I won’t go into too much detail on how you can do this. However, if you look around, you will find multiple sources where you can get your APK files from.
Similar to the Google play store, these sources have app categories listing the most popular apps from each category. I downloaded the most popular ~400 apps from only 3 app categories, which means I only reviewed about 1,200 apps. However, there are about 34 categories in total, excluding gaming categories.
Before we can check for unauthenticated access, we need to look into Firebase’s services, its different URLs, and the kind of data we need to extract from the APK.
Exploring Firebase services
First, it's important to understand that project IDs are not exclusive to a single service. You can create a Firebase project and use multiple services such as Realtime Database and Firebase Storage and those services can have the same project ID. For example:
- Realtime Database:
dev-project.firebaseio.com
- Firebase Storage:
dev-project.firebasestorage.app
Firebase Storage
Firebase Storage is a service for storing and serving files. When extracting Firebase URLs I found many with the format <PROJECT_ID>.appspot.com
but also some with <PROJECT_ID>.firebasestorage.app
. I couldn't find much about the latter; however, the Firebase documentation states that <PROJECT_ID>.firebasestorage.app
is now (as of October 30, 2024) the default bucket format. All buckets before that date have the format <PROJECT_ID>.appspot.com
.
We can use the following Google API to access the PROJECT_ID.appspot.com storage bucket:
I figured that this probably must be the same for firebasestorage.app, although I couldn't find much about this. At this point I decided to just create a Firebase project with different services to see the URLs, instead of only trying to enumerate it from a blackbox perspective. Turns out that you can use the same API for firebasestorage.app:
As you can see in the image below, these are separate buckets. No mapping or redirection occurs in the background. You can make a request to example-app.appot.com
and get a not found error, but make the request with the same project ID to example-app.firebasestorage.app
and find that all your data is public:
This was enough to collect the stats about open storage buckets. However, if you want to access the files, there are some things you need to add to the URL. OpenFirebase will not extract and download files from databases or buckets, it just checks if it is open or not.
You can't view a file by just going through the URL like so:
Instead, it requires URL encoding of the slashes (%2F) and adding ?alt=media to the end:
It is also possible that an additional token is required (e.g filename.png?alt=media&token=dc23c3a5-a551-4b6c-8237-5fc3d4d0ed0c
). In that case, you cannot access the file unless you know the random token. These tokens might be hardcoded in the source code or can be retrieved from other sources such as the Wayback Machine (though this only gives you access to the specific file).
As mentioned, if there's a lot of data in the bucket then there will be a nextPageToken value in the initial JSON response. You can load the data on the next page, using the pageToken parameter (this also seems to be the same for other Firebase services):
There is one interesting common 400 error:
Listing objects in a bucket is disallowed for rules_version = "1". Please update storage security rules to rules_version = "2" to use list.
This happens when the security rules for the storage bucket don't include rules_version = '2';
. This does not automatically mean that the security rules are configured securely! If you know the exact name of a file and the security rules allow unauthenticated access, then you can still read or write to the bucket.
Firebase realtime databases
The Firebase Realtime Database service allows data to be stored and synchronized in real time across all connected clients. As mentioned earlier, many scripts will check <PROJECT_ID>.firebaseio.com
, but is that the only URL format? When reviewing the documentation and creating a Realtime Database, I noticed that it uses the following format:
So where does <PROJECT_ID>.firebaseio.com
come from? The Firebase Admin SDK docs states that this used to be the old format for Realtime databases created before September 2020.
So when we extract project IDs, we have to test for both formats. But that is not all, realtime databases can live in different regions which have different domains:
-
Database in US; old format -
<PROJECT_ID>.firebaseio.com
-
Database in US; new format -
<PROJECT_ID>-default-rtdb.firebaseio.com
-
Database in other regions (EU and ASIA); old format -
<PROJECT_ID>.REGION.firebasedatabase.app
-
Database in other regions (EU and ASIA); new format -
<PROJECT_ID>-default-rtdb.REGION.firebasedatabase.app
Does that mean we have to make a request to each region? What happens if we make a request to <PROJECT_ID>.firebaseio.com
or <PROJECT_ID>-default-rtdb.firebaseio.com
, but the database is actually in the EU?
For once, Google API errors are actually helpful and solves our problems. If the database exists for that project ID, the API response includes the correct region/URL (for both GET and POST). It does not automatically redirect, so the tool extracts the correct URL from the response and makes a new request.
Therefore, we only need to make two requests using the two firebaseio.com
formats. The other two firebasedatabase.app
formats are handled via content extracting and redirecting. Appending /.json
to the end of the URL is enough to retrieve the database content, if unauthenticated read is allowed.
If you add a second realtime database to the same project, Firebase will, by default, suggest using the project ID as its name. OpenFirebase supports scanning multiple databases within the same project.
Additionality, the tool supports a write permission check (not executed by default). If writing is allowed then you can also overwrite existing files, so make sure you use a non-existent filename.
Firebase Remote Config
Firebase Remote Config lets you dynamically change your app’s behavior and appearance without requiring users to download an update. This is defined with key-values, for example:
Doesn’t necessarily sound like a security issue, right? Unless... you define secrets tokens, access tokens or private API keys, but why would you put those in a config that anyone can access? Nobody would do that, right?!
So how can we retrieve the content of a Remote Config? Again, we need the Project ID, and this time we also need a google_api_key
and a google_app_id
, both of which can be retrieved from the res/values/strings.xml
file. This file is compiled into resources.arsc
and included in the final APK, meaning anyone can extract it and obtain these values.
Initially, these keys and IDs are automatically generated from the google-services.json
file during build time. This file is included when adding any Firebase service to your android app:
Example of the google_api_key
and a google_app_id
, project ID and any firebase URL in the google-services.json
file:
Example content of the final res/values/strings.xml
from an APK:
I have also seen apps that use different strings.xml files for different languages, for example:
OpenFirebase will scan all strings.xml files. Once these values have been extracted, you can make the following POST request to retrieve the actual content of a remote config (I will talk about bypassing a common API restriction in part II of this blog):
Similar to other services, when you receive a 200 OK, it means that the Remote Config service is used and not restricted. However, for this service, a 200 OK response can also include the error NO_TEMPLATE
, which means a remote config is not defined.
Firestore
Firestore is another service that allows you to store JSON-like documents organized into collections, using NoSQL. It's important to understand that Firestore is not the same as the Realtime database service.
A Firestore database consists of at least one collection (comparable to a table in relational databases) and document but it most likely has multiple collections and documents. It can also have subcollections/nested collections, e.g. /users/{userId}/orders/{orderId}/items/{itemId}
.
Below is an example of a Firestore database with one collection (users) and one document with an auto generated ID. This does not have to be random you can use any value for a document name. For more info check: https://firebase.google.com/docs/firestore/data-model
To check for unauthenticated Firestore access on an entire collection, we can use one of the following APIs:
Or:
If we want to directly check the data in a document then we would make the following request (using the data from our Firestore example):
To query the database we need the PROJECT_ID (which is stored in all APKs, if firebase is used) and we need at least one collection name. Ideally, we get all collection and document names but where can we get those from? Up to this point, I was just parsing the /res/values/strings.xml
, which worked fine, but it's not going to work for this one. So, I decided to add JADX decompiler as a second extraction option, since we need to check the source code of the app. If it cannot be decompiled with JADX (Tiktok), or if it takes too long, it falls back to the fast extraction method.
Collections and documents can be defined in the source code by creating references: "A reference is a lightweight object that just points to a location in your database. You can create a reference whether or not data exists there." See https://googleapis.dev/nodejs/firestore/latest/CollectionReference.html and https://firebase.google.com/docs/firestore/data-model
Example to create a reference to a collection:
Or a document reference example:
But wait...does it make sense to hardcode the document name in this example from the Firebase docs? I don't think it does. When I started decompiling apps with JADX, I found that the document name is indeed usually just a variable that is retrieved instead of hardcoded. This totally makes sense but it's unfortunate for us.
It still works fine for checking unauthenticated access to Firestore collections. I’ve found public read access to random collection names that wouldn’t be discovered by simple fuzzing. In my experience, roughly 1 in 30 apps have hardcoded collection names. Note: Collection names are case-sensitive and can contain spaces (I'm still finetuning the regex/filters for this).
If you scan for public Firestore collections, you may come across the message: "Cloud Firestore API is not available for Firestore in Datastore Mode database." This typically occurs when the database is in Datastore mode, but it can also be caused by not having any collections in your database.
Similar to the other service scans, it also has a write permission check, but this is not performed by default. Openfirebase checks for both read and write (create (POST)
) access. For Firestore, it does not yet support the other granular write permissions update (PATCH)
and delete
, because I do not consider these useful for mass scanning Firestore projects/collections. They would be useful if you were testing at the document level, but in that case, you probably would not be using Openfirebase.
Lastly, the tool includes an option and wordlist to fuzz for common collection names such as users
, accounts
, passwords
, orders
, uploads
, logs
, etc. If Firestore allows unauthenticated read access but the correct collection name is unknown, it will attempt to discover it through fuzzing. This is done by combining collection names extracted from the decompiled source code (if any) with entries from the wordlist.
So does that mean you can check if a Firestore allows unauthenticated access even without knowing the "not so secret" collection name? Yes! If you make a request with any collection name, even if it is invalid, the response will be different from that of a protected database. That is why OpenFirebase by default makes a request to the users
collection, which is the most likely name to exist. See the following example where we get a 200 OK but no content:
If we were to just go to /databases/(default)/documents/
then the Firestore API gives back a 404 but if we go to the non-existent users
collection, the empty content tells us that the Firestore database does not require authentication. I tried to find a valid collection name for this project but couldn't find one. If you were to come across this in a bug bounty program then how would you show more impact? Check if you have write permissions! If anyone can read then test mode is likely used (it was in most of my cases). See this example from a bug bounty program:
Next, write the file which confirms that test mode is indeed used:
Then read the new firestore_unauthenticated_access
collection:
The results
From my sample of 1,200 apps, I found that about 80% of mobile apps use Firebase in some way (I thought this was on the higher side, but it seems to line up with public info). Among these, I found around 150 Firebase services that allow unauthenticated access to data, secrets etc. Again, this did not include apps with only a few hundred downloads. I'm talking about the top 400 most popular apps worldwide from three different app categories.
That might sound like around 13% of mobile apps are leaking sensitive data. But is that true? Although some of these services allow unauthenticated access, they don’t (yet) store any data, or they only contain non-sensitive data. Storage buckets, in particular, often hold non-sensitive data. I can’t give a completely accurate number because I did not go through all the data, but I’d estimate the average closer to 6%. This decreases as you reach the very top downloaded apps.
It’s also important to note that some apps have all their Firebase services exposed. In 18 cases (including apps with 10 million+ downloads), I encountered project IDs using all four major Firebase services with three or four of them set to public and containing customer data.
All these tests were performed before I added all the write checks, authenticated scanning and the Google API key restriction bypass to OpenFirebase.
I was planning to release this blog and the tool after the first week back in august, but I kept adding more and more features to the tool. In the meantime, I have reported all issues to the affected companies and I also downloaded and scanned most of the APKs from public bug bounty programs as well as some from private programs on HackerOne, Bugcrowd, Intigriti, YesWeHack. The results below do not include those.
I'm not going to turn this blog into a bug bounty platform rant, but reporting Firebase issues has been a frustrating process :)
Firebase Storage
Storage Scan Summary | Count |
---|---|
Total projects scanned | 937 |
Public storage buckets | 44 |
Protected storage buckets (401/403/412/400) | 386 |
Storage bucket not found (404) | 507 |
It is not rare to find Storage buckets with non-sensitive data. Just because you find a storage bucket that allows unauthenticated read access does not mean that it is automatically worth reporting. It is only worth reporting if you find actual sensitive data or if it also allows unauthenticated write access.
One of the more interesting cases had 100M+ downloads, which is in the range of well-known apps such as Twitch, Tumblr, etc. The storage bucket was storing the ID photo for each user, meaning anyone could access 100M+ IDs without any authentication. Any read check in OpenFirebase only sees the first 1000, unless you manually extract and use the nextPageToken to request the next batch.
Firebase Realtime Databases
Realtime database Scan Summary | Count |
---|---|
Total projects scanned | 937 |
Public realtime databases | 35 |
Protected databases (401/403/412) | 277 |
Database not found (404) | 467 |
Locked/deactivated (423) | 158 |
More often than not, it is an issue when realtime databases are accessible without authentication. I have only found a few rare cases where a company used the realtime database service as a kind of remote configuration; in such cases, allowing unauthenticated access is not a problem. Still, make sure that there are no secrets and write access is not allowed!
The public realtime databases that I found contained all kinds of data, including user data (names, emails, encrypted and even cleartext passwords), user prompts, chats between people, coordinates of people’s current location, and much more.
Firebase Remote Config
Remote Config Scan Summary | Count |
---|---|
Public remote configs found | 383 |
Protected remote configs (401/403) | 61 |
Missing config data | 1 |
Apps without Remote Config | 429 |
383 remote configs accessible without restrictions might seem like a huge problem, but as mentioned before, exposing your remote config is not the issue. It becomes an issue when secrets are defined in it, which was the case in about 30 of them. However, I have not manually checked all 383 of them, so this could easily be 50+.
When a remote config is found, OpenFirebase extracts the config content, which you can manually inspect for secret keys. However, you can also automate some of it by running tools such as Trufflehog or Gitleaks on the entire folder of config results. You can then test specific keys by using Trufflehog in analyze mode. There are more things you can do like proxy the results through Burp and use its extensions but I will leave that up to you.
So, what happens when you do that for hundreds of configs? You find many passwords, secrets, API keys for services such as OpenAI, stable diffusion and more. The most interesting ones? AWS access + secret token for the root account and here is another example of an exposed Github token which would allow complete control over all their code repositories:
Reporting these has been just as painful as reporting the others. Anytime you mention Remote Config in your title, you risk having your report closed as N/A because “Remote Configs are supposed to be public,” even though that’s not what your report is about. I guess people have been spamming programs in the past with public remote configs being an issue and now they don't bother reading even though your report is about valid secrets/passwords being exposed in them.
Firestore
Firebase Firestore Scan Summary | Count |
---|---|
Total projects scanned | 929 |
Projects with publicly accessible Firestore DB | 50 |
Protected Firestore (403) | 675 |
Projects in Datastore Mode (empty/unused) | 122 |
Total open collections found | 24 |
Other/errors | 2 |
In most cases, it is an issue if your Firestore or realtime database is left wide open. When scanning for this, OpenFirebase is able to detect if the Firestore database is publicly accessible, even if you don’t know the correct collection name. That is why it lists 24 open collections but 50 total open Firestore databases found.
For the ones where a valid collection could not be found, I have now implemented an automatic fuzz function. However, this function was not used for these results. Below is an example of unauthenticated access to an onboarding Firestore collection, which was discovered by extracting collection names from the APK:
Many bug bounty triagers are not aware that Firestore and Realtime Database are two different Firebase services or they simply do not read the report at all; just the title (this has happened a few times, and only after asking for mediation did some get reopened and triaged). As soon as you mention the word database anywhere in the report or title, they mark it as a duplicate of some Firebase Realtime Database report from years ago. Make sure to clearly distinguish between the two in your report and hope for the best.
Root cause and mitigation
When you use the Firebase Storage, Realtime Database or Firestore service, the first thing it asks is whether you want to set up the service in production or test mode. The difference is obvious, but this is where mistakes can happen. For production mode it states:
"Your data is private by default. Client read/write access will only be granted as specified by your security rules." vs test mode: "Your data is open by default to enable quick setup. However, you must update your security rules within 30 days to enable long-term client read/write access."
If you select the vulnerable test mode, anyone can read your data and even write files to your bucket for 30 days! Firebase also displays the following warning:
"The default security rules for test mode allow anyone with your storage bucket reference to view, edit and delete all data in your storage bucket for the next 30 days":
So what do you do if you use test mode and you have reached the 30 day limit? Should you switch this to require authentication? No, just extend the date!
Don't do this... :)
So, is this the root cause? Are developers just using test mode and not changing or extending these rules simply because it’s easier that way? I think that’s only one of the causes. Another is that some developers may not be fully aware of Firebase security rules best practices.
While implementing the write-permission check for the Storage service, I was looking for the request format but couldn’t easily find it, until I stumbled upon this Gist. It suggests setting the security rules to allow read, write;
, which allows unauthenticated access. If developers use things like this for testing and forget to revert the rules, it causes the exact same risk as running in test mode.
I was planning to talk about how to actually write good security rules and commonly made mistakes. However, it turns out the Firebase documentation does exactly that for the same services that we have talked about, so I'm not gonna copy and paste their docs. Instead, if you are interested have a look here: https://firebase.google.com/docs/rules/insecure-rules
What's next?
Limitations? There is one check that OpenFirebase currently does not support and very likely allows access to even more Firebase projects. As far as I know, there is one known tool (firepwn) that does this particular check, but it’s not available from the CLI.
I have now implemented authenticated scanning for all read and write checks across all services. I’ll be releasing Part II very soon, where I will discuss this in more detail and explain how you can bypass a common Google API key restriction.
When I have time, I'll also look into other Firebase services such as Cloud Functions.
Feel free to message me if you have any questions about the tool or would like me to add other features :)