Firebase 安全审计实战:90% 的 BaaS 应用都存在这些配置漏洞

深入分析 Firebase 和 Supabase 常见安全漏洞,包括 Firestore 规则绕过、Storage 权限泄露、API Key 暴露等,附完整审计脚本和修复代码,帮助开发者构建安全的 BaaS 应用。

安全与密码 2026-06-03 15 分钟

一位安全研究员花了 1500 美元让 14 个大模型去攻击一个故意留有漏洞的 App,结果发现 GPT-5.5 的破解率高达 70%,而绝大多数模型找到的漏洞都指向同一个地方——Firebase 的权限配置。这不是个例,根据 2026 年 OWASP 移动安全报告,超过 60% 使用 BaaS(Backend as a Service)的应用存在不同程度的权限配置错误。如果你的项目正在使用 Firebase 或 Supabase,这篇文章可能会帮你避免一次严重的数据泄露。

🔐 一、BaaS 安全漏洞全景:攻击者眼中的 Firebase

1.1 为什么 BaaS 平台成为安全重灾区?

BaaS 的核心卖点是「零后端代码」——前端直接与数据库通信,省去了传统 API 层。但这也意味着安全边界从服务端转移到了配置层。一个错误的 Firestore Security Rule、一个过于宽松的 Supabase RLS 策略,就等于把数据库大门直接敞开。

最常见的攻击模式有三种:

  • 直接读取 Firestore:通过 google-services.json 中的配置信息直接连接 Firebase,绕过你的 API
  • 匿名认证绕过:Firebase 默认允许匿名登录,攻击者可以用匿名身份访问「已认证用户」才能看的数据
  • Storage 公开访问:Firebase Storage 规则配置不当,导致用户私有文件可被任何人下载

⚠️ **警告:**Firebase 的 google-services.json / GoogleService-Info.plist 文件会被打包到 App 中,任何人都可以通过反编译获取你的 Firebase 项目 ID、API Key 和数据库地址。API Key 不是秘密,安全完全依赖 Security Rules。

1.2 一个真实的攻击流程

以那个 1500 美元实验中的漏洞 App 为例,攻击者只需要 4 步:

  1. 反编译 APK,提取 google-services.json
  2. 获取 Firebase 项目 ID 和 API Key
  3. 使用 Firebase SDK 直接连接 Firestore
  4. 查询所有用户的私有数据
// 攻击者只需要这几行代码就能读取你的数据库
// firebase-exploit.js — 通过反编译获取的配置直接连接 Firebase
import { initializeApp } from 'firebase/app'
import { getFirestore, collection, getDocs } from 'firebase/firestore'
import { getAuth, signInAnonymously } from 'firebase/auth'

// 这些信息来自 google-services.json,不是秘密
const firebaseConfig = {
  apiKey: 'AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
  authDomain: 'your-project.firebaseapp.com',
  projectId: 'your-project-id',
  storageBucket: 'your-project.appspot.com',
  messagingSenderId: '123456789',
  appId: '1:123456789:android:abcdef'
}

const app = initializeApp(firebaseConfig)
const db = getFirestore(app)
const auth = getAuth(app)

// 第一步:匿名登录(如果规则允许)
await signInAnonymously(auth)

// 第二步:直接查询所有用户数据
const querySnapshot = await getDocs(collection(db, 'users'))
querySnapshot.forEach(doc => {
  console.log(`用户 ${doc.id}:`, doc.data())
})

📌 **记住:**这个实验中,70% 的成功攻击都是因为 Firestore Security Rules 没有正确配置。API 本身是安全的,但数据库的大门是敞开的。

🛡️ 二、Firebase Security Rules 深度审计

2.1 最危险的规则配置 Top 5

以下是从真实项目中收集的最常见危险配置。如果你的规则命中了任何一条,请立即修复:

