Introduction

Recently, while working on a project called Campus Rides, hosted at campusrides.vercel.app, I decided to perform a security review of the application. The platform allows university students to coordinate shared rides and includes features such as ride booking, chat between users, and notifications.

Since the application relies heavily on Firebase Firestore for backend storage, I began by reviewing its Firestore security rules. What I found was a subtle but serious misconfiguration that allowed any authenticated user to read and modify private chat messages between other users.

This article explains:

  • How the vulnerability was discovered
  • Why the Firestore rules were insecure
  • How the issue could be exploited
  • How the rules should be fixed properly

Understanding the Backend

The application uses Firebase Firestore as its database. Firestore relies on security rules to enforce authorization.

For chat functionality, messages were stored under the following path:

/universities/{univId}/chats/{chatId}/messages/{messageId}

Each chat contains multiple participants and a subcollection of messages.

At first glance, the rules looked fairly detailed and well structured.

However, when reviewing the messages rule, a critical issue appeared.

The Vulnerable Firestore Rule

The rule responsible for protecting messages looked like this:

match /messages/{messageId} {
  allow create: if isAuth() && request.resource.data.senderId == request.auth.uid;
  allow get, list: if isAuth();
  allow update: if isAuth();
  allow delete: if false;
}

Why This Is Vulnerable

The rule only checks whether the user is authenticated:

allow get, list: if isAuth();

This means:

  • Any logged-in user can read messages
  • No verification that the user is a participant of the chat

As a result, any authenticated user could access messages from chats they were not part of.

Even worse, the following rule allowed message modification:

allow update: if isAuth();

This means any logged-in user could edit messages sent by other users.

This is a classic Broken Access Control vulnerability.

Exploitation Process

To confirm the vulnerability, I performed the following steps.

1. Creating Test Accounts

I created three accounts:

  • Account A
  • Account B
  • Account C (attacker)

Accounts A and B started a private chat and exchanged messages.

Example message:

"shamveelhello"

2. Capturing Firestore Traffic

Using browser developer tools, I observed the Firestore request responsible for streaming chat messages.

Example request:

GET /google.firestore.v1.Firestore/Listen/channel

The response contained the message data:

projects/campus-ride-<project-id>/databases/(default)/documents/universities/fast/chats/<chatId>/messages/<messageId>

This revealed the chatId and messageId.

3. Accessing Messages from an Unauthorized Account

After logging in with Account C, I extracted the Firebase authentication token and sent a request directly to the Firestore REST API.

Example request:

GET /v1/projects/campus-ride-b8063/databases/(default)/documents/universities/fast/chats/<chatId>/messages
Host: firestore.googleapis.com
Authorization: Bearer <Firebase ID Token>

Despite Account C not being a participant, the server returned the private chat messages.

This confirmed that chat messages were accessible to any authenticated user.

Message Tampering

Because the rules also allowed updates from any authenticated user:

allow update: if isAuth();

An attacker could modify messages sent by other users.

Example request:

PATCH /v1/projects/campus-ride-<project-id>/databases/(default)/documents/.../messages/<messageId>

Payload:

{
 "fields": {
   "content": { "stringValue": "HACKED MESSAGE" }
 }
}

this would allow attackers to alter or inject messages into conversations.

Security Impact

The vulnerability could lead to:

  • Exposure of private conversations
  • Disclosure of user identities
  • Message tampering
  • Trust compromise within the platform

In security terms, this is categorized as:

Broken Access Control (OWASP Top 10: A01).

The Correct Fix

The rule should ensure that only participants of a chat can read or modify its messages.

A secure rule would look like this:

match /messages/{messageId} {
  function isParticipant() {
    return request.auth.uid in
      get(/databases/$(database)/documents/universities/$(univId)/chats/$(chatId)).data.participants;
  }
  allow create: if isAuth()
    && request.resource.data.senderId == request.auth.uid
    && isParticipant();
  allow get, list: if isAuth() && isParticipant();
  allow update: if isAuth()
    && resource.data.senderId == request.auth.uid;
  allow delete: if false;
}

This ensures:

  • Only chat participants can read messages
  • Only the sender can update their own messages

Lessons Learned

Misconfigured Firestore rules are one of the most common security issues in Firebase applications.

Developers often assume that security checks in the frontend will protect their data. However, Firestore rules are the true security boundary.

Key takeaways:

  • Always validate access based on ownership or participation
  • Never rely solely on request.auth != null
  • Carefully review nested collections and inheritance in Firestore rules

Conclusion

This vulnerability demonstrates how a small oversight in Firestore security rules can expose sensitive user data.

By carefully analyzing backend rules and testing access with multiple accounts, it is possible to identify and validate real-world access control issues.

Developers using Firebase should regularly audit their Firestore rules to ensure that data access is restricted to the correct users.

Proper rule design can prevent serious privacy and integrity violations in production applications.