TL;DR
Ich habe dieselben Anfragen gegen 11 Firestore-Regelsätze geschickt, als Gast, als anonymer Nutzer und als drei angemeldete Nutzer. request.auth != null ließ einen Fremden die Bestellungen aller lesen, ändern und löschen. Eine Besitzerprüfung allein auf resource ließ einen Nutzer eine Bestellung an jemand anderen übergeben. Ein breites {document=**}-Match öffnete eine Collection, die eine korrekte Besitzerregel hatte. Und eine Rolle in einem Dokument, das der Nutzer selbst schreiben darf, machte ihn mit einer Anfrage zum Admin. Zu jedem Fall gibt es unten eine Korrektur, und die korrigierte Regel hielt gegen dieselben Anfragen.
Zwischen Browser und Datenbank einer Firebase-App sitzt kein Server von Ihnen. Die Projektkonfiguration steckt in der Seite, und Security Rules sind das Einzige, was jeden, der sie hat, von Ihren Daten trennt. Deshalb wollte ich Beispiele für Regeln mit dem Angriff daneben, keine Liste von Regeln, die nur richtig aussehen.
So habe ich getestet. Firebase Local Emulator Suite aus Firebase CLI 15.34.2, am 3. November 2026. Ich habe jeden Regelsatz geladen, die Daten zurückgesetzt und die Firestore-REST-API mit echten Tokens aus dem Auth-Emulator aufgerufen: ohne Token, ein anonymer Nutzer, die Nutzer A und B und ein Fremder, der sich nur registriert hatte. Die Daten waren ausgedacht: Die Bestellungen a1 und a2 gehören A, b1 gehört B.
Welche Firebase-Regeln lecken?
| Regel | Anfrage | Ergebnis |
|---|---|---|
if true (Testmodus) |
Gast listet Bestellungen | 200, alle 3 |
if true (Testmodus) |
Gast löscht Bs Bestellung | 200 |
request.auth != null |
Anonymer Nutzer listet Bestellungen | 200, alle 3 |
request.auth != null |
Fremder löscht As Bestellung | 200 |
Besitzerprüfung nur auf resource |
A legt eine Bestellung von B an | 200 |
Besitzerprüfung nur auf resource |
A ändert ownerId der eigenen Bestellung |
200 |
Besitzerregel plus Lesen auf {document=**} |
A liest Bs Bestellung | 200 |
Nutzer darf eigenes users-Dokument schreiben |
A setzt role: admin, liest Bs Bestellung |
200 |
Was öffnet der Testmodus eigentlich?
match /{document=**} {
allow read, write: if true;
}
Alles, für alle. Ein Gast ohne Token listete alle 3 Bestellungen, löschte eine und legte eine neue an. Dafür braucht es weder ein Konto noch ein geleaktes Geheimnis, nur die Projekt-ID.
Die datierte Variante, die der Testmodus oft verwendet, scheitert anders. Mit if request.time < timestamp.date(2026, 1, 1) und einem Datum in der Vergangenheit bekam der Gast 403, ebenso Nutzer A beim Lesen der eigenen Bestellung. Das Leck ist weg, und die App gleich mit.
Reicht request.auth != null?
Nein. Es sagt nur, dass hinter der Anfrage irgendein Konto steht.
match /orders/{id} {
allow read, write: if request.auth != null;
}
Der Gast bekam 403, deshalb wirkt diese Regel im schnellen Test sicher. Dann listete ein Fremder, der sich nur registriert hatte, alle 3 Bestellungen, änderte die Summe von Bs Bestellung und löschte As Bestellung, jeweils mit 200. Ein anonymer Nutzer listete ebenfalls alle 3. Wenn die Registrierung offen ist, heißt diese Regel „jeder, der sich die Mühe macht“. Die Firebase-Dokumentation führt sie auch als häufiges unsicheres Muster.
Warum braucht eine Besitzerprüfung resource und request.resource?
resource ist das Dokument, wie es gespeichert ist. request.resource ist das Dokument, wie es nach dem Schreiben aussähe. Eine Regel, die nur das erste prüft, prüft, wem die Zeile jetzt gehört, und sagt nichts darüber, was daraus gemacht wird.
match /orders/{id} {
allow read, update, delete: if resource.data.ownerId == request.auth.uid;
allow create: if request.auth != null;
}
Das Lesen sah richtig aus: A bekam 200 auf die eigene Bestellung und 403 auf Bs. Das Schreiben nicht. A legte eine neue Bestellung mit ownerId auf B an und bekam 200. Dann änderte A ownerId der eigenen Bestellung auf B, ebenfalls 200, und B konnte sie von da an lesen. In einer App, in der Bestellungen Rechnungen oder Salden bedeuten, schreibt damit ein Nutzer in das Konto eines anderen.
Diese Fassung lehnte alles ab:
match /orders/{id} {
allow read, delete: if request.auth != null
&& resource.data.ownerId == request.auth.uid;
allow create: if request.auth != null
&& request.resource.data.ownerId == request.auth.uid;
allow update: if request.auth != null
&& resource.data.ownerId == request.auth.uid
&& request.resource.data.ownerId == resource.data.ownerId;
}
| Anfrage von A | Ergebnis |
|---|---|
| Eigene Bestellung lesen | 200 |
| Bs Bestellung lesen, ändern, löschen | 403 |
| Bestellung von B anlegen | 403 |
ownerId auf B ändern |
403 |
Sie ließ A aber weiterhin total der eigenen Bestellung ändern, mit 200. Wenn der Client dieses Feld nicht setzen darf, nennen Sie die Felder, die er ändern darf:
allow update: if request.auth != null
&& resource.data.ownerId == request.auth.uid
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(['note']);
Damit gab eine Änderung von total 403 zurück und eine Änderung von note 200.
Warum scheiterte meine Abfrage trotz korrekter Regel?
Weil Regeln keine Filter sind. Unter der Besitzerregel oben bekam A auf die Abfrage der ganzen Collection 403, obwohl A 2 der 3 Bestellungen gehören. Firestore beurteilt die Abfrage danach, was sie zurückgeben könnte, nicht danach, was vorhanden ist.
| Anfrage von A | Ergebnis |
|---|---|
| Alle Bestellungen listen | 403 |
Abfrage ownerId == A |
200, 2 Dok. |
Abfrage ownerId == B |
403 |
Wenn eine Liste nach dem Verschärfen der Regeln kaputtgeht, korrigieren Sie also die Abfrage. Die Regel zu lockern, damit die Liste funktioniert, ist genau der Weg, auf dem die breiten Regeln oben entstehen.
Kann ein breites Match eine korrekte Regel aufheben?
Ja, und danach würde ich in einem Projekt mit langer Regeldatei zuerst suchen. Firestore erlaubt eine Anfrage, wenn irgendeine passende Regel sie erlaubt.
match /orders/{id} {
allow read: if request.auth != null
&& resource.data.ownerId == request.auth.uid;
}
match /{document=**} {
allow read: if request.auth != null;
}
Der erste Block liest sich wie eine gute Besitzerregel. Mit dem zweiten Block las A Bs Bestellung mit 200, ein Fremder listete alle 3 Bestellungen, und derselbe Fremde las As Dokument in users. Nichts im ersten Block warnt Sie. Löschen Sie das breite Match; eine spätere Regel kann Zugriff nicht wieder entziehen.
Wie macht sich ein Nutzer selbst zum Admin?
Dieses Muster taucht auf, sobald Rollen in einem Dokument liegen:
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
}
match /users/{uid} {
allow read, write: if request.auth.uid == uid;
}
match /orders/{id} {
allow read: if resource.data.ownerId == request.auth.uid || isAdmin();
}
„Nutzer dürfen ihr eigenes Profil bearbeiten“ und „Admins dürfen alle Bestellungen lesen“ sind jeweils vernünftig. Zusammen sind sie eine Rechteausweitung.
Die Lösung: den Client daran hindern, dieses Feld zu schreiben. Mit request.resource.data.role == resource.data.role in der Schreibregel gab dieselbe Anfrage 403 zurück, und A blieb aus Bs Bestellung draußen. Custom Claims, die von einem Server gesetzt werden, sind der andere Weg, denn ein Client kann sie nicht ändern.
Zwei Dinge, die sich als in Ordnung herausstellten
Regeln auf einem übergeordneten Pfad lecken nicht in Subcollections. Mit allow read auf /orders/{id} für jeden angemeldeten Nutzer las A die Bestellung, und /orders/b1/notes/n1 darunter gab 403. Das ist das dokumentierte Verhalten: Regeln gelten nur am passenden Pfad.
Ein Gast kommt nicht durch einen Vergleich mit einem fehlenden Token. resource.data.ownerId == request.auth.uid gab dem Gast 403, auch ohne separates request.auth != null. Ich schreibe die Prüfung trotzdem hin, weil die Regel dann sagt, was sie meint.
Wie prüfen Sie Ihre eigenen Regeln?
Führen Sie Ihre Anfragen im Emulator als Gast, als Fremder und als zweiter Nutzer aus. Das hat jeden Fall oben aufgedeckt; das Lesen der Regeln nicht.
Für einen ersten Durchgang liest der kostenlose Firebase-Regelcheck eingefügte Regeln, ohne Ihr Projekt anzufassen. Bei den Regelsätzen hier markierte er den Testmodus, Zugriff nur mit Anmeldung, das breite Match und die selbst geschriebene Rolle. Er liest nur Text, kann also nicht sagen, wie Ihre Daten aussehen oder ob eine Regel ausgerollt wurde.
Ihr apiKey in der Seite ist für sich kein Problem. Die Firebase-Webkonfiguration ist absichtlich öffentlich, und der kostenlose Secret-Scanner kennzeichnet sie so, denn die Regeln schützen die Daten dahinter. Für Dateien gelten wiederum andere Regeln, mit einem Leck, das Regeln nicht stoppen können, und das habe ich in Firebase-Storage-Regeln getestet.
Grenzen. Nur Firestore, im lokalen Emulator, über REST, mit ausgedachten Daten. Die Realtime Database nutzt eine andere Regelsprache und wurde nicht getestet. App Check, das einschränkt, welche Apps Ihr Backend aufrufen dürfen, gehört nicht dazu; es ersetzt keine Regeln. Die Produktion kann sich vom Emulator in Details unterscheiden, die ich nicht geprüft habe.
Unsicher, was Ihre Regeln durchlassen?
Gern lese ich sie mit Ihnen. Ohne Verkaufsgespräch.
Kontakt aufnehmen