Supabase Sicherheit
Reading time: 11 minutes
tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Grundinformationen
As per their landing page: Supabase is an open source Firebase alternative. Start your project with a Postgres database, Authentication, instant APIs, Edge Functions, Realtime subscriptions, Storage, and Vector embeddings.
Subdomain
Im Grunde erhält der Benutzer beim Erstellen eines Projekts eine supabase.co-Subdomain wie: jnanozjdybtpqgcwhdiz.supabase.co
Database configuration
tip
Diese Daten können über einen Link wie https://supabase.com/dashboard/project/<project-id>/settings/database abgerufen werden
Diese Datenbank wird in einer AWS-Region bereitgestellt, und um sich damit zu verbinden ist es möglich, sich zu verbinden mit: postgres://postgres.jnanozjdybtpqgcwhdiz:[YOUR-PASSWORD]@aws-0-us-west-1.pooler.supabase.com:5432/postgres (dies wurde in us-west-1 erstellt).
Das Passwort ist ein vom Benutzer zuvor gesetztes Passwort.
Da die Subdomain bekannt ist, als Username verwendet wird und die AWS-Regionen begrenzt sind, könnte es möglich sein, einen brute force the password zu versuchen.
Dieser Abschnitt enthält auch Optionen zum:
- Datenbank-Passwort zurücksetzen
- Connection pooling konfigurieren
- SSL konfigurieren: Reject plan-text connections (standardmäßig sind sie aktiviert)
- Festplattengröße konfigurieren
- Netzwerkbeschränkungen und Sperren anwenden
API Configuration
tip
Diese Daten können über einen Link wie https://supabase.com/dashboard/project/<project-id>/settings/api abgerufen werden
Die URL, um auf die supabase API in deinem Projekt zuzugreifen, sieht beispielsweise so aus: https://jnanozjdybtpqgcwhdiz.supabase.co.
anon api keys
Es wird außerdem ein anon API key (role: "anon"), z. B.: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTQ5OTI3MTksImV4cCI6MjAzMDU2ODcxOX0.sRN0iMGM5J741pXav7UxeChyqBE9_Z-T0tLA9Zehvqk, generiert, den die Anwendung verwenden muss, um mit der API zu kommunizieren, wie in unserem Beispiel unten gezeigt.
Es ist möglich, die REST-API, um diese API anzusprechen, in den docs zu finden, aber die interessantesten Endpoints wären:
Signup (/auth/v1/signup)
``` POST /auth/v1/signup HTTP/2 Host: id.io.net Content-Length: 90 X-Client-Info: supabase-js-web/2.39.2 Sec-Ch-Ua: "Not-A.Brand";v="99", "Chromium";v="124" Sec-Ch-Ua-Mobile: ?0 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTQ5OTI3MTksImV4cCI6MjAzMDU2ODcxOX0.sRN0iMGM5J741pXav7UxeChyqBE9_Z-T0tLA9Zehvqk User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.60 Safari/537.36 Content-Type: application/json;charset=UTF-8 Apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTQ5OTI3MTksImV4cCI6MjAzMDU2ODcxOX0.sRN0iMGM5J741pXav7UxeChyqBE9_Z-T0tLA9Zehvqk Sec-Ch-Ua-Platform: "macOS" Accept: */* Origin: https://cloud.io.net Sec-Fetch-Site: same-site Sec-Fetch-Mode: cors Sec-Fetch-Dest: empty Referer: https://cloud.io.net/ Accept-Encoding: gzip, deflate, br Accept-Language: en-GB,en-US;q=0.9,en;q=0.8 Priority: u=1, i{"email":"test@exmaple.com","password":"SomeCOmplexPwd239."}
</details>
<details>
<summary>Anmeldung (/auth/v1/token?grant_type=password)</summary>
POST /auth/v1/token?grant_type=password HTTP/2 Host: hypzbtgspjkludjcnjxl.supabase.co Content-Length: 80 X-Client-Info: supabase-js-web/2.39.2 Sec-Ch-Ua: "Not-A.Brand";v="99", "Chromium";v="124" Sec-Ch-Ua-Mobile: ?0 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTQ5OTI3MTksImV4cCI6MjAzMDU2ODcxOX0.sRN0iMGM5J741pXav7UxeChyqBE9_Z-T0tLA9Zehvqk User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.60 Safari/537.36 Content-Type: application/json;charset=UTF-8 Apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTQ5OTI3MTksImV4cCI6MjAzMDU2ODcxOX0.sRN0iMGM5J741pXav7UxeChyqBE9_Z-T0tLA9Zehvqk Sec-Ch-Ua-Platform: "macOS" Accept: / Origin: https://cloud.io.net Sec-Fetch-Site: same-site Sec-Fetch-Mode: cors Sec-Fetch-Dest: empty Referer: https://cloud.io.net/ Accept-Encoding: gzip, deflate, br Accept-Language: en-GB,en-US;q=0.9,en;q=0.8 Priority: u=1, i
{"email":"test@exmaple.com","password":"SomeCOmplexPwd239."}
</details>
Also, whenever you discover a client using supabase with the subdomain they were granted (it's possible that a subdomain of the company has a CNAME over their supabase subdomain), you might try to **create a new account in the platform using the supabase API**.
### secret / service_role API-Keys
A secret API key will also be generated with **`role: "service_role"`**. This API key should be secret because it will be able to bypass **Row Level Security**.
The API key looks like this: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTcxNDk5MjcxOSwiZXhwIjoyMDMwNTY4NzE5fQ.0a8fHGp3N_GiPq0y0dwfs06ywd-zhTwsm486Tha7354`
### JWT Secret
A **JWT Secret** will also be generate so the application can **create and sign custom JWT tokens**.
## Authentifizierung
### Registrierungen
<div class="mdbook-alerts mdbook-alerts-tip">
<p class="mdbook-alerts-title">
<span class="mdbook-alerts-icon"></span>
tip
</p>
By **default** supabase will allow **new users to create accounts** on your project by using the previously mentioned API endpoints.
</div>
However, these new accounts, by default, **will need to validate their email address** to be able to login into the account. It's possible to enable **"Allow anonymous sign-ins"** to allow people to login without verifying their email address. This could grant access to **unexpected data** (they get the roles `public` and `authenticated`).\
This is a very bad idea because supabase charges per active user so people could create users and login and supabase will charge for those:
<figure><img src="../images/image (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
#### Auth: Server-seitige Durchsetzung der Registrierung
Hiding the signup button in the frontend is not enough. If the **Auth server still allows signups**, an attacker can call the API directly with the public `anon` key and create arbitrary users.
Schneller Test (von einem nicht authentifizierten Client):
<div class="codeblock_filename_container"><span class="codeblock_filename_inner hljs">bash</span></div>
```bash
curl -X POST \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
-H "Content-Type: application/json" \
-d '{"email":"attacker@example.com","password":"Sup3rStr0ng!"}' \
https://<PROJECT_REF>.supabase.co/auth/v1/signup
Expected hardening:
- Deaktivieren Sie E-Mail/Passwort-Registrierungen im Dashboard: Authentication → Providers → Email → Disable sign ups (invite-only), oder setzen Sie das entsprechende GoTrue-Setting.
- Stellen Sie sicher, dass die API jetzt 4xx auf den vorherigen Aufruf zurückgibt und kein neuer Benutzer erstellt wird.
- Wenn Sie sich auf Invites oder SSO verlassen, stellen Sie sicher, dass alle anderen Providers deaktiviert sind, sofern sie nicht explizit benötigt werden.
RLS und Views: Schreib-Bypass via PostgREST
Die Verwendung einer Postgres VIEW, um sensible Spalten zu „verbergen“, und deren Exposition über PostgREST kann verändern, wie Berechtigungen bewertet werden. In PostgreSQL:
- Gewöhnliche Views werden standardmäßig mit den Privilegien des View-Owners ausgeführt (definer semantics). In PG ≥15 können Sie
security_invokeraktivieren. - Row Level Security (RLS) gilt für Basistabellen. Tabellenbesitzer umgehen RLS, es sei denn,
FORCE ROW LEVEL SECURITYist auf der Tabelle gesetzt. - Updatable views können INSERT/UPDATE/DELETE akzeptieren, die dann auf die Basistabelle angewendet werden. Ohne
WITH CHECK OPTIONkönnen Schreibvorgänge, die nicht mit dem View-Prädikat übereinstimmen, trotzdem erfolgreich sein.
Beobachtetes Risikomuster in der Praxis:
- Eine View mit reduzierten Spalten wird über Supabase REST bereitgestellt und
anon/authenticatedzugewiesen. - PostgREST erlaubt DML auf der updatable view und die Operation wird mit den Privilegien des View-Owners ausgewertet, wodurch die vorgesehenen RLS-Policies auf der Basistabelle effektiv umgangen werden.
- Ergebnis: Clients mit niedrigen Rechten können massenhaft Zeilen bearbeiten (z. B. Profil-Bios/Avatare), die sie nicht hätten ändern dürfen.
Illustratives Schreiben via View (versucht von einem öffentlichen Client):
curl -X PATCH \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"bio":"pwned","avatar_url":"https://i.example/pwn.png"}' \
"https://<PROJECT_REF>.supabase.co/rest/v1/users_view?id=eq.<victim_user_id>"
Hardening-Checkliste für Views und RLS:
- Bevorzuge das Freigeben von Basistabellen mit expliziten, nach dem Prinzip der geringsten Rechte vergebenen Berechtigungen und präzisen RLS-Richtlinien.
- Wenn du eine View freigeben musst:
- Mache sie nicht aktualisierbar (z. B. durch Ausdrücke/Joins) oder verweigere
INSERT/UPDATE/DELETEauf der view für alle nicht vertrauenswürdigen Rollen. - Erzwinge
ALTER VIEW <v> SET (security_invoker = on), sodass die Rechte des Invokers statt der des Owners verwendet werden. - Auf Basistabellen verwende
ALTER TABLE <t> FORCE ROW LEVEL SECURITY;, damit selbst Owner der RLS unterliegen. - Wenn du Schreibzugriffe über eine updatable view erlaubst, füge
WITH [LOCAL|CASCADED] CHECK OPTIONhinzu und ergänze passende RLS auf den Basistabellen, um sicherzustellen, dass nur erlaubte Zeilen geschrieben/geändert werden können. - In Supabase solltest du
anon/authenticatedkeine Schreibrechte auf views gewähren, es sei denn, du hast das End-to-End-Verhalten mit Tests verifiziert.
Erkennungstipp:
- Versuche mit
anonund einemauthenticatedTestuser alle CRUD-Operationen gegen jede exponierte Tabelle/View. Jeder erfolgreiche Schreibzugriff, bei dem du eine Verweigerung erwartet hättest, deutet auf eine Fehlkonfiguration hin.
OpenAPI-gesteuertes CRUD-Probing von anon/auth-Rollen
PostgREST stellt ein OpenAPI-Dokument bereit, mit dem du alle REST-Ressourcen auflisten und anschließend automatisch erlaubte Operationen aus Sicht von Rollen mit geringen Rechten prüfen kannst.
Hole das OpenAPI-Dokument (funktioniert mit dem öffentlichen anon key):
curl -s https://<PROJECT_REF>.supabase.co/rest/v1/ \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
-H "Accept: application/openapi+json" | jq '.paths | keys[]'
Sondierungsmuster (Beispiele):
- Eine einzelne Zeile lesen (erwarte 401/403/200 abhängig von RLS):
curl -s "https://<PROJECT_REF>.supabase.co/rest/v1/<table>?select=*&limit=1" \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>"
- Test UPDATE ist blockiert (verwenden Sie einen nicht existierenden Filter, um zu vermeiden, dass Daten während des Tests verändert werden):
curl -i -X PATCH \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
-H "Content-Type: application/json" \
-H "Prefer: return=minimal" \
-d '{"__probe":true}' \
"https://<PROJECT_REF>.supabase.co/rest/v1/<table_or_view>?id=eq.00000000-0000-0000-0000-000000000000"
- Test INSERT ist blockiert:
curl -i -X POST \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
-H "Content-Type: application/json" \
-H "Prefer: return=minimal" \
-d '{"__probe":true}' \
"https://<PROJECT_REF>.supabase.co/rest/v1/<table_or_view>"
- Prüfen, ob DELETE blockiert ist:
curl -i -X DELETE \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
"https://<PROJECT_REF>.supabase.co/rest/v1/<table_or_view>?id=eq.00000000-0000-0000-0000-000000000000"
Recommendations:
- Automatisiere die vorherigen Probes für sowohl
anonals auch einen minimalenauthenticated-User und integriere sie in CI, um Regressionen zu erkennen. - Behandle jede exponierte table/view/function als gleichwertige Angriffsfläche. Gehe nicht davon aus, dass eine view das gleiche RLS-Verhalten wie ihre Basistabellen "erbt".
Passwörter & Sitzungen
Es ist möglich, die minimale Passwortlänge anzugeben (standardmäßig), Anforderungen (standardmäßig keine) und die Verwendung von leaked passwords zu verbieten.
Es wird empfohlen, die Anforderungen zu verschärfen, da die Standardwerte schwach sind.
- User Sessions: Es ist möglich zu konfigurieren, wie User Sessions funktionieren (Timeouts, 1 Session pro Benutzer...)
- Bot and Abuse Protection: Es ist möglich, Captcha zu aktivieren.
SMTP-Einstellungen
Es ist möglich, einen SMTP-Server zum Versenden von E-Mails einzurichten.
Erweiterte Einstellungen
- Ablaufzeit für access tokens festlegen (standardmäßig 3600)
- Erkennung und Widerruf potenziell kompromittierter refresh tokens und Timeouts konfigurieren
- MFA: Angeben, wie viele MFA-Faktoren pro Benutzer gleichzeitig registriert werden können (standardmäßig 10)
- Max Direct Database Connections: Maximale Anzahl direkter Verbindungen für Auth (standardmäßig 10)
- Max Request Duration: Maximal erlaubte Dauer einer Auth-Anfrage (standardmäßig 10s)
Storage
tip
Supabase erlaubt Dateien zu speichern und über eine URL zugänglich zu machen (es verwendet S3-Buckets).
- Setze das Upload-Dateigrößenlimit (Standard ist 50MB)
- Die S3-Verbindung wird über eine URL angegeben wie:
https://jnanozjdybtpqgcwhdiz.supabase.co/storage/v1/s3 - Es ist möglich, S3 access key anzufordern, die aus einer
access key ID(z. B.a37d96544d82ba90057e0e06131d0a7b) und einersecret access key(z. B.58420818223133077c2cec6712a4f909aec93b4daeedae205aa8e30d5a860628) bestehen
Edge Functions
Es ist auch möglich, secrets zu speichern in supabase, die dann von edge functions zugänglich sind (sie können über das Web erstellt und gelöscht werden, aber deren Werte können nicht direkt eingesehen werden).
References
- Building Hacker Communities: Bug Bounty Village, getDisclosed’s Supabase Misconfig, and the LHE Squad (Ep. 133) – YouTube
- Critical Thinking Podcast – Episode 133 page
- Supabase: Row Level Security (RLS)
- PostgreSQL: Row Security Policies
- PostgreSQL: CREATE VIEW (security_invoker, check option)
- PostgREST: OpenAPI documentation
tip
Lernen & üben Sie AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
HackTricks Cloud