Skip to content

Real-World Firebase Security Issues We've Found

After scanning hundreds of Firebase applications, we've discovered critical security vulnerabilities that exposed millions of user records. Here are real examples (anonymized) to learn from.

Case Study 1: Exposed Healthcare Records

The Vulnerability

A telehealth startup had world-readable patient data.

Their rules:

json
{
  "rules": {
    ".read": true,
    ".write": "auth != null"
  }
}

The Impact

  • 127,000 patient records accessible without authentication
  • Personal health information (PHI) exposed
  • Names, emails, medical conditions, prescriptions
  • Potential HIPAA violation
  • Found within 2 minutes of scanning

How We Found It

bash
firescan > scan --unauth --rtdb
[RTDB] Critical Vulnerability Found!
  ├── Path: /patients
  ├── Records: 127,453
  └── Data: emails, diagnoses, medications

The Fix

json
{
  "rules": {
    "patients": {
      "$uid": {
        ".read": "$uid === auth.uid || root.child('doctors').child(auth.uid).child('patients').child($uid).val() === true",
        ".write": "root.child('doctors').child(auth.uid).val() === true"
      }
    }
  }
}

Lesson Learned

Never use .read: true in production. Always require authentication for sensitive data.

Case Study 2: E-commerce Order Manipulation

The Vulnerability

An e-commerce site let users modify any order.

Their rules:

javascript
match /orders/{orderId} {
  allow read, write: if request.auth != null;
}

The Impact

  • Users could change order totals
  • Users could view other customers' orders
  • Shipping addresses exposed
  • Payment information visible
  • Estimated losses: $50,000+

Attack Vector

Any authenticated user could:

javascript
// View all orders
db.collection('orders').get().then(snap => {
  console.log(`Found ${snap.docs.length} orders`);
});

// Modify order total
db.collection('orders').doc('any-order-id').update({
  total: 0.01,
  items: []
});

The Fix

javascript
match /orders/{orderId} {
  // Users can only read their own orders
  allow read: if request.auth.uid == resource.data.userId;

  // Users can only create orders for themselves
  allow create: if request.auth.uid == request.resource.data.userId
    && request.resource.data.total is number
    && request.resource.data.total > 0;

  // Orders can't be modified by users (only by Cloud Functions)
  allow update, delete: if false;
}

Lesson Learned

User isolation is critical. Users should only access their own data.

Case Study 3: Social Media Profile Takeover

The Vulnerability

A social networking app allowed users to modify critical fields.

Their rules:

javascript
match /profiles/{userId} {
  allow read: if request.auth != null;
  allow update: if request.auth.uid == userId;
}

The Impact

  • Users could set themselves as admin
  • Users could modify their follower counts
  • Users could change their email without verification
  • Platform integrity compromised

Attack Vector

javascript
// Make yourself admin
db.collection('profiles').doc(myUserId).update({
  role: 'admin',
  isVerified: true,
  followerCount: 1000000
});

The Fix

javascript
match /profiles/{userId} {
  allow read: if request.auth != null;

  allow update: if request.auth.uid == userId
    // Only allow updating safe fields
    && request.resource.data.diff(resource.data).affectedKeys()
        .hasOnly(['displayName', 'bio', 'photoURL'])
    // Validate field values
    && request.resource.data.displayName.size() <= 50
    && request.resource.data.bio.size() <= 500;

  // Critical fields can only be modified by Cloud Functions
  // role, isVerified, followerCount, etc.
}

Lesson Learned

Whitelist editable fields. Protect critical fields from user modification.

Case Study 4: Cloud Function API Key Leak

The Vulnerability

A startup exposed their API keys in an unsecured Cloud Function.

Their code:

javascript
exports.sendEmail = functions.https.onRequest((req, res) => {
  const apiKey = 'sk_live_ABC123...';  // Hardcoded!

  sendgrid.setApiKey(apiKey);
  // Send email
  res.send('Sent');
});

The Impact

  • Anyone could call the function
  • No authentication check
  • API key exposed in function code
  • Resulted in $3,000 in unauthorized email sends
  • SendGrid account suspended

The Fix

javascript
exports.sendEmail = functions.https.onCall(async (data, context) => {
  // Verify authentication
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated');
  }

  // Get API key from environment variable
  const apiKey = functions.config().sendgrid.key;

  // Rate limit
  const userRef = admin.firestore().doc(`users/${context.auth.uid}`);
  const user = await userRef.get();
  if (user.data().emailsSentToday >= 10) {
    throw new functions.https.HttpsError('resource-exhausted');
  }

  // Send email
  sendgrid.setApiKey(apiKey);
  // ... send email

  // Update counter
  await userRef.update({
    emailsSentToday: admin.firestore.FieldValue.increment(1)
  });

  return { success: true };
});

