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/channelThe 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.