// firestore.rules — ❌ 危险配置 Top 5

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // ❌ 危险 #1:允许所有人读写所有数据
    match /{document=**} {
      allow read, write: if true;
    }

    // ❌ 危险 #2:只检查是否登录,不限制数据归属
    match /users/{userId} {
      allow read, write: if request.auth != null;
    }

    // ❌ 危险 #3:允许用户读取其他用户数据
    match /users/{userId}/reviews/{reviewId} {
      allow read: if request.auth != null;
      allow write: if request.auth.uid == userId;
    }

    // ❌ 危险 #4:Storage 规则允许所有已认证用户访问
    match /b/{bucket}/o/{allPaths=**} {
      allow read, write: if request.auth != null;
    }

    // ❌ 危险 #5:没有规则(默认拒绝,但很多人不知道需要显式配置)
    // 如果你完全没有写规则,Firebase 默认是拒绝的
    // 但很多教程会告诉你加上 allow read, write: if true 来「调试」
  }
}

2.2 安全的 Firestore Security Rules 模板

正确的规则应该遵循最小权限原则:每个集合、每条文档都有明确的访问控制。

// firestore.rules — ✅ 生产级安全配置
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // ✅ 辅助函数:检查是否为文档所有者
    function isOwner(userId) {
      return request.auth != null && request.auth.uid == userId;
    }

    // ✅ 辅助函数:验证数据格式
    function isValidReview(data) {
      return data.title is string
        && data.content is string
        && data.rating is number
        && data.rating >= 1 && data.rating <= 5;
    }

    // 用户公开资料:所有人可读,仅自己可写
    match /users/{userId} {
      allow read: if true;
      allow create: if isOwner(userId);
      allow update: if isOwner(userId)
        && request.resource.data.diff(resource.data).affectedKeys()
          .hasOnly(['displayName', 'avatar', 'bio']);
      allow delete: if false;  // 禁止删除,用软删除
    }

    // 用户私有数据:仅所有者可读写
    match /users/{userId}/private/{docId} {
      allow read, write: if isOwner(userId);
    }

    // 书评:所有人可读,仅作者可写
    match /reviews/{reviewId} {
      allow read: if true;
      allow create: if isOwner(request.resource.data.authorId)
        && isValidReview(request.resource.data);
      allow update: if isOwner(resource.data.authorId)
        && request.resource.data.authorId == resource.data.authorId;
      allow delete: if isOwner(resource.data.authorId);
    }

    // 管理员集合:仅自定义 Token 中的 admin claim
    match /admin/{docId} {
      allow read, write: if request.auth != null
        && request.auth.token.admin == true;
    }

    // 默认拒绝所有其他路径
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

2.3 Firebase Storage 安全规则

Storage 的规则同样容易出错。以下是常见的安全配置:

// storage.rules — ✅ 按用户隔离的存储规则
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {

    // 用户头像:所有人可读,仅本人可写且限制文件大小
    match /avatars/{userId}/{allPaths=**} {
      allow read: if true;
      allow write: if request.auth != null
        && request.auth.uid == userId
        && request.resource.size < 5 * 1024 * 1024  // 最大 5MB
        && request.resource.contentType.matches('image/.*');
    }

    // 用户私有文件:仅本人可读写
    match /private/{userId}/{allPaths=**} {
      allow read, write: if request.auth != null
        && request.auth.uid == userId;
    }

    // 默认拒绝
    match /{allPaths=**} {
      allow read, write: if false;
    }
  }
}

💡 **提示:**使用 firebase deploy --only firestore:rules 单独部署规则,不需要重新部署整个项目。每次修改规则后,用 Firebase Emulator 测试确认行为正确。

🔍 三、Supabase RLS 安全配置与审计

Supabase 基于 PostgreSQL 的 Row Level Security(RLS)提供了比 Firebase 更细粒度的权限控制,但如果忘记启用 RLS,效果和 Firebase 的 allow all 一样糟糕。

3.1 最常见的 RLS 配置错误