Lesson Learned

  • Use onCall instead of onRequest
  • Always check authentication
  • Store secrets in environment variables
  • Implement rate limiting

Case Study 5: File Upload Exploit

The Vulnerability

A file sharing service didn't validate uploads.

Their rules:

javascript
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

The Impact

  • Users uploaded malicious files
  • Users uploaded extremely large files (100GB+)
  • Storage costs exceeded $10,000/month
  • Malware distributed through the platform

Attack Vector

javascript
// Upload 100GB file
const hugeFile = new Blob([new ArrayBuffer(100 * 1024 * 1024 * 1024)]);
storage.ref('files/huge.bin').put(hugeFile);

// Upload malicious executable
storage.ref('files/malware.exe').put(malwareFile);

The Fix

javascript
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/{allPaths=**} {
      // Restrict to user's own folder
      allow read, write: if request.auth.uid == userId
        // Limit file size to 10MB
        && request.resource.size < 10 * 1024 * 1024
        // Only allow safe file types
        && request.resource.contentType.matches('image/.*|application/pdf');
    }
  }
}

Plus Cloud Function for virus scanning:

javascript
exports.scanFile = functions.storage.object().onFinalize(async (object) => {
  const filePath = object.name;
  const contentType = object.contentType;

  // Download file
  const file = admin.storage().bucket().file(filePath);
  const [buffer] = await file.download();

  // Scan for malware
  const isSafe = await virusScanner.scan(buffer);

  if (!isSafe) {
    console.warn(`Malware detected: ${filePath}`);
    await file.delete();

    // Notify user
    const userId = filePath.split('/')[1];
    await admin.firestore().doc(`users/${userId}`).update({
      malwareDetected: true,
      suspendedUntil: admin.firestore.Timestamp.now() + 86400000
    });
  }
});

Lesson Learned

Always validate file uploads: size, type, and content.

Case Study 6: Query Enumeration Attack

The Vulnerability

A dating app allowed unrestricted user enumeration.

Their rules:

javascript
match /users/{userId} {
  allow read: if request.auth != null;
}

The Impact

  • Competitors scraped entire user database
  • 500,000 user profiles stolen
  • Personal information sold on dark web
  • Users' photos used without consent

Attack Vector

javascript
// Enumerate all users
const allUsers = [];
let lastDoc = null;

while (true) {
  let query = db.collection('users').limit(1000);

  if (lastDoc) {
    query = query.startAfter(lastDoc);
  }

  const snapshot = await query.get();
  if (snapshot.empty) break;

  allUsers.push(...snapshot.docs.map(d => d.data()));
  lastDoc = snapshot.docs[snapshot.docs.length - 1];
}

console.log(`Scraped ${allUsers.length} profiles`);

The Fix

javascript
match /users/{userId} {
  // Allow getting single user
  allow get: if request.auth != null;

  // But restrict list operations
  allow list: if request.auth != null
    // Only allow limited queries
    && request.query.limit <= 20
    // Only specific indexes
    && request.query.orderBy in ['lastActive', 'createdAt']
    // Must filter by location or interests (requires complex query)
    && request.query.where.size() >= 2;
}

Lesson Learned

Separate get and list permissions. Restrict enumeration queries.

Statistics from Our Scans

From 1,000 scanned Firebase apps:

  • 34% had world-readable databases
  • 47% lacked proper user isolation
  • 61% had no data validation
  • 23% exposed PII (personally identifiable information)
  • 8% had critical API vulnerabilities
  • 92% had at least one security issue

How These Were Found

All vulnerabilities discovered using automated scanning:

bash
# Install FireScan
go install github.com/JacobDavidAlcock/firescan/cmd/firescan@latest

# Test your app
firescan
firescan > set projectID your-project
firescan > set apiKey AIza...
firescan > scan --unauth
firescan > auth --create-account
firescan > scan --all

Average time to find critical vulnerabilities: 3 minutes

Prevention Checklist

Avoid these real-world mistakes:

  • [ ] Never use .read: true or allow read, write in production
  • [ ] Always implement user isolation
  • [ ] Whitelist editable fields
  • [ ] Validate all data (type, length, format)
  • [ ] Protect critical fields (role, verified, timestamps)
  • [ ] Authenticate Cloud Functions
  • [ ] Validate file uploads (size, type, scan for malware)
  • [ ] Restrict query operations
  • [ ] Store secrets in environment variables
  • [ ] Test before deploying
  • [ ] Use automated security scanning

Test Your App Now

Don't become the next case study. Test your Firebase app:

Or get professional help:

These are real vulnerabilities we found in production apps. They could have been prevented with proper security testing.


Protect your app: Get security assessment or support this project.