Files
hacktricks-cloud/src/pentesting-ci-cd/supabase-security.md

270 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sicurezza Supabase
{{#include ../banners/hacktricks-training.md}}
## Informazioni di base
Secondo la loro [**landing page**](https://supabase.com/): Supabase è un'alternativa open source a Firebase. Avvia il tuo progetto con un database Postgres, Authentication, instant APIs, Edge Functions, Realtime subscriptions, Storage, e Vector embeddings.
### Sottodominio
Fondamentalmente quando viene creato un progetto, l'utente riceverà un sottodominio supabase.co come: **`jnanozjdybtpqgcwhdiz.supabase.co`**
## **Configurazione del database**
> [!TIP]
> **Questi dati sono accessibili da un link come `https://supabase.com/dashboard/project/<project-id>/settings/database`**
Questo **database** verrà distribuito in una regione AWS e, per connettersi, sarebbe possibile farlo collegandosi a: `postgres://postgres.jnanozjdybtpqgcwhdiz:[YOUR-PASSWORD]@aws-0-us-west-1.pooler.supabase.com:5432/postgres` (questo è stato creato in us-west-1).\
La password è una **password scelta dall'utente** in precedenza.
Pertanto, dato che il sottodominio è noto ed è usato come username e le region AWS sono limitate, potrebbe essere possibile provare a **brute force the password**.
Questa sezione contiene anche opzioni per:
- Reimpostare la password del database
- Configurare il connection pooling
- Configurare SSL: rifiutare le connessioni in plain-text (di default sono abilitate)
- Configurare la dimensione del Disk
- Applicare restrizioni e ban di rete
## Configurazione API
> [!TIP]
> **Questi dati sono accessibili da un link come `https://supabase.com/dashboard/project/<project-id>/settings/api`**
L'URL per accedere all'API supabase del tuo progetto sarà: `https://jnanozjdybtpqgcwhdiz.supabase.co`.
### anon api keys
Genererà anche un'**anon API key** (`role: "anon"`), come: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTQ5OTI3MTksImV4cCI6MjAzMDU2ODcxOX0.sRN0iMGM5J741pXav7UxeChyqBE9_Z-T0tLA9Zehvqk` che l'applicazione dovrà usare per contattare l'API esposta nel nostro esempio in
È possibile trovare l'API REST per contattare questa API nei [**docs**](https://supabase.com/docs/reference/self-hosting-auth/returns-the-configuration-settings-for-the-gotrue-server), ma gli endpoint più interessanti sarebbero:
<details>
<summary>Registrazione (/auth/v1/signup)</summary>
```
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>Accesso (/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>
Quindi, ogni volta che scopri un client che usa supabase con il sottodominio a loro assegnato (è possibile che un sottodominio dell'azienda abbia un CNAME puntato sul loro sottodominio supabase), potresti provare a **creare un nuovo account sulla piattaforma usando la supabase API**.
### secret / service_role api keys
Verrà anche generata una secret API key con **`role: "service_role"`**. Questa API key deve rimanere segreta perché sarà in grado di bypassare **Row Level Security**.
La API key appare così: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTcxNDk5MjcxOSwiZXhwIjoyMDMwNTY4NzE5fQ.0a8fHGp3N_GiPq0y0dwfs06ywd-zhTwsm486Tha7354`
### JWT Secret
Un **JWT Secret** verrà inoltre generato così l'applicazione può **creare e firmare JWT personalizzati**.
## Authentication
### Signups
> [!TIP]
> Per **default** supabase permetterà ai **nuovi utenti di creare account** sul tuo progetto usando gli endpoint API menzionati in precedenza.
Tuttavia, questi nuovi account, per impostazione predefinita, **dovranno convalidare il loro indirizzo email** per poter effettuare il login nell'account. È possibile abilitare **"Allow anonymous sign-ins"** per consentire alle persone di effettuare il login senza verificare l'indirizzo email. Questo potrebbe concedere accesso a **dati inaspettati** (ottenendo i ruoli `public` e `authenticated`).\
Questa è una pessima idea perché supabase addebita per utente attivo, quindi le persone potrebbero creare utenti, effettuare il login e supabase addebiterà per quelli:
<figure><img src="../images/image (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
#### Auth: Server-side signup enforcement
Nascondere il pulsante di registrazione nel frontend non è sufficiente. Se il **Auth server continua a permettere le registrazioni**, un attaccante può chiamare direttamente l'API con la public `anon` key e creare utenti arbitrari.
Test rapido (da un client non autenticato):
```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:
- Disable email/password signups in the Dashboard: Authentication → Providers → Email → Disable sign ups (invite-only), or set the equivalent GoTrue setting.
- Verify the API now returns 4xx to the previous call and no new user is created.
- If you rely on invites or SSO, ensure all other providers are disabled unless explicitly needed.
## RLS and Views: Bypass di scrittura via PostgREST
Using a Postgres VIEW to “hide” sensitive columns and exposing it via PostgREST can change how privileges are evaluated. In PostgreSQL:
- Ordinary views execute with the privileges of the view owner by default (definer semantics). In PG ≥15 you can opt into `security_invoker`.
- Row Level Security (RLS) applies on base tables. Table owners bypass RLS unless `FORCE ROW LEVEL SECURITY` is set on the table.
- Updatable views can accept INSERT/UPDATE/DELETE that are then applied to the base table. Without `WITH CHECK OPTION`, writes that dont match the view predicate may still succeed.
Risk pattern observed in the wild:
- A reduced-column VIEW is exposed through Supabase REST and granted to `anon`/`authenticated`.
- PostgREST allows DML on the updatable VIEW and the operation is evaluated with the view owners privileges, effectively bypassing the intended RLS policies on the base table.
- Result: low-privileged clients can mass-edit rows (e.g., profile bios/avatars) they should not be able to modify.
Illustrative write via view (attempted from a public client):
```bash
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>"
```
Checklist di hardening per views e RLS:
- Preferisci esporre le tabelle base con permessi espliciti, minimo privilegio e politiche RLS precise.
- Se devi esporre una view:
- Rendila non-updatable (es., includendo espressioni/join) o nega `INSERT/UPDATE/DELETE` sulla view a tutti i ruoli non attendibili.
- Forza `ALTER VIEW <v> SET (security_invoker = on)` in modo che vengano usati i privilegi dell'invocatore invece di quelli del proprietario.
- Sulle tabelle base, usa `ALTER TABLE <t> FORCE ROW LEVEL SECURITY;` in modo che anche i proprietari siano soggetti a RLS.
- Se permetti scritture tramite una view updatable, aggiungi `WITH [LOCAL|CASCADED] CHECK OPTION` e politiche RLS complementari sulle tabelle base per garantire che solo le righe consentite possano essere scritte/modificate.
- In Supabase, evita di concedere a `anon`/`authenticated` qualsiasi privilegio di scrittura sulle view a meno che tu non abbia verificato il comportamento end-to-end con test.
Detection tip:
- Da `anon` e da un utente di test `authenticated`, prova tutte le operazioni CRUD contro ogni tabella/view esposta. Qualsiasi scrittura riuscita che ti aspettavi fosse negata indica una misconfigurazione.
### Probing CRUD guidato da OpenAPI dai ruoli anon/auth
PostgREST espone un documento OpenAPI che puoi usare per enumerare tutte le risorse REST, quindi sondare automaticamente le operazioni consentite dai ruoli a basso privilegio.
Recupera l'OpenAPI (funziona con la public anon key):
```bash
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[]'
```
Probe pattern (examples):
- Leggi una singola riga (atteso 401/403/200 a seconda di RLS):
```bash
curl -s "https://<PROJECT_REF>.supabase.co/rest/v1/<table>?select=*&limit=1" \
-H "apikey: <SUPABASE_ANON_KEY>" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>"
```
- Verificare che UPDATE sia bloccato (usa un filtro non esistente per evitare di alterare i dati durante i test):
```bash
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 è bloccato:
```bash
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>"
```
- Verifica che DELETE sia bloccato:
```bash
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:
- Automatizza le probe precedenti per entrambi `anon` e un utente minimamente `authenticated` e integrale nella CI per intercettare regressioni.
- Tratta ogni table/view/function esposta come una superficie di prima classe. Non presumere che una view “inherits” lo stesso posture RLS delle sue tabelle di base.
### Passwords & sessions
È possibile indicare la lunghezza minima della password (di default), i requisiti (nessuno di default) e disallow to use leaked passwords.\
È raccomandato **migliorare i requisiti poiché quelli di default sono deboli**.
- Sessioni utente: È possibile configurare come funzionano le user sessions (timeouts, 1 session per user...)
- Bot and Abuse Protection: È possibile abilitare Captcha.
### SMTP Settings
È possibile impostare un SMTP per inviare email.
### Advanced Settings
- Impostare il tempo di scadenza degli access tokens (3600 by default)
- Abilitare il rilevamento e la revoca dei refresh tokens potenzialmente compromessi e timeout
- MFA: Indicare quanti fattori MFA possono essere enrolati contemporaneamente per utente (10 by default)
- Max Direct Database Connections: Numero massimo di connessioni usate per l'auth (10 by default)
- Max Request Duration: Tempo massimo consentito per una richiesta di Auth (10s by default)
## Storage
> [!TIP]
> Supabase allows **to store files** and make them accesible over a URL (it uses S3 buckets).
- Impostare il limite di dimensione per l'upload dei file (default è 50MB)
- La connessione S3 è fornita con una URL come: `https://jnanozjdybtpqgcwhdiz.supabase.co/storage/v1/s3`
- È possibile **request S3 access key** che sono formate da un `access key ID` (e.g. `a37d96544d82ba90057e0e06131d0a7b`) e un `secret access key` (e.g. `58420818223133077c2cec6712a4f909aec93b4daeedae205aa8e30d5a860628`)
## Edge Functions
È possibile anche **store secrets** in supabase che saranno **accessible by edge functions** (possono essere create e cancellate dal web, ma non è possibile accedere direttamente al loro valore).
## References
- [Building Hacker Communities: Bug Bounty Village, getDiscloseds Supabase Misconfig, and the LHE Squad (Ep. 133) YouTube](https://youtu.be/NI-eXMlXma4)
- [Critical Thinking Podcast Episode 133 page](https://www.criticalthinkingpodcast.io/episode-133-building-hacker-communities-bug-bounty-village-getdisclosed-and-the-lhe-squad/)
- [Supabase: Row Level Security (RLS)](https://supabase.com/docs/guides/auth/row-level-security)
- [PostgreSQL: Row Security Policies](https://www.postgresql.org/docs/current/ddl-rowsecurity.html)
- [PostgreSQL: CREATE VIEW (security_invoker, check option)](https://www.postgresql.org/docs/current/sql-createview.html)
- [PostgREST: OpenAPI documentation](https://postgrest.org/en/stable/references/api.html#openapi-documentation)
{{#include ../banners/hacktricks-training.md}}