-- ❌ 错误 #1:创建表后忘记启用 RLS
-- Supabase 默认新表不启用 RLS,这意味着所有已认证用户可以访问所有行
CREATE TABLE profiles (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES auth.users(id),
  email TEXT,
  is_admin BOOLEAN DEFAULT false
);

-- ❌ 错误 #2:创建了 RLS 策略但忘记启用 RLS
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

-- ❌ 错误 #3:策略条件过于宽松
CREATE POLICY "allow_authenticated" ON profiles
  FOR ALL
  USING (auth.role() = 'authenticated');  -- 所有登录用户都能看所有数据

-- ❌ 错误 #4:在 Storage 中使用过于宽松的策略
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true);  -- true = 公开访问

3.2 正确的 Supabase RLS 配置

-- ✅ 正确的 RLS 配置模板
-- Step 1: 创建表并立即启用 RLS
CREATE TABLE profiles (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID UNIQUE REFERENCES auth.users(id) ON DELETE CASCADE,
  display_name TEXT,
  avatar_url TEXT,
  created_at TIMESTAMPTZ DEFAULT now()
);

ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

-- Step 2: 所有人可读公开资料
CREATE POLICY "profiles_select_public" ON profiles
  FOR SELECT USING (true);

-- Step 3: 仅本人可创建自己的资料
CREATE POLICY "profiles_insert_own" ON profiles
  FOR INSERT WITH CHECK (auth.uid() = user_id);

-- Step 4: 仅本人可更新,且不能修改 user_id
CREATE POLICY "profiles_update_own" ON profiles
  FOR UPDATE
  USING (auth.uid() = user_id)
  WITH CHECK (auth.uid() = user_id);

-- Step 5: 禁止删除(使用软删除模式)
CREATE POLICY "profiles_delete_none" ON profiles
  FOR DELETE USING (false);

-- ✅ Storage Bucket 配置:私有 bucket + 按用户隔离
INSERT INTO storage.buckets (id, name, public)
VALUES ('user-files', 'user-files', false);

-- Storage RLS:用户只能访问自己文件夹下的文件
CREATE POLICY "storage_user_isolation" ON storage.objects
  FOR ALL
  USING (
    bucket_id = 'user-files'
    AND auth.uid()::text = (storage.foldername(name))[1]
  );

⚡ **关键结论:**Supabase 的 RLS 比 Firebase Security Rules 更强大(支持完整的 SQL 表达式),但也更容易遗漏。建议在 CI/CD 中加入 RLS 检查脚本,确保每张表都启用了 RLS。

3.3 自动化审计脚本

以下脚本可以检查你的 Supabase 项目中哪些表没有启用 RLS:

-- audit-rls.sql — 检查所有用户表是否启用了 RLS
-- 在 Supabase SQL Editor 中运行

SELECT
  schemaname,
  tablename,
  rowsecurity AS rls_enabled,
  CASE
    WHEN rowsecurity THEN '✅ RLS 已启用'
    ELSE '❌ RLS 未启用 — 危险!'
  END AS status
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;

-- 检查每张表的 RLS 策略数量
SELECT
  schemaname,
  tablename,
  COUNT(pol.policyname) AS policy_count,
  CASE
    WHEN COUNT(pol.policyname) = 0 THEN '❌ 无策略 — 需要添加'
    WHEN COUNT(pol.policyname) < 2 THEN '⚠️ 策略不完整 — 至少需要 SELECT 和 INSERT'
    ELSE '✅ 有 ' || COUNT(pol.policyname) || ' 条策略'
  END AS status
FROM pg_tables t
LEFT JOIN pg_policies pol ON pol.tablename = t.tablename
  AND pol.schemaname = t.schemaname
WHERE t.schemaname = 'public'
GROUP BY t.schemaname, t.tablename
ORDER BY t.tablename;

📊 四、BaaS 安全检查清单与对比

下表总结了 Firebase 和 Supabase 的安全机制对比,帮助你快速判断哪个平台的安全模型更适合你的项目:

