How Firestore was used in the Arc code injection vulnerability


News landed this/last week about a (since plugged) vulnerability in the Arc browser due to the way it stores (and secured) user settings in the Firestore database: gaining access to anyones browser without them even visiting a website.

As usual, there is quite some noise around the problem, but (also as usual) there are good things to learn from it. I’ll try to clarify below.

Disclosure: I was on the Firebase team at Google for a decade and helped a lot of developers with their Firestore security rules. While I still have the requisite knowledge, I no longer work at (nor speak for) Firebase or Google.

How Arc uses Firestore

From the CVE-2024-45489 Incident Report on Arc.net:

Arc has a feature called Boosts that allows you to customize any website with custom CSS and Javascript.

So Arc allows you to configure bits of CSS and script to apply to specific web sites when you visit them. And (from the same link) Arc stores this information in Firestore, so that it can easily be shared across devices (for the same user).

This in itself is a meaningful use-case, and seems like a good usage of Firestore too.

Where the vulnerability comes from

Continuing from Arc.net:

Unfortunately our Firebase ACLs (Access Control Lists, the way Firebase secures endpoints) were misconfigured, which allowed users Firebase requests to change the creatorID of a Boost after it had been created.

Access to Firestore data is protected by server-side security rules, which the developer writes as part of implementing Firestore. Since Firestore is used directly from the client/browser here, writing these rules replaces writing your own application server.

Diving in a bit deeper, xyzeva writes how they were able to transfer ownership of their Boost document to another user and The Firestore vulnerability found in Arc is likely widespread shows the likely rules for that, based on this example from the Firebase documentation:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow public read access, but only content owners can write
    match /some_collection/{document} {
      allow read: if true
      allow create: if request.auth.uid == request.resource.data.author_uid;
      allow update, delete: if request.auth.uid == resource.data.author_uid;
    }
  }
}

These rules control all access to the documents in some_collection. For full documentation on this bespoke language, I recommend reading the documentation here and here, but in this specific example:

  • request.auth is the user that is signed in to Firebase Authentication on the client/browser. This information is securely transfered from the client to the (database) server.
  • resource.data is the data for the document as it exists in the database, before the write operation.
  • request.resource.data is the data for the document that is being written, or more accurately: the data for the document as it will exist if the write (create or update) operation is allowed.

For these specific rules:

  • The allow create rule here says that you can only create a document if you specify your own user ID as the author_uid field in that (new) document.
  • The allow update rule here says that you can only update the document if your own user ID is the current value of the author_uid field in the document.

The crux is in that emphasized part in the update rule: this rule allows the current owner of a document to set somebody else (in fact: any other value) as the author_uid. So it allows the owner of the document to transfer ownershow to someone else.

There are plenty of good reasons to allow this use-case (like transfering items in a game), but given that Arc stores executable code in the document that it then runs in the owner’s browser, this allows a malicious user to write code in their own Boost document and then transfer that document to a target user who will then end up runing the same code in their browser. 😱

How to discover a target user’s ID

In most Firebase implementation the user IDs are freely shared, as they are not meant to be any sort of security mechanism. Luckily in the case of Arc that does not seem to be the case, and the researcher describes this angle to get a target’s user ID in their post:

when someone referrs you to arc, or you [refer] someone to arc, you automatically get their user id in the user_referrals table, which means you could just ask someone for their arc invite code and they’d likely give it

So here it takes some social engineering to get the user ID, but in many (especially social media like) apps that use Firebase/Firestore the UIDs are likely going to be easy to harvest in bulk.

How to fix this vulnerability

The simplest way to fix this vulnerability is to make the author_uid field immutable in the security rules, so to ensure it can’t be updated. Here’s one way to do that:

allow update: if request.auth.uid == resource.data.author_uid
              && request.resource.data.author_uid == resource.data.author_uid;

In words, that second clause says: ensure that the author_uid after this operation is the same as it was before it.

Since making fields immutable is a quite common requirement, I typically create a helper function for this, so that I can say:

allow update: if request.auth.uid == resource.data.author_uid
              && isNotModified('author_uid');

Conclusion

As said at the start, while there is some noise around this issue, it seems to indeed have been a real vulnerability in the Arc browser and it’s good that this was discovered and patched.

It would probably be good if the example in the Firebase documentation included the protection against changing the author_uid field that I showed above. After all: it’s more likely that devs will think of this specific use-case/abuse-vector if the sample code refers to it.

Magic pixel View count: