Api Rate Abuse on Firebase
How Api Rate Abuse Manifests in Firebase
Firebase's serverless architecture and real-time database capabilities create unique opportunities for rate abuse that differ from traditional API deployments. Understanding these Firebase-specific manifestations is crucial for securing your applications.
Firestore Query Abuse
Firestore's flexible querying can be exploited through aggressive pagination or repeated complex queries. Attackers can trigger expensive operations by repeatedly requesting large datasets or performing unbounded queries:
const db = firebase.firestore();
// Vulnerable: No rate limiting on queries
exports.getUserData = functions.https.onRequest((req, res) => {
const userId = req.query.userId;
db.collection('users')
.doc(userId)
.collection('largeCollection')
.orderBy('timestamp', 'desc')
.limit(1000)
.get()
.then(snapshot => res.json(snapshot.docs.map(doc => doc.data())))
.catch(err => res.status(500).send(err.message));
});This endpoint has no protection against a single client making thousands of requests, potentially exhausting Firestore read operations and incurring significant costs.
Realtime Database Flood
Firebase Realtime Database's WebSocket connections can be abused to create connection floods. Each client maintains an open connection, and without proper authentication or rate limiting, attackers can overwhelm your database:
// Vulnerable: No authentication or rate limiting
exports.streamData = functions.https.onRequest((req, res) => {
const ref = admin.database().ref('sensitive-data');
ref.on('value', snapshot => {
res.json(snapshot.val());
});
});An attacker could repeatedly call this endpoint, creating numerous WebSocket connections that consume bandwidth and database resources.
Cloud Functions Invocation Abuse
Cloud Functions for Firebase can be triggered through HTTP requests, Firestore writes, or Realtime Database changes. Without proper controls, these can be abused:
// Vulnerable: No throttling on function execution
exports.processPayment = functions.https.onRequest((req, res) => {
const { amount, userId } = req.body;
// No rate limiting - could be called thousands of times per second
processPaymentLogic(amount, userId);
res.json({ success: true });
});This payment processing function could be called repeatedly by a single user or bot, potentially leading to duplicate charges or system overload.
Authentication Endpoint Abuse
Firebase Authentication endpoints are particularly vulnerable to rate abuse, especially during brute-force attacks:
// Vulnerable: Default Firebase Auth rate limits may be insufficient
exports.login = functions.https.onRequest((req, res) => {
const { email, password } = req.body;
admin.auth().signInWithEmailAndPassword(email, password)
.then(user => res.json(user))
.catch(err => res.status(401).send(err.message));
});Without custom rate limiting, this endpoint could be hammered with credential stuffing attempts or brute-force attacks.
Firebase-Specific Detection
Detecting rate abuse in Firebase requires monitoring both application-level metrics and Firebase-specific telemetry. Here's how to identify these issues:
Firebase Console Monitoring
The Firebase Console provides built-in monitoring for Cloud Functions, Firestore, and Realtime Database. Look for:
- Sudden spikes in function invocations or execution time
- Increased error rates or timeout errors
- Unusual patterns in database read/write operations
- Authentication failure rate increases
Cloud Logging Integration
Enable detailed logging to track API usage patterns:
// Enhanced logging for rate abuse detection
exports.secureEndpoint = functions.https.onRequest((req, res) => {
const startTime = Date.now();
const clientIP = req.ip || req.connection.remoteAddress;
// Log request details
console.log({
timestamp: startTime,
endpoint: req.originalUrl,
method: req.method,
clientIP,
userAgent: req.get('User-Agent'),
authStatus: req.user ? 'authenticated' : 'unauthenticated'
});
// Your business logic here
const duration = Date.now() - startTime;
console.log({
responseTime: duration,
statusCode: res.statusCode
});
});middleBrick API Security Scanning
middleBrick's Firebase-specific scanning identifies rate abuse vulnerabilities by:
- Testing HTTP endpoints for rate limiting controls
- Analyzing Firestore security rules for query abuse protections
- Checking Cloud Functions for proper throttling
- Evaluating authentication endpoints for brute-force protection
The scanner provides a security score (A–F) and specific findings with remediation guidance. For example, it might detect that your payment processing function lacks rate limiting, or that your authentication endpoint is vulnerable to credential stuffing.
Firebase Extensions for Rate Limiting
Firebase offers extensions specifically designed for rate limiting:
// Using Firebase Rate Limiter Extension
const rateLimiter = require('@firebaseextensions/rate-limiter');
exports.limitedEndpoint = functions.https.onRequest(async (req, res) => {
const limiter = rateLimiter.bucket({
capacity: 100, // Max tokens
refillRate: 10, // Tokens per second
key: req.ip // Rate limit per IP
});
const token = await limiter.get();
if (!token) {
return res.status(429).json({
error: 'Rate limit exceeded',
retryAfter: token ? token.waitTime : 60
});
}
// Process request
});Custom Rate Limiting Implementation
For more granular control, implement custom rate limiting using Firestore or Realtime Database:
const db = admin.firestore();
async function checkRateLimit(clientId, endpoint, limit = 100, window = 60000) {
const now = Date.now();
const windowStart = now - window;
// Store request timestamps
const userRef = db.collection('rateLimits').doc(clientId);
const requestsRef = userRef.collection(endpoint);
// Clean old entries
await requestsRef.where('timestamp', '<', windowStart).delete();
// Count current requests
const snapshot = await requestsRef.get();
const requestCount = snapshot.size;
if (requestCount >= limit) {
return false; // Rate limit exceeded
}
// Record this request
await requestsRef.add({ timestamp: now });
return true;
}
exports.secureEndpoint = functions.https.onRequest(async (req, res) => {
const clientId = req.ip || req.body.userId;
const endpoint = req.originalUrl;
const allowed = await checkRateLimit(clientId, endpoint, 100, 60000);
if (!allowed) {
return res.status(429).json({
error: 'Rate limit exceeded',
retryAfter: 60
});
}
// Process request
});Firebase-Specific Remediation
Securing Firebase applications against rate abuse requires a multi-layered approach using Firebase's native capabilities. Here are specific remediation strategies:
Firestore Security Rules with Rate Limiting
Implement rate limiting directly in Firestore security rules:
// Rate limiting using document count in security rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId}/messages/{messageId} {
// Allow max 100 writes per 60 seconds per user
allow write: if
request.auth != null &&
existsAfter(/databases/$(database)/documents/rateLimits/$(request.auth.uid)/$(resource.parent.name)) == false ||
get(/databases/$(database)/documents/rateLimits/$(request.auth.uid)/$(resource.parent.name))
.data.count < 100;
}
match /rateLimits/{userId}/{endpoint} {
allow read, write: if false; // Internal use only
}
}
}Cloud Functions Rate Limiting Middleware
Create reusable rate limiting middleware for your Cloud Functions:
const rateLimit = require('express-rate-limit');
// Firebase-specific rate limiter with IP-based and user-based limits
const createFirebaseRateLimiter = (options = {}) => {
const {
max = 100,
windowMs = 60000,
keyGenerator = req => req.ip,
skipAuthenticated = false
} = options;
return rateLimit({
max,
windowMs,
keyGenerator: skipAuthenticated ?
req => req.user ? req.user.uid : req.ip :
keyGenerator,
standardHeaders: true,
legacyHeaders: false,
handler: (req, res, next) => {
res.status(429).json({
error: 'Rate limit exceeded',
retryAfter: Math.ceil((Date.now() - req.rateLimit.resetTime) / 1000),
message: 'Too many requests. Please try again later.'
});
}
});
};
// Apply to your functions
const generalLimiter = createFirebaseRateLimiter({
max: 100,
windowMs: 60000
});
exports.secureEndpoint = functions.https.onRequest(
generalLimiter,
(req, res) => {
// Your secure logic here
res.json({ message: 'Request processed successfully' });
}
);Firebase Authentication Rate Limiting
Implement custom rate limiting for authentication endpoints:
const db = admin.firestore();
// Track failed login attempts
const MAX_FAILED_ATTEMPTS = 5;
const LOCKOUT_PERIOD = 15 * 60 * 1000; // 15 minutes
async function checkAuthRateLimit(email) {
const userRef = db.collection('authLocks').doc(email);
const doc = await userRef.get();
if (doc.exists) {
const data = doc.data();
const now = Date.now();
// Check if still in lockout period
if (now - data.lastAttempt < LOCKOUT_PERIOD) {
if (data.failedAttempts >= MAX_FAILED_ATTEMPTS) {
return {
allowed: false,
reason: 'Account temporarily locked',
retryAfter: Math.ceil((LOCKOUT_PERIOD - (now - data.lastAttempt)) / 1000)
};
}
} else {
// Reset counter after lockout period
await userRef.update({
failedAttempts: 0,
lastAttempt: now
});
}
}
return { allowed: true };
}
async function recordAuthAttempt(email, success) {
const userRef = db.collection('authLocks').doc(email);
const doc = await userRef.get();
if (success) {
// Clear on successful login
if (doc.exists) {
await userRef.delete();
}
} else {
// Increment failed attempts
if (doc.exists) {
await userRef.update({
failedAttempts: admin.firestore.FieldValue.increment(1),
lastAttempt: admin.firestore.FieldValue.serverTimestamp()
});
} else {
await userRef.set({
failedAttempts: 1,
lastAttempt: admin.firestore.FieldValue.serverTimestamp()
});
}
}
}
exports.login = functions.https.onRequest(async (req, res) => {
const { email, password } = req.body;
// Check rate limit
const rateLimitCheck = await checkAuthRateLimit(email);
if (!rateLimitCheck.allowed) {
return res.status(429).json({
error: rateLimitCheck.reason,
retryAfter: rateLimitCheck.retryAfter
});
}
try {
const user = await admin.auth().signInWithEmailAndPassword(email, password);
await recordAuthAttempt(email, true);
res.json(user);
} catch (error) {
await recordAuthAttempt(email, false);
res.status(401).json({
error: 'Invalid credentials',
code: error.code
});
}
});Firebase Extensions for Advanced Rate Limiting
Utilize Firebase Extensions for sophisticated rate limiting:
// Using Firebase Rate Limiter Extension
const rateLimiter = require('@firebaseextensions/rate-limiter');
exports.processPayment = functions.https.onRequest(async (req, res) => {
const limiter = rateLimiter.bucket({
capacity: 5, // Max 5 requests
refillRate: 1, // 1 token per minute
key: req.user ? req.user.uid : req.ip
});
const token = await limiter.get();
if (!token) {
return res.status(429).json({
error: 'Rate limit exceeded',
retryAfter: token ? token.waitTime : 60
});
}
try {
// Payment processing logic
await processPayment(req.body);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});Monitoring and Alerting
Set up Firebase Crashlytics and Cloud Monitoring to detect rate abuse patterns:
// Enhanced monitoring for rate abuse detection
exports.monitoredEndpoint = functions.https.onRequest(async (req, res) => {
const startTime = Date.now();
const clientIP = req.ip;
try {
// Your business logic
const result = await processRequest(req.body);
// Monitor for suspicious patterns
if (shouldTriggerAlert(req)) {
console.warn('Suspicious activity detected', {
clientIP,
endpoint: req.originalUrl,
timestamp: startTime,
userAgent: req.get('User-Agent'),
requestData: req.body
});
}
res.json(result);
} catch (error) {
console.error('Endpoint error', {
clientIP,
endpoint: req.originalUrl,
error: error.message,
stack: error.stack
});
res.status(500).json({ error: 'Internal server error' });
} finally {
const duration = Date.now() - startTime;
console.log('Request completed', {
responseTime: duration,
statusCode: res.statusCode
});
}
});