Firebase during bug bounty hunting
Firebase is a startup which was founded in 2011. In 2014 Google acquired Firebase and since then the feature set of Firebase has become bigger and bigger. It contains features and APIs like databases, remote configuration, serverless functions, hosting, authentication and even machine learning. All these functions are really simple to implement by using the SDKs which they provide in many programming languages.
The number of organisations which are using Firebase in their software is also growing. According to Wappalyzer there are over 40.100 websites which are using Firebase. Most of the Android apps that I reverse engineered were also having references to Firebase features, most of them were using Firestore, which is a serverless NoSQL database, or implemented Firebase Authentication for easy single sign-on with third party providers.
Firestore
What is Firestore?
I’ll start with Firestore. Firestore is a simple NoSQL database which runs serverless. This means as developer or system engineer you don’t have to maintain any server, perform patches and updates and you only pay for what you use. A lot of mobile apps are using Firestore to store their user data, for example user configurations.
A Firestore database can have multiple collections and each collection contains documents, you can see the database as a JSON structure with the collections as the properties on the root level.
The screenshot above shows an example of a simple database containing the following data as JSON:
{
"users": {
"PVelAaROoPLBJIKc81u4": {
"first_name": "Jane",
"last_name": "Doe"
},
"w8qLD2YtXFLxmAXZ7yBK": {
"first_name": "John",
"last_name": "Doe"
}
}
}
Access control
When you create a Firestore database, the UI will ask it’s gonna be used for production or test purposes. Depending on this Firebase will automatically set up a default set of rules. These rules are used to configure the access to your Firestore database. When you select ‘production’, it will setup the following rules by default:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
This ruleset means that your database is not readable and writeable at all. A very secure configuration, but not very usefull in most cases. When combining Firestore with Firebase Authentication a very common setup is as follow:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
With this setup the Firestore database is readable and writable for authenticated users.
Authentication
With Firebase Authentication it’s very easy to setup authentication for your mobile or web application. It supports multiple providers, for example Google, Facebook, Twitter and Apple single sign-on, but also support registration of username/password accounts and even anonymous authentication. All these providers can be enabled or disabled in the Firebase console.
A fun fact is that ‘anonymous authentication’ is also a way of authentication. So if an user is anonymously authenticated, he will pass the Firestore security rule “allow read, write: if request.auth != null;
”.
Let’s hack
Firestore SDK
When you will use the Firestore SDK, you have to configure it with the following configuration:
// Javascript example
firebase.initializeApp({
apiKey: "<some API key>",
authDomain: "j0vsecdemo.firebaseapp.com",
projectId: "j0vsecdemo",
storageBucket: "j0vsecdemo.appspot.com",
messagingSenderId: "<message sender ID>",
appId: "<app ID>"
});
To search for usages of Firebase in your target you first want to search for strings like firebaseapp.com
, appspot.com
, initializeApp
and firebase
in the Javascript source. When you can find any of these strings you can find for doc(
and collection(
surrounded with function calls to get()
and set()
. You have to search for a collection name, for example users
, messages
, posts
, etc.
An example for reading data from Firestore in Javascript looks as follow:
db.collection("users").doc(id).get().then((doc) => {
// ...
}).catch((error) => {
// ...
});
// or
await db.collection("users").doc(id).get();
If you find any Javascript source which follows this pattern, you’re good to go!
Let’s try to get some data
Some websites are loading the Firebase SDK from a CDN. In that case, you can open the Developer Toolbar in your browser, go to the console and fire up some commands directly. If you have to deal with minimized Javascript generated by build tools like Webpack, you probably have to create a script yourself. You can use Javascript, Python, Golang, Java, etc., see the docs for examples.
As example I created a very simple HTML page which loads the Firebase SDK from a CDN. If you open the Developer Console in the browser, you can directly call methods in the firebase
object and you’re using the SDK with the Firebase configuration of the website.
First I check if firebase
is loaded and accessible. You’ll see a Javascript object as output in the console.
You can try if the Firestore database is wide open, meaning that you don’t have to be authenticated to read the data.
firebase.firestore().collection('users').get().then(s => s.forEach(c => console.log(c.data())))
If you see this error the database is protected for unauthenticated access.
The next step is to check if anonymous authentication is enabled. You can sign in anonymously directly from the console:
firebase.auth().signInAnonymously()
If you don’t see any error, you’re authenticated!. Let’s try the Firestore fetch again…
As you can see the data is printed in the console. This means that the Firestore database is protected with a very basic setup. Data is readable (and perhaps writable) for authenticated users, but they enabled the anonymous authentication, which is also a way of authentication in Firebase.
More ways to authenticate
I successfully used the signInAnonymously
trick a couple of times during bug bounty adventures. There are also some other ways to authenticate yourself very easy in Firebase, if there are some misconfigurations.
Try to create a new account and use it to sign in:
firebase.auth().createUserWithEmailAndPassword(email, password)
firebase.auth().signInWithEmailAndPassword(email, password)
Try to use a single sign-on method, ie. Google sign in:
firebase.auth().signInWithRedirect(new firebase.auth.GoogleAuthProvider())
You can find more ways in the documentation.
Read the code
Read your target’s Javascript source and use the debugger in the Developer Toolbar of your browser to find out how your target is using Firebase. I have seen cases where they store user accounts in Firebase Authentication and some extra user data in Firestore using the user ID as document ID. In some cases they stored the user role in Firebase, but configured the Firestore database so that the user was able to change his own role.