安全维度 Firebase Supabase 推荐
默认安全策略 ❌ 默认允许所有访问 ⚠️ 默认关闭 RLS 都需要手动配置
规则语言 自定义 DSL(有限) 完整 SQL(强大) ✅ Supabase
行级安全 ✅ Security Rules ✅ RLS(PostgreSQL 原生) ✅ Supabase
列级安全 ❌ 不支持 ✅ 通过 View 实现 ✅ Supabase
服务端验证 ✅ Admin SDK 绕过规则 ✅ Service Role Key 平手
审计日志 ⚠️ 需要额外配置 ✅ PostgreSQL 原生日志 ✅ Supabase
本地安全测试 ✅ Emulator Suite ✅ Docker 本地环境 平手
规则测试框架 ⠷ Rules Unit Testing ⚠️ 需要自行编写 ✅ Firebase

💡 提示:如果你的团队有 SQL 经验,Supabase 的 RLS 会让你的审计和维护成本大幅降低。如果你是全 JavaScript 团队且项目简单,Firebase 的 Security Rules 也足够用,但一定要写单元测试

⚠️ 五、实战避坑指南

以下是从真实项目中总结的安全教训,每一条都对应过真实的数据泄露事件:

  • 永远不要在客户端代码中信任用户输入 — 即使有 Security Rules,也要在 Cloud Functions / Edge Functions 中做二次验证
  • 不要在 Firestore Rules 中使用 request.auth != null 作为唯一条件 — 这只检查是否登录,不检查是否有权限
  • ⚠️ 注意 Firebase 的 list 操作get 检查单条文档权限,list 检查集合权限,两者规则不同
  • 定期运行安全审计 — 在 CI/CD 中集成 Firebase Security Rules 模拟器测试
  • 不要在 .env 或配置文件中硬编码 Service Account Key — 使用环境变量或 Secret Manager
  • 启用 Firebase App Check — 防止来自非你 App 的请求(爬虫、脚本攻击)
  • ⚠️ Supabase 的 anon key 不是秘密 — 它和 Firebase API Key 一样会暴露在客户端,安全依赖 RLS
// ✅ 正确做法:在 Cloud Functions 中做二次验证
// functions/validateAccess.js
const { onCall, HttpsError } = require('firebase-functions/v2/with')
const { getFirestore } = require('firebase-admin/firestore')

exports.getUserReview = onCall(async (request) => {
  const { auth, data } = request

  // 验证 1:必须已认证
  if (!auth) {
    throw new HttpsError('unauthenticated', '请先登录')
  }

  // 验证 2:只能访问自己的数据
  if (data.userId !== auth.uid) {
    throw new HttpsError('permission-denied', '无权访问他人数据')
  }

  // 验证 3:查询参数验证
  if (!data.reviewId || typeof data.reviewId !== 'string') {
    throw new HttpsError('invalid-argument', '参数无效')
  }

  const db = getFirestore()
  const doc = await db
    .collection('users')
    .doc(data.userId)
    .collection('reviews')
    .doc(data.reviewId)
    .get()

  if (!doc.exists) {
    throw new HttpsError('not-found', '评论不存在')
  }

  return doc.data()
})

🎯 总结

BaaS 安全的核心原则只有一条:假设客户端代码完全公开。你的 google-services.json、Supabase anon key、所有前端代码都会被攻击者看到。安全不能依赖「隐藏」,而必须依赖「验证」。

三条行动建议:

  1. 今天就审计 — 运行本文提供的 SQL 脚本检查你的 Supabase 项目,或用 Firebase Emulator 测试你的 Security Rules
  2. 建立安全 CI/CD — 每次部署前自动运行规则测试,防止规则被意外修改
  3. 关注 OWASP — 每年更新的 OWASP Top 10 会告诉你最新的安全威胁

🔧 相关工具推荐:

📚 相关文章