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.