Firebase中横向校验子集合UID实现读取权限是否存在安全风险?
问题分析:Firestore群聊规则的安全风险
数据结构与场景说明
你的Firestore数据结构如下:
groups/{groupID}/groupInfo groups/{groupID}/groupMembers
其中groupInfo存储群的通用信息,groupMembers存储群成员,用于群聊场景。
你配置的安全规则如下:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // Allow reading groupInfo collection if user's UID is in groupMembers collection match /groups/{groupID}/groupInfo/{document=**} { allow read: if exists(/databases/$(database)/documents/groups/$(groupID)/groupMembers/$(request.auth.uid)); } // Allow reading groupMembers collection match /groups/{groupID}/groupMembers/{document=**} { allow read: if request.auth.uid != null; } } }
你提出的疑问:这种仅当用户UID在groupMembers中时允许读取groupInfo的配置是否存在安全风险?另外,这种横向校验(跨集合文档校验)是否在Firestore中有固有风险?
风险分析与建议
1. 当前配置的直接安全风险
这种配置存在明显的隐私泄露风险:
- 规则允许任何已认证用户(只要
request.auth.uid != null)读取所有群的groupMembers集合。也就是说,只要用户登录,就能查看任意群的全部成员列表,完全违背了私密群聊的隐私逻辑——非群成员无权知晓群内成员信息。 - 攻击者可利用这个权限,批量枚举不同的
groupID,获取大量群的成员信息,再结合groupInfo的校验规则,进一步读取对应群的通用信息,造成大范围信息泄露。
2. 横向校验的固有问题
你提到的横向校验(通过exists()检查另一集合的文档)确实存在两个潜在问题:
- 性能损耗:每次校验
groupInfo的读取权限时,Firestore都需要额外发起一次对groupMembers文档的存在性检查。虽然规则评估的读取不计入收费,但高频的此类操作会增加规则评估的延迟,影响用户体验。 - 枚举攻击隐患:如果攻击者能通过其他渠道获取群ID(或暴力枚举可能的群ID),结合能读取
groupMembers的权限,可以快速验证哪些用户属于哪些群,进而针对性读取groupInfo,扩大攻击范围。
3. 更安全的优化方案
你提到的「上层规则校验通过则允许读取深层集合」的思路是正确的,推荐调整规则如下:
方案一:限制groupMembers的读取权限
让groupMembers的读取权限和groupInfo保持一致,仅允许群成员读取:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /groups/{groupID}/groupInfo/{document=**} { allow read: if exists(/databases/$(database)/documents/groups/$(groupID)/groupMembers/$(request.auth.uid)); } match /groups/{groupID}/groupMembers/{document=**} { allow read: if exists(/databases/$(database)/documents/groups/$(groupID)/groupMembers/$(request.auth.uid)); } } }
方案二:优化校验逻辑,避免跨文档检查
把群成员UID存储在groupInfo的一个数组字段(比如members)中,直接通过resource.data校验,无需跨文档调用exists(),性能更优:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /groups/{groupID}/groupInfo/{document=**} { allow read: if request.auth.uid in resource.data.members; } match /groups/{groupID}/groupMembers/{document=**} { allow read: if request.auth.uid in get(/databases/$(database)/documents/groups/$(groupID)/groupInfo).data.members; } } }
(注:如果groupMembers是集合而非单个文档,也可以保持原exists()逻辑,但必须限制其读取权限仅对群成员开放)
内容的提问来源于stack exchange,提问作者whatwhatwhat
相关产品推荐
相关产品推荐

