Firebase Security Fundamentals

Every application built on Firebase that we've looked at has had the same vulnerabilities. These common vulnerabilities aren’t hard to prevent but they're easy to overlook.

Firebase Security Fundamentals

If you’re a new developer building your first Firebase app and thinking about security or you're a penetration tester who’s come across a Firebase application during an engagement, this article is for you.

Firebase is a group of cloud based services offered by Google. Most people describe the platform as a Backend as a service.

Why is Firebase Security Different?

In a traditional web application, your backend server enforces access control.

For example, in a to-do app you may want to prevent users from reading other people's to-do items, the backend code might perform a check to make sure that the requested todo_id actually belongs to the requesting user:

@app.route('/todo/<todo_id>')
def get_todo(todo_id):
    todo = get_todo_by_id(todo_id)

    if not todo:
        abort(404, "Todo not found.")

    # Authorisation check: only allow access if the todo belongs to the user
    if str(todo.user_id) != str(request.user.id):
        abort(403, "You are not authorised to access this todo item.")

    return jsonify(todo)
Traditional web app security

With Firebase, there isn’t a traditional backend server.

Instead, the database is exposed directly to the client, and access control must be enforced through Security Rules rather than server-side code.

Firebase!

What Security Rules are Important?

This needs to be tailored to what application use cases you need to support but generally speaking here are some sane defaults to consider:

  • Users should not be able to list all documents in a collection (database table)
    • This is akin to restricting users from performing a SELECT * FROM todos; query
    • A list operation retrieves all documents in a collection along with the documentId for each document
  • For a given documentId, users should only be able to get, update, replace, or delete records that belong to themself!
💡
If you're a penetration tester these are test cases you want to consider checking.

A Worked Example

Here's what can happen if you don't implement any security rules.

Let's take a look at our victim - a classic todo list app.

You login, and the app lets you add and remove your own todo lists items.

Victim todo list app

Although the frontend does not provide any buttons to interact with anyone else's Todo list items, we can connect directly to underlying Firebase data store using the same SDK to try perform these actions.

Exploitation

In all Firebase apps, the firebaseConfig required by the Firebase SDK needs to be exposed.

If you search for firebaseConfig or authDomain: in the Browser dev tools you'll normally find the details we need to connect directly.

Searching for authDomain:
💡
It might look alarming that an apiKey is exposed but in Firebase projects this is expected.

"None of the Firebase-related APIs use an API key as authorization for calling the API. The API key passed with the API call is only used for identification of the Firebase project or app."

We can use firepwn to exploit the lack of security rules. This is a project that Project Black has contributed to.

GitHub - 0xbigshaq/firepwn-tool: Firepwn is a tool made for testing the Security Rules of a firebase application.
Firepwn is a tool made for testing the Security Rules of a firebase application. - GitHub - 0xbigshaq/firepwn-tool: Firepwn is a tool made for testing the Security Rules of a firebase application.

Give it a star!

Intialise the configuration as follows (the parameters look a bit different because the tool uses the v7 SDK).

Initialising firepwn

To find collection names to query we can search the Frontend code again for collection or alternatively inspect network traffic when using the app.

We see a collection called todos
Looking carefully at network responses to find the same collection

After logging in, we can perform a list operation by performing a Get without specifying a documentId.

In this case, we're able to retrieve todo list items created by other users!

todo list items from other users. The equivalent of select * from todolist

Overwriting/updating our victim's todo item is as simple as changing the operation to Set and specifying a documentId to update.

Overwriting an existing document

Firebase storage is secured the same way with security rules. In the absence of any security rules you're similarly able to retrieve all files stored in the storage and overwrite them as desired.

Implementing Security Rules

To fix our broken access control issue the solution is to check that the userId from authentication matches the userId stored in the document.

request.auth.uid == resource.data.userId

💡
To support rule, our todo list items also need to have an associated userId stored in the document.
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /todos/{todoId} {
      // Allow users to read and write only their own todos
      allow read, write: if request.auth != null \
        && request.auth.uid == resource.data.userId;
      // Allow creating todos if authenticated, only allow creating todos
      // owned by the authenticated user
      allow create: if request.auth != null \
        %& request.auth.uid == request.resource.data.userId;
    }
  }
}

Full example rule.

Trying the same requests now returns this error in firepwn.

:(

Conclusion

Security controls implemented only in the frontend can always be bypassed.

By understanding this you can avoid the same vulnerabilities we see over and over in Firebase apps.


Project Black performs penetration testing in Australia. We're a CREST accredited organisation that can help you understand your gaps.