Skip to content

[Bug][Level 2] firestore.rules: referrals update rule allows the same uid to be appended to usedBy multiple times, enabling unlimited referral point farming #83

@anshul23102

Description

@anshul23102

Description

The referrals/{uid} update rule in firestore.rules checks that:

  1. The usedBy array grows by exactly one element.
  2. The new element is the current caller's UID.
  3. totalEarned increases by exactly 100.
allow update: if isOwner(uid) || (
  isAuthenticated()
  && request.resource.data.usedBy.size() == resource.data.usedBy.size() + 1
  && request.resource.data.usedBy[resource.data.usedBy.size()] == request.auth.uid
  && request.resource.data.totalEarned == resource.data.totalEarned + 100
);

The rule checks that the incoming usedBy array has exactly one more element than the current one, and that the last element matches the caller. However, it does NOT verify that the caller's UID is not already present in usedBy. Firestore arrays allow duplicate values. If usedBy currently contains ["user_A"], user A can submit another update with usedBy: ["user_A", "user_A"], which satisfies:

  • usedBy.size() == 1 + 1 (true)
  • usedBy[1] == request.auth.uid (true, because usedBy[1] = "user_A" = request.auth.uid)
  • totalEarned == current + 100 (true)

This means the same user can trigger the referral reward multiple times against the same referrer, earning 100 points per call without limit.

This vulnerability compounds with RULE 3 in the users/{uid} rules: each successful referrals write also triggers a corresponding users/{referrerUid} update that adds 100 referral points, because RULE 3 only checks the numeric increment, not uniqueness.

Steps to Reproduce

  1. Sign in as User A. User B refers User A with a referral code.
  2. User A redeems the referral: usedBy becomes ["user_A"], totalEarned becomes 100.
  3. User A immediately sends another write: usedBy: ["user_A", "user_A"], totalEarned: 200.
  4. Observe that step 3 succeeds and User B's referralPoints increase to 200.
  5. Repeat to farm unlimited referral points for User B.

Expected Behavior

A user's UID should only appear once in usedBy. The rule should verify that the caller's UID is not already in the existing usedBy array before allowing the append.

Actual Behavior

The rule allows the same UID to be appended repeatedly because it only checks array size growth, not UID uniqueness.

Affected Code

firestore.rules, referrals/{uid} update rule:

&& request.resource.data.usedBy.size() == resource.data.usedBy.size() + 1
&& request.resource.data.usedBy[resource.data.usedBy.size()] == request.auth.uid
// missing: && !(request.auth.uid in resource.data.usedBy)

Suggested Fix

Add a uniqueness check:

allow update: if isOwner(uid) || (
  isAuthenticated()
  && !(request.auth.uid in resource.data.usedBy)       // prevent duplicate entries
  && request.resource.data.usedBy.size() == resource.data.usedBy.size() + 1
  && request.resource.data.usedBy[resource.data.usedBy.size()] == request.auth.uid
  && request.resource.data.totalEarned == resource.data.totalEarned + 100
);

Contribution Guidelines

I would like to work on this under NSoC '26. @indresh404, could you please assign/ this issue to me?

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions