Beast Attack in Firestore
How Beast Attack Manifests in Firestore
The Beast attack in Firestore contexts typically emerges through insecure data access patterns and improper authentication handling. Unlike traditional web applications where Beast attacks exploit SSL/TLS vulnerabilities, Firestore-specific Beast attacks focus on unauthorized data access through misconfigured security rules and client-side authentication bypasses.
A common manifestation occurs when developers implement Firestore queries without proper security rule validation. Consider this vulnerable pattern:
const db = firebase.firestore();
const userId = getUserIdFromSomewhere();
const userDoc = db.collection('users').doc(userId);
The vulnerability here isn't in the client code itself, but in what happens when an attacker modifies the userId parameter. Without proper Firestore security rules, any authenticated user can access any document by simply changing the document ID in their client requests.
Another Firestore-specific Beast attack vector involves collection enumeration. Attackers can discover valid document IDs by analyzing response patterns:
const testDoc = db.collection('users').doc('admin-123');
testDoc.get().then(doc => {
if (doc.exists) {
console.log('Document exists - user found');
} else {
console.log('Document does not exist');
}
});
This enumeration becomes particularly dangerous when combined with Firestore's real-time listeners:
// Vulnerable real-time listener
const userFeed = db.collection('userFeed').doc('currentUserId').collection('posts');
const unsubscribe = userFeed.onSnapshot(snapshot => {
snapshot.forEach(doc => console.log(doc.data()));
});
Without proper security rules, an attacker can subscribe to any user's feed by changing the document ID, effectively performing a real-time Beast attack on user data.
Firestore-Specific Detection
Detecting Beast attacks in Firestore requires both static analysis of security rules and dynamic testing of access patterns. The most effective approach combines automated scanning with manual verification.
middleBrick's Firestore-specific detection includes:
- Security rule analysis for overly permissive read/write operations
- Authentication bypass testing across all Firestore endpoints
- Collection enumeration vulnerability scanning
- Real-time listener access control verification
- Data exposure through improperly secured queries
Here's how middleBrick identifies these vulnerabilities:
// middleBrick CLI scan for Firestore security
middlebrick scan https://firestore.googleapis.com/ --target firestore --auth none
The scanner tests for common Firestore Beast attack patterns:
- Unauthenticated access to public collections
- Missing authentication checks in security rules
- Excessive data exposure through wildcard rules
- Missing index-based access controls
Manual detection techniques include:
// Test for BOLA vulnerabilities
const db = firebase.firestore();
const testIds = ['admin', 'root', 'superuser', 'owner', 'me'];
testIds.forEach(id => {
db.collection('users').doc(id).get()
.then(doc => {
if (doc.exists) {
console.log(`VULNERABLE: ${id} exists`);
}
});
});
Security rule analysis should specifically check for:
// Vulnerable rule - allows any authenticated user to read any user document
match /users/{userId} {
allow read: if request.auth != null;
}
// Secure rule - only allows reading own user document
match /users/{userId} {
allow read: if request.auth.uid == userId;
}
Firestore-Specific Remediation
Remediating Beast attacks in Firestore requires a defense-in-depth approach combining security rules, proper authentication, and input validation. Here are Firestore-specific fixes for common vulnerabilities.
1. Secure Security Rules:
service cloud.firestore {
match /databases/{database}/documents {
// Only allow reading your own user document
match /users/{userId} {
allow read: if request.auth.uid == userId;
allow write: if request.auth.uid == userId;
}
// Secure posts collection - only owners can read/write their posts
match /posts/{postId} {
allow read: if resource.data.authorId == request.auth.uid;
allow write: if request.auth.uid == request.resource.data.authorId;
}
// Secure comments - only post owners and comment authors can access
match /posts/{postId}/comments/{commentId} {
allow read: if
request.auth.uid == resource.data.authorId ||
request.auth.uid == get(/databases/$(database)/documents/posts/$(postId)).data.authorId;
allow write: if request.auth.uid == request.resource.data.authorId;
}
}
}
2. Client-Side Validation:
// Secure client code - validate user context before queries
async function getSecureUserData(userId) {
const currentUser = firebase.auth().currentUser;
if (!currentUser || currentUser.uid !== userId) {
throw new Error('Unauthorized access attempt detected');
}
const userDoc = await firebase.firestore()
.collection('users')
.doc(userId)
.get();
return userDoc.data();
}
// Secure real-time listener with validation
function subscribeToOwnFeed() {
const currentUser = firebase.auth().currentUser;
if (!currentUser) return;
const feedRef = firebase.firestore()
.collection('userFeed')
.doc(currentUser.uid)
.collection('posts');
return feedRef.onSnapshot(snapshot => {
snapshot.forEach(doc => processSecurePost(doc.data()));
});
}
3. Input Validation and Sanitization:
// Validate document IDs to prevent injection attacks
function validateDocumentId(id) {
const validPattern = /^[a-zA-Z0-9_-]{1,100}$/;
if (!validPattern.test(id)) {
throw new Error('Invalid document ID format');
}
return id;
}
// Secure query function with validation
async function getValidatedUserDocument(userId) {
const validatedId = validateDocumentId(userId);
const currentUser = firebase.auth().currentUser;
if (!currentUser || currentUser.uid !== validatedId) {
throw new Error('Access denied');
}
return await firebase.firestore()
.collection('users')
.doc(validatedId)
.get();
}
4. Monitoring and Alerting:
// Monitor for suspicious access patterns
const suspiciousAccess = firebase.functions().httpsCallable('monitorSuspiciousAccess');
async function monitorAccess(userId) {
const currentUser = firebase.auth().currentUser;
if (currentUser.uid !== userId) {
await suspiciousAccess({
attemptedUserId: userId,
actualUserId: currentUser.uid,
timestamp: Date.now(),
ipAddress: getClientIP()
});
}
}