Firestore Black Box Security Testing Guide — Go Beyond *.firebaseio.com/.json with the bug bounty
GitHub Gist — format friendly version
Insentives
Firestore security is an important topic for modern applications. Its wide usage and serverless architecture may cause security issues in the areas such as authentication, authorization, and data exposure. Especially they are exposed to data leakages, which may be caused by a non-serverless design approach. In a world of multi-tier applications, using a backend database, the data stored inside the DB was secured by default and could be accessed only if there is a server code that accesses it. In a world of serverless components, all your DB’s content is wide open unless security rules were set. This goes for both collections and the data inside them. Even though collection may have sufficient authentication and authorization, they may include sensitive fields that shouldn’t be exposed to the end-users. While in non-serverless architecture, these fields would just be neglected throughout server-side queries, in a serverless world if the document is accessible to a user, all of its fields are accessible.
The goal of this article is to combine all Firestore security-related information in one place. While there is a lot of developer-targeted documentation, there are scattered pieces of security-related information and code examples. The article should be helpful for penetration testers and architects involved in planning a secure design for solutions utilizing Firestore DB.
Firestore Test Steps
1. Authentication checks to get access to the database without the intended authentication schemes.
- Anonymous access
- Access with a self-registered account
- Access with 3rd party accounts
2. Analyse data leakages. Get all accessible Firestore content directly to reveal possible data leakages.
3. Authorization testing
- Extract known collections and documents from the source code.
- Test all collections and documents for access control bypasses using get(), set(), update(), and delete() actions.
4. Brute force for hidden collection names to reveal restricted sensitive data.
5. Map and discover Cloud Functions.
Intro to Firestore Database
Firestore is a NoSQL document database, a fully managed service that provides a flexible, scalable, and real-time data storage solution for your mobile, web, and server-side applications. It is built on Google’s infrastructure and is designed to automatically scale as the data and usage grow. The main components of the Firestore database structure are:
- Databases: A Firestore database is a container for all of the data. You can create multiple databases within a Firebase project, each with its own set of collections and documents.
- Collections: A collection is a group of documents in a Firestore database. Collections are analogous to tables in a traditional relational database.
- Documents: A document is a unit of data in a Firestore database. Documents are analogous to rows in a traditional relational database. A document can contain any number of fields, and each field can contain any data type (e.g., strings, numbers, arrays, etc.).
The actions that are exposed in Firestore include the following:
- Reading data: Firestore allows you to read individual documents or collections of documents. You can also use queries to retrieve specific subsets of data.
- Writing data: Firestore allows you to create new documents or update existing ones. You can also delete documents or collections of documents.
- Listening for changes: Firestore provides real-time updates by allowing you to listen for changes to specific documents or collections. When a change is made, the listener is triggered and your application can take the appropriate action.
- Transactions: Firestore allows you to perform atomic operations on multiple documents in a single transaction. This ensures that all the operations are either applied or rolled back as a unit, ensuring the consistency of the data.
- Cloud Functions — execution of pre-defined server-side code.
There are several incentives to use Google Firestore, a NoSQL document database, in your application:
- Scalability: Firestore is designed to scale automatically with your application. As your application grows in popularity and usage, Firestore will automatically allocate more resources to handle the increased load.
- Real-time updates: Firestore allows you to listen for real-time updates to documents and query results. This can be useful for building collaborative applications or for creating a more responsive user interface.
- Strong consistency: Firestore provides strong consistency for read and writes operations, meaning that when you read a document, you can be confident that you are getting the most up-to-date version.
- Easy to use: Firestore has a simple and intuitive API that makes it easy to get started with. It also integrates seamlessly with other Google Cloud services, such as Cloud Functions and Cloud Pub/Sub.
- Offline support: Firestore allows you to access data while offline and automatically synchronizes changes when the device comes back online. This can be useful for applications that need to work in areas with poor or unstable network connections.
- Security: Firestore provides robust security features, such as data validation, access control, and encryption at rest, to help ensure the safety and privacy of the data.
- Cost: Firestore has a pay-as-you-go pricing model that charges based on the number of reads, writes, and deletes your application performs. This means that you only pay for the resources you use.
- Powerful querying capabilities so you can retrieve specific subsets of data based on your needs.
Firestore Security
There are several security layers and configuration options available in Firestore to help secure the data:
- Authentication: Firestore integrates with Firebase Authentication to provide authentication for your users. Firestore can support different authentication providers, such as email and password, phone number, or popular identity providers like Google.
- Access control: Firestore provides fine-grained access control for the data, allowing you to specify which users or groups of users can read, write, or delete specific documents or collections.
To ensure the security of the Firestore database, you can use a combination of authentication types, security rules, and access control measures. The authentication layer restricts who can authenticate to the system and will define requirements for the end-users token. Security rules define the conditions under which data can be read, written, or deleted in the database, while access control measures determine which users or groups of users have permission to access the data. If you do not properly secure your Firestore database, it may be possible for unauthorized individuals to access and read the data stored in it. This could occur if you have not set up proper authentication and/or authorization controls.
Firestore Authentication
Firebase Authentication supports the following authentication methods:
- Anonymous authentication.
- Email and password: Allows users to sign up and sign in using their email address and password.
- Phone number: Allows users to sign up and sign in using their phone number.
- Common external auth providers like Google, Facebook, Twitter, Microsoft, Apple, GitHub, Yahoo, and LinkedIn.
- Custom authentication, which allows you to use your own authentication system or integrate with an external identity provider.
After the successful authentication, the end user will receive a dedicated JWT token that will be used to access the database.
Firestore Authorization
One way to control access to data stored in Firestore is through the use of authentication and access control lists (ACLs). Here are a few examples of how you might use authentication and ACLs in Firestore:
- Limit access to a specific document: You can use Firestore security rules to allow only authenticated users to read or write to a specific document. For example, you might want to allow only the owner of a document to be able to edit it.
- Limit access to a collection: You can use security rules to allow only authenticated users to read or write to a specific collection of documents. For example, you might want to allow only admins to add new documents to a collection.
- Limit access based on a user’s role: You can use security rules to allow only users with a specific role to read or write to a specific document or collection. For example, you might want to allow only moderators to delete documents from a collection.
- Limit access based on custom claims in a user’s ID token: You can use security rules to allow access based on custom claims that are included in a user’s ID token. For example, you might want to allow only users with a “paid” claim to read or write to a specific document or collection.
If the security rules for a Firestore database are not properly configured, it could potentially allow unauthorized access to the data. This could include allowing read or write access to sensitive data or allowing malicious users to execute unintended actions on the database.
Here are some examples of access control rules for Firestore:
Allow all users to read from the “public” collection, but only allow authenticated users to write to it:
service cloud.firestore {
match /databases/{database}/documents {
match /public/{document} {
allow read: if true; allow write: if request.auth.uid != null;
}
}
}
Only allow users with the “admin” role to read and write to the “admin” collection:
service cloud.firestore {
match /databases/{database}/documents {
match /admin/{document} {
allow read, write: if request.auth.token.roles.hasAny(["admin"]);
}
}
}
Only allow authenticated users to read and write to the “users” collection:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth.uid != null;
}
}
}
Only allow the owner of a document to read and write to it:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
}
}
Keep in mind that these are just a few examples, and you can use the Firestore security rules language to define much more complex and customized access control rules for the database.
Firestore access control rules can use the claims of a Firebase authenticated user to grant or deny access to specific documents or collections. Claims are custom attributes that you can assign to a user’s token when they authenticate using Firebase Authentication.
Here’s an example of how you can use claims in your Firestore access control rules to implement an ACL (access control list) system:
service cloud.firestore {
match /databases/{database}/documents {
match /projects/{projectId} {
allow read, write: if request.auth.token.admin == true || request.auth.token.projects.hasAny([projectId]); }
}
}
In this example, the rule allows users with the admin claim set to true to read and write to any project document. It also allows users with a projects claim that contains the projectId of the document to read and write to that specific document.
To assign claims to a user’s token, you can use the Firebase Admin SDK or the Firebase client SDK. For example, to assign the admin claim to a user’s token using the Firebase Admin SDK (using Firestore admin access), you can do the following:
const admin = require('firebase-admin');
admin.auth().setCustomUserClaims('test-user', {
admin: true
}).then(() => {
console.log('Claims set successfully');
});
This will set the admin claim to true for the user with the UID of test-user. You can then use this claim in your Firestore access control rules to grant the user access to certain resources.
Security Best Practices
To ensure the security of the data in Firestore, it is important to follow best practices in the following areas:
- Data validation: Use Cloud Firestore Security Rules to enforce constraints on the data and ensure that only valid data can be written to the database.
- Authentication: Use Firebase Authentication to securely authenticate your users and control access to the data. Disable anonymous users and self-registration.
- Access control: Use Firestore’s fine-grained access controls to specify which users or groups of users can read, write, or delete specific documents or collections.
How To Access And Test Firestore Database
There are several ways to programmatically access Firestore, depending on your programming language and the platform you are using. Here are some examples:
- Client SDK — you can use the Firebase SDKs for web or mobile to access Firestore. These SDKs provide client libraries that allow you to read, write, and listen for changes to data in Firestore.
- RestAPI — you can send raw HTTP requests to the API directly. The base URL for the API ishttps://firestore.googleapis.com. You can then specify the collection and document that you want to access by adding them to the URL path. For example, to retrieve a document with the ID my-doc from a collection called my-collection, you would use the following URL:https://firestore.googleapis.com/v1/projects/my-project/databases/(default)/documents/my-collection/my-doc/documents/my-collection/my-doc)
- Firebase Admin SDKs. These SDKs provide server-side libraries that allow you to read, write, and delete data in Firestore from your server as well as deploy security rules, and manage Firebase project.
- Command-line interface: If you want to access Firestore from the command line, you can use the Firebase command-line interface (CLI) to perform various operations.
To use the Firestore API to access the data, you need to provide the following connection details:
- Project ID: The ID of your Firebase project, which is unique to your project and is used to identify it in the Firebase console and other Firebase tools. You can find your project ID in the Firebase console.
- API key: The API key for your project, which is used to authenticate requests to the Firestore REST API. You can find your API key in the Firebase console under the “Settings” menu.
- Collection ID: The ID of the collection that the document belongs to if the document is part of a collection.
- Document ID: The ID of the document you want to access, which is used to identify the document in the database.
When Firestore requires authentication, an authentication token should also be provided.
JavaScript Client SDK
To test Firestore using JavaScript you should use Firebase SDK. You’ll need to include the Firebase Authentication and Firestore libraries in your JavaScript code.
Here is an example of how you could use JavaScript to perform penetration testing on the access controls for a Firestore collection:
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-firestore.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-functions.js"></script>
<script>
// Initialize the Firebase app
// Replace YOUR_FIREBASE_CONFIG with your actual Firebase config object
firebase.initializeApp({ YOUR_FIREBASE_CONFIG });
var firebaseConfig = {
"apiKey": "AIXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"authDomain": "PROJECTNAME.firebaseapp.com",
"databaseUrl": "https://PROJECTNAME.firebaseio.com",
"projectId": "PROJECTNAME",
"storageBucket": "PROJECTNAME.appspot.com",
"messagingSenderId": 1111111111111,
"appId": ""
};
// Initialize the Firebase SDK
var app = firebase.initializeApp(firebaseConfig, Date.now().toString());
app.auth().signInWithCustomToken(
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.{\"aud\":\"https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit\",\"iat\":1673432605,\"exp\":1673436205,\"iss\":\"firebase-adminsdk-oavdj@PROJECTNAME.iam.gserviceaccount.com\",\"sub\":\"firebase-adminsdk-oavdj@PROJECTNAME.iam.gserviceaccount.com\",\"uid\":\"mtsATf9KBTWbpqXwT05HljnjE8iaGxvAmBYHu21wRgo=\",\"claims\":{\"userId\":\"YYYYY\",\"tenantId\":\"BBBBBB\"}}.DpRmJxTupj4WkYtFw1oZURsrTtabrl_QEJU3VkNbL8VCFdkjtbOYcO1XqdVtcTq6yiyNCToxaMAbNXhbAPtdpqs6Rj75vwSySjDI8kJEg9zYqixrgqsVSqUphjMnBXK99lLSu9XdH5NEUfd-6aQ1Q0E1_0_0xHpYL8_67c7jFeLPhl2XZLQdUNTabkPDrm978tJcy9M8AJg-yGRO8wZsVFe3Yja-0-agLaFvrkc2XuC95ZUPDNz2XWHcAqC4fPemkegZBB9oo8Y_dUZ7SF_E6gW77muY_PeE9rFykNTnA6OzjfJJcGEN2pT3_AY2bx-YhZpaKXjxjJAFx-05G6KJ2w"
);
// Set the collection reference const
collectionRef = firestore.collection(collectionId);
let collectionRef = db.collection("TestCollection");
// Get all documents from the collection and print them as JSON to the console
let allDocs = collectionRef.get()
.then(snapshot => {
let collectionData = [];
snapshot.forEach(doc => {
let data = doc.data();
collectionData.push(data);
});
console.log(JSON.stringify(collectionData));
})
.catch(err => {
console.log('Error getting documents', err);
});
</script>
This code initializes the Firebase SDK with a custom JWT token (take the token from the running application proxy logs), gets the Firestore instance, and sets a reference to the collection with the specified ID. It then attempts to read the collection using the get() method. If the operation is successful, it means that the current user has read access to the collection. If the operation fails, it means that the user does not have read access or that the collection does not exist.
You can use similar code to test write and delete access to the collection by using the add(), update(), and delete() methods.
Python / RestAPI
The Firestore REST API allows you to interact with Firestore from any environment that can send HTTP requests. Here is an example of how to use the Firestore REST API in Python to read a document:
import requests
import json
# Set baseline unique Firestore instance connection details
# For Python / RestAPI project id and Firebase JWT tokens are mandatory
# jwt_token - firebase authentication token; if custom authentication is used, then custom JWT should be exchanged to Firebase token (POST to https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=PROJECT_jwt_token {"token":"CUSTOM_TOKEN","returnSecureToken":true}) which require also the api key of the Firestore project
jwt_token = "JWT-Token"
project_id = "your-project-id"
# Set the headers
headers = {"Authorization": f"Bearer {jwt_token}", "Content-Type": "application/json"}
collection_id = "your-collection-id"
document_id = "your-document-id"
# This example sends a GET request to the Firestore REST API to read a document with the specified ID in the default database of your Firebase project. The response is a JSON object that contains the fields of the document.
# Send the request
base_url = f"https://firestore.googleapis.com/v1/projects/{project_id}/databases/(default)/documents/{document_id}"
response = requests.get(base_url, headers=headers)
# Get the document data
document = response.json()
data = document["fields"]
print(data)
# Here is an example of how to use the Firestore REST API in Python to write a document
# This example sends a PATCH request to the Firestore REST API to update a document with the specified ID in a collection with the specified ID in the default database of your Firebase project. The request includes the fields of the document in the request body as a JSON object.
base_url = f"https://firestore.googleapis.com/v1/projects/{project_id}/databases/(default)/documents/{collection_id}/{document_id}"
# Set the document data
data = {"fields": {"name": {"stringValue": "John Smith"}, "age": {"integerValue": 30}}}
# Send the request
response = requests.patch(base_url, json=data, headers=headers)
# Check the response status
if response.status_code == 200:
print("Document updated successfully.")
else:
print("Error updating document.")
Authentication Testing
Self-Registration
The code below checks if self-registration is set. In such a case it may be possible to get un-authenticated access to Firestore data and potentially to circumvent incorrectly set ACLs that else, with a legitimate user may not be possible to circumvent.
var email = "sdflkjsdlkfjhio79@XXXXXX.com";
var password = "kjashdokh8&^*(0jasd";
firebase.auth().createUserWithEmailAndPassword(email, password);
firebase.auth().signInWithEmailAndPassword(email, password).then(function (user) {
console.log('Login successful!', user);
}).catch(function (error) {
console.error('Error:', error);
});
firebase.firestore().doc('users/test').get().then(function (doc) {
console.log('Document data:', doc.data());
}).catch(function (error) {
console.error('Error:', error);
});
Anonymous Authentication
You can use the signInAnonymously() method to sign in a user anonymously if it is configured.
firebase.auth().signInAnonymously().then(function (user) {
console.log('Anonymous login successful!', user);
}).catch(function (error) {
console.error('Error:', error);
});
firebase.firestore().doc('users/test').get().then(function (doc) {
console.log('Document data:', doc.data());
}).catch(function (error) {
console.error('Error:', error);
});
External Authentication Providers
- Google sign-in: You can use the signInWithPopup() method with the google.auth.GoogleAuthProvider provider to sign in a user with their Google account. This method opens a pop-up window where the user can choose to allow your application to access their Google account.
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(function (result) {
var user = result.user;
console.log('Login successful!', user);
}).catch(function (error) {
console.error('Error:', error);
});
Authorization Testing
To test authorization for access to a Firestore collection using JavaScript, you can use the get() method of the Firestore API to try to read a document from the collection. This method takes a document path as an argument and returns a promise that resolves with the document data if the read is successful, or rejects with an error if the read is not allowed by the security rules.
Collection Testing Examples
Actually, as detailed in the Firestore JS SDK documentation, retrieving a list of collections IS NOT possible with the mobile/web client libraries (non-admin credentials). This is true for the root collections of the Firestore database and also for the sub-collections.
To test collection access controls we need to extract a list of collection names from the source code and try accessing them manually.
Collection Access Examples
Here are a couple of examples of getting data from the collection/documents using JavaScript SDK
//Firestore initialization + authentication
// getting content of single collection
db.collection("users").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(JSON.stringify(doc.data()));
});
});
// Getting content of document inside a collection
db.collection("Users").doc("users1").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(JSON.stringify(doc.data()));
});
});
// nested collections
db.collection("Users").doc("users1").collection("files").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(JSON.stringify(doc.data()));
});
});
// nested documents and nested collections
db.collection("app").document("users").collection(uid).document("notifications").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(JSON.stringify(doc.data()));
});
});
// getting specific fields
db.collection("users").get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
results += doc.id + "," + doc.data().account + "," + doc.data().displayName + "," + doc.data().logo + "," + doc.data().XXX + "," + JSON.stringify(doc.data().YYY) + "," + doc.data().ZZZ + "\n";
});
});
Extracting Collection Names From Code
Here are the steps to extract Firestore collection names from 3rd party JavaScript and other client-side code.
- Locate the code that interacts with the Firestore database. This may be in the form of a JavaScript file or an Android class file.
- Identify the Firestore client object that is used to access the database. In JavaScript, this will typically be an instance of the Firestore class. In Android, this will typically be an instance of the FirebaseFirestore class.
- Look for calls to methods that retrieve collections from the database, such as getCollections(), listCollections(), or collection(). These methods may be used to retrieve a list of collections or a single collection from the database.
To grep for the collection and document paths in third-party source code you can use the grep command-line utility or use the regEx in Chrome DevTools.
grep -E "\.collection\(['\"][^'\"]+['\"]\)\." -r ./*
grep -oP '(?<=db\.collection\()(.*?)(?=\))|(?<=doc\()(.*?)(?=\))' -r ./*b
You can use a similar approach to search for collection and document paths in other types of source code, such as Android code or Chrome DevTools. Just be sure to use the appropriate pattern that matches the way collection and document paths are used in the source code.
Brute Forcing Collections Of A Single Instance Of Firestore Database
app = firebase.initializeApp(TestfirebaseConfig, Date.now().toString());
app.auth().signInWithCustomToken("JWT_TOKEN");
db = app.firestore();
await fetch(
"https://raw.githubusercontent.com/drtychai/wordlists/master/sqlmap/common-tables.txt")
.then((response) => response.text()).then((text) => {
text.split('\n').forEach(line => {
if ((line.indexOf("#") == -1) && line != "") {
db.collection(line).get().then((querySnapshot) => {
console.log(JSON.stringify(querySnapshot.data))
})
}
});
}).catch(() => null);
Testing For Operation Access Issues
Get
Here’s an example of how you might use this method to test authorization for access to a collection using JavaScript code:
firebase.firestore().collection('users').doc(user.uid).get().then(function (doc) {
console.log('Document data:', doc.data());
}).catch(function (error) {
console.error('Error:', error);
});
In this example, the get() method is trying to read a document with the user’s unique ID from the “users” collection. If the security rules for the collection allow the current user to read the document, the promise will resolve with the document data. If the security rules do not allow the current user to read the document, the promise will reject with an error.
Add
You can use this approach to test authorization for various actions on a collection, such as reading, writing, or deleting documents. For example, you could try to write a new document to the collection like this:
firebase.firestore().collection('users').add({
name: 'Test User',
email: 'test@example.com'
}).then(function (doc) {
console.log('Document added with ID:', doc.id);
}).catch(function (error) {
console.error('Error:', error);
});
In this example, the add() method is trying to write a new document to the “users” collection. If the security rules for the collection allow the current user to write the document, the promise will resolve with the newly created document. If the security rules do not allow the current user to write the document, the promise will reject with an error.
Update
To update a document in Cloud Firestore using the JavaScript client library, you can use the update() method of the DocumentReference class. Here is an example of how you can update a document:
// Get a reference to the document to be updated
var docRef = db.collection('collection').doc('document');
// Update the document
docRef.update({
field: 'new value'
}).then(function() {
console.log('Document updated successfully');
}).catch(function(error) {
console.error('Error updating document: ', error);
});
docRef.update({
field1: 'new value 1',
field2: 'new value 2'
});
Delete
// Get a reference to the document to be deleted
var docRef = db.collection('collection').doc('document');
// Delete the document
docRef.delete().then(function() {
console.log('Document successfully deleted');
}).catch(function(error) {
console.error('Error deleting document: ', error);
});
Brute Forcing Multiple Instances Of Firestore Databases
async function BF(TestfirebaseConfig) {
// console.log(TestfirebaseConfig.databaseUrl);
app = firebase.initializeApp(TestfirebaseConfig, Date.now().toString());
app.auth().signInWithCustomToken("JWT_TOKEN");
db = app.firestore();
await fetch(
"https://raw.githubusercontent.com/drtychai/wordlists/master/sqlmap/common-tables.txt")
.then((response) => response.text()).then((text) => {
text.split('\n').forEach(line => {
if ((line.indexOf("#") == -1) && line != "") {
db.collection(line).get().then((querySnapshot) => {
console.log(JSON.stringify(querySnapshot.data))
})
}
});
}).catch(() => null);
}
async function BF_ALL() {
var appsToTest = [{
"google_app_id": "",
"firebase_database_url": "https://xxxx.firebaseio.com",
"App": "xxxx",
"google_api_key": "AIzaXXXXXXXXXXXXX"
}
//more apps configs
]
var TestfirebaseConfig = null;
var app = null;
var db = null;
for (var i = 0; i < appsToTest.length; i++) {
app = appsToTest[i];
if (typeof app.firebase_sender_id !== 'undefined') {
console.log(JSON.stringify(app));
TestfirebaseConfig = {
"apiKey": app.google_api_key,
"authDomain": app.firebase_project_id + ".firebaseapp.com",
"databaseUrl": app.firebase_database_url,
"projectId": app.firebase_project_id,
"storageBucket": app.firebase_project_id + ".appspot.com",
"messagingSenderId": app.firebase_sender_id,
"appId": app.google_app_id
};
console.log(JSON.stringify(TestfirebaseConfig));
BF(TestfirebaseConfig);
}
if ((typeof app.firebase_database_url !== 'undefined') && (typeof app.google_app_id !==
'undefined') && (typeof app.google_api_key !== 'undefined')) {
var
projectName = app.firebase_database_url.substring(app.firebase_database_url.lastIndexOf("/") + 1,
app.firebase_database_url.indexOf("."));
TestfirebaseConfig = {
"apiKey": app.google_api_key,
"authDomain": projectName + ".firebaseapp.com",
"databaseUrl": app.firebase_database_url,
"projectId": projectName,
"storageBucket": projectName + ".appspot.com",
"messagingSenderId": "",
"appId": app.google_app_id
};
console.log(JSON.stringify(TestfirebaseConfig));
await BF(TestfirebaseConfig);
console.log(i);
}
}
}
# Getting Firebase connection details from APKs
{
# Extract a list of APKs using Jadx into a single folder and run this code to extract Firestore connection details
# FOR %I in (*.*) do ("jadx.bat" "%I" --output-dir "%I_o" --export-gradle)
$ResultsArrayList = [System.Collections.ArrayList]::new()
$dirs = Get-ChildItem -Directory
foreach ($dir in $dirs) {
[xml]$XDoc = Get-Content "./$($dir.Name)/app/src/main/res/values/strings.xml"
echo $dir.Name
$ele = @{}
$ele.add("App", $dir.Name)
if ($Xdoc.resources.string.name.indexof("firebase_database_url") -ne -1) { $ele.Add("firebase_database_url", $Xdoc.resources.string.get($Xdoc.resources.string.name.indexof("firebase_database_url")).innerXML) }
if ($Xdoc.resources.string.name.indexof("google_app_id") -ne -1) { $ele.Add("google_app_id", $Xdoc.resources.string.get($Xdoc.resources.string.name.indexof("google_app_id")).innerXML) }
if ($Xdoc.resources.string.name.indexof("google_api_key") -ne -1) { $ele.Add("google_api_key", $Xdoc.resources.string.get($Xdoc.resources.string.name.indexof("google_api_key")).innerXML) }
if ($Xdoc.resources.string.name.indexof("firebase_google_api_key") -ne -1) { $ele.Add("firebase_google_api_key", $Xdoc.resources.string.get($Xdoc.resources.string.name.indexof("firebase_google_api_key")).innerXML) }
if ($Xdoc.resources.string.name.indexof("firebase_project_id") -ne -1) { $ele.Add("firebase_project_id", $Xdoc.resources.string.get($Xdoc.resources.string.name.indexof("firebase_project_id")).innerXML) }
if ($Xdoc.resources.string.name.indexof("firebase_release_google_app_id") -ne -1) { $ele.Add("firebase_release_google_app_id", $Xdoc.resources.string.get($Xdoc.resources.string.name.indexof("firebase_release_google_app_id")).innerXML) }
if ($Xdoc.resources.string.name.indexof("firebase_sender_id") -ne -1) { $ele.Add("firebase_sender_id", $Xdoc.resources.string.get($Xdoc.resources.string.name.indexof("firebase_sender_id")).innerXML) }
$ResultsArrayList.add($ele)
}
}
Testing Cloud Functions
Cloud functions are somehow similar to AWS Lambda functions. They can be accessed externally and may expose sensitive features/actions to an unauthorized caller.
Intro
Cloud Functions for Firebase is a serverless platform that allows you to run your code in response to events triggered by Firebase and Google Cloud services. You can use Cloud Functions to implement backend logic for your Firebase app, extend and connect Firebase services, and automate tasks.
With Cloud Functions, you can write code in Node.js that is executed in response to a specific trigger, such as a change in data in a Firebase Realtime Database, the creation of a new document in a Cloud Firestore collection, or the receipt of a new message in a Cloud Pub/Sub topic. The code you write is hosted and executed in a fully managed environment, so you don’t need to worry about scaling, monitoring, or maintaining infrastructure.
Cloud Functions is particularly useful for implementing backend logic that integrates with other Firebase and Google Cloud services, such as sending push notifications, triggering cloud storage operations, or performing data analysis with BigQuery. It’s also a powerful tool for automating tasks, such as optimizing images, validating data, or sending emails.
To use Cloud Functions, you’ll need to set up a Firebase project, install the Firebase CLI, and write and deploy your functions using the Firebase Console or the CLI. You can also use the Cloud Functions for Firebase client libraries to interact with your functions from your app.
Extracting Function Names
To find references to Firebase Cloud Functions in third-party Android and JavaScript code:
- JavaScript code — search for firebase.functions() in the JavaScript codebase.
- Android — search for import com.google.firebase.functions.FirebaseFunctions in the Android decompiled source.
- Look for keywords and patterns that are commonly used with Firebase Cloud Functions, such as .httpsCallable, .onCall, and .runWith.
Once you have found references to Firebase Cloud Functions in the code, you can review the code to understand how the functions are being used and whether they can be abused.
Brute Forcing Function Names
function bfCloudFunction() {
var firebaseConfig = {
"apiKey": "AIXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"authDomain": "PROJECTNAME.firebaseapp.com",
"databaseUrl": "https://PROJECTNAME.firebaseio.com",
"projectId": "PROJECTNAME",
"storageBucket": "PROJECTNAME.appspot.com",
"messagingSenderId": 1111111111111,
"appId": ""
}
// Initialize Firebase WEB
var app = firebase.initializeApp(firebaseConfig, Date.now().toString());
app.auth().signInWithCustomToken("JWT_TOKEN");
var db = app.firestore();
var fbFunctions = app.functions();
fetch(
"https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/Web-Content/burp-parameter-names.txt"
)
.then((response) => response.text()).then((text) => {
text.split('\n').forEach(line => {
if ((line.indexOf("#") == -1) && line != "") {
var addMessage = fbFunctions.httpsCallable(line);
addMessage({
text: "messageText"
})
.then((result) => {
// Read result of the Cloud Function.
console.log(JSON.stringify(result.data.text));
});
}
});
});
}