Zugriffsregeln in Supabase und Firebase (CWE-862): Wie fehlende Row Level Security Daten preisgibt und wie man sie behebt
Supabase- und Firebase-Apps lassen den Browser die Datenbank direkt mit einem öffentlichen Schlüssel abfragen, sodass zwischen einem Besucher und jeder Zeile nur Row Level Security oder Security Rules stehen. Eine per SQL angelegte Tabelle ohne enable row level security oder eine Firestore-Regel, die auf allow read, write: if true stehen geblieben ist, kann jeder lesen und ändern. Wie Richtlinien mit USING und WITH CHECK die Zeilen auf den angemeldeten Nutzer beschränken, mit einem PostgreSQL-Migrationsbeispiel.
Einfache Erklärung (ELI5)
Ein Bürogebäude gibt jedem Besucher dieselbe Lobby-Karte, aufgedruckt auf der Rückseite der Broschüre. Das ist in Ordnung, solange die Tür jeder Etage prüft, wer man ist, bevor sie aufgeht. Eine Etage wurde in Eile eingerichtet, und ihre Tür hat gar kein Lesegerät, also öffnet die Lobby-Karte sie, und jeder kann hinein und die Akten lesen. Der anon-Schlüssel von Supabase ist die Lobby-Karte: Er soll öffentlich sein. Row Level Security ist das Lesegerät an jeder Etagentür. Die Lösung: an jeder Tür ein Lesegerät anbringen und ihm genau sagen, wessen Akten jede Person sehen darf.
Kernkonzepte & Begriffe
- Öffentlicher anon-Schlüssel
- Der Supabase-Schlüssel
anonund die Firebase-Webkonfiguration stecken im App-Bundle, und jeder kann sie kopieren. Sie identifizieren das Projekt, nicht den Nutzer, sind also kein Geheimnis und schützen Daten nicht allein. - Row Level Security (RLS)
- Eine PostgreSQL-Funktion, die jede Abfrage durch Richtlinien filtert. Der Table Editor von Supabase aktiviert sie für neue Tabellen, doch per SQL oder Migration angelegte Tabellen starten ohne RLS, und die Standardrechte erlauben
anonundauthenticateddann, jede Zeile zu lesen und zu ändern. - USING und WITH CHECK
USINGlegt fest, welche vorhandenen Zeilen ein Nutzer sehen, ändern oder löschen darf;WITH CHECKlegt fest, welche neuen oder geänderten Zeilen er schreiben darf. Eine Richtlinie nur mitUSINGbei insert oder update erlaubt, Zeilen für eine fremde Organisation zu schreiben.- service_role-Schlüssel
- Der Schlüssel
service_roleumgeht RLS vollständig. Er gehört nur auf Server und in Edge Functions, nie in einen Browser oder eine mobile App. - Firestore-Regeln im Testmodus
- Im Testmodus gestartete Projekte erhalten Regeln, die bis zu einem Datum alle Lese- und Schreibzugriffe erlauben, und viele werden später auf
allow read, write: if truegeändert. Regeln müssenrequest.auth.uidmit dem Besitzerfeld des Dokuments vergleichen.
Schritt-für-Schritt Angriffsablauf
Der Angreifer öffnet die App
Supabase-URL und anon-Schlüssel stehen im JavaScript-Bundle oder im Paket der mobilen App.
Er fragt die Tabelle direkt ab
Er ruft den REST-Endpunkt für invoices nur mit dem anon-Schlüssel auf und umgeht die Oberfläche der App. Ein Request an ein Testprojekt mit dem anon-Schlüssel und ohne Nutzersitzung zeigt den Fall.
Die Datenbank liefert jede Zeile
RLS ist aus und anon hat select-Rechte, also filtert PostgreSQL nichts und liefert die Rechnungen aller Organisationen.
Daten fließen ab oder werden verändert
Sind auch update und delete gewährt, kann der Angreifer Datensätze ändern oder löschen. 2025 wurde CVE-2025-48757 vergeben, nachdem Forscher fehlende RLS gefunden hatten, durch die Daten in mehr als 170 von einem KI-App-Baukasten erzeugten Apps offenlagen.
Quellcode: Verwundbar vs. Sicher
-- migration.sql: RLS wird für diese Tabelle nie aktiviert
create table public.invoices (
id bigint generated always as identity primary key,
org_id uuid not null references public.orgs (id),
customer text not null,
amount_cents bigint not null
);
-- der öffentliche anon-Schlüssel darf jede Zeile lesen, ändern und löschen
grant select, insert, update, delete on public.invoices to anon, authenticated;
-- migration.sql: RLS an, anon entzogen, eine Richtlinie pro Befehl
create table public.invoices (
id bigint generated always as identity primary key,
org_id uuid not null references public.orgs (id),
customer text not null,
amount_cents bigint not null
);
-- mit aktiver RLS gilt: keine Richtlinie, kein Zugriff
alter table public.invoices enable row level security;
revoke all on public.invoices from anon;
-- Nutzer sehen nur Rechnungen ihrer eigenen Organisationen
create policy "members read their org's invoices"
on public.invoices for select to authenticated
using (org_id in (select org_id from public.memberships where user_id = (select auth.uid())));
-- neue Zeilen müssen zu einer Organisation des Nutzers gehören; keine update- oder delete-Richtlinie
create policy "members add invoices to their org"
on public.invoices for insert to authenticated
with check (org_id in (select org_id from public.memberships where user_id = (select auth.uid())));
Checkliste für Engineering & Systemsicherheit
- RLS für jede Tabelle in exponierten Schemas aktivieren und die CI scheitern lassen, wenn
select tablename from pg_tables where schemaname = 'public' and not rowsecurityeine Zeile liefert. - Eine Richtlinie pro Befehl schreiben, beschränkt
to authenticated, mitUSINGfür Lesezugriffe undWITH CHECKfür insert und update; nicht benötigte update- und delete-Richtlinien weglassen. anondie Tabellenrechte entziehen, wenn anonyme Nutzer keinen Grund zum Lesen haben, und den Schlüsselservice_rolenur in Serverumgebungen halten.- RLS auch für Tabellen aktivieren, die Ihre Richtlinien lesen, etwa
memberships, damit sich Nutzer nicht selbst einer anderen Organisation hinzufügen können. - Mit zwei Testnutzern testen: Nutzer A muss für die Organisation von Nutzer B null Zeilen erhalten; für Firestore Regel-Unit-Tests gegen den Emulator mit
@firebase/rules-unit-testingausführen.