From ac3901d8765661e8a01425f13039f3a9dcecaf33 Mon Sep 17 00:00:00 2001 From: Translator Date: Mon, 29 Sep 2025 23:10:18 +0000 Subject: [PATCH] Translated ['', 'src/pentesting-ci-cd/supabase-security.md'] to fr --- src/pentesting-ci-cd/supabase-security.md | 194 +++++++++++++++++----- 1 file changed, 152 insertions(+), 42 deletions(-) diff --git a/src/pentesting-ci-cd/supabase-security.md b/src/pentesting-ci-cd/supabase-security.md index c73700e64..7e9a5d5b8 100644 --- a/src/pentesting-ci-cd/supabase-security.md +++ b/src/pentesting-ci-cd/supabase-security.md @@ -1,49 +1,49 @@ -# Sécurité de Supabase +# Supabase Security {{#include ../banners/hacktricks-training.md}} ## Informations de base -Selon leur [**page d'accueil**](https://supabase.com/): Supabase est une alternative open source à Firebase. Commencez votre projet avec une base de données Postgres, Authentification, APIs instantanées, Fonctions Edge, abonnements en temps réel, Stockage et embeddings vectoriels. +Comme indiqué sur leur [**landing page**](https://supabase.com/) : Supabase est une alternative open source à Firebase. Démarrez votre projet avec une base de données Postgres, Authentication, instant APIs, Edge Functions, Realtime subscriptions, Storage, and Vector embeddings. ### Sous-domaine -Fondamentalement, lorsqu'un projet est créé, l'utilisateur recevra un sous-domaine supabase.co comme : **`jnanozjdybtpqgcwhdiz.supabase.co`** +Lorsque un projet est créé, l'utilisateur reçoit généralement un sous-domaine supabase.co tel que : **`jnanozjdybtpqgcwhdiz.supabase.co`** ## **Configuration de la base de données** > [!TIP] -> **Ces données peuvent être accessibles via un lien comme `https://supabase.com/dashboard/project//settings/database`** +> **This data can be accessed from a link like `https://supabase.com/dashboard/project//settings/database`** -Cette **base de données** sera déployée dans une région AWS, et pour s'y connecter, il serait possible de le faire en se connectant à : `postgres://postgres.jnanozjdybtpqgcwhdiz:[YOUR-PASSWORD]@aws-0-us-west-1.pooler.supabase.com:5432/postgres` (cela a été créé dans us-west-1).\ -Le mot de passe est un **mot de passe que l'utilisateur a saisi** précédemment. +Cette **database** sera déployée dans une région AWS, et pour s'y connecter il est possible de le faire en se connectant à : `postgres://postgres.jnanozjdybtpqgcwhdiz:[YOUR-PASSWORD]@aws-0-us-west-1.pooler.supabase.com:5432/postgres` (cela a été créé en us-west-1).\ +Le mot de passe est le **mot de passe choisi précédemment par l'utilisateur**. -Par conséquent, comme le sous-domaine est connu et qu'il est utilisé comme nom d'utilisateur et que les régions AWS sont limitées, il pourrait être possible d'essayer de **forcer le mot de passe**. +Ainsi, comme le sous-domaine est connu et qu'il est utilisé comme nom d'utilisateur et que les régions AWS sont limitées, il pourrait être possible d'essayer de **brute force the password**. -Cette section contient également des options pour : +Cette section contient aussi des options pour : -- Réinitialiser le mot de passe de la base de données -- Configurer le pooling de connexions -- Configurer SSL : Rejeter les connexions en texte clair (par défaut, elles sont activées) -- Configurer la taille du disque -- Appliquer des restrictions et des interdictions réseau +- Reset the database password +- Configure connection pooling +- Configure SSL: Reject plan-text connections (by default they are enabled) +- Configure Disk size +- Apply network restrictions and bans ## Configuration de l'API > [!TIP] -> **Ces données peuvent être accessibles via un lien comme `https://supabase.com/dashboard/project//settings/api`** +> **This data can be accessed from a link like `https://supabase.com/dashboard/project//settings/api`** -L'URL pour accéder à l'API supabase dans votre projet sera comme : `https://jnanozjdybtpqgcwhdiz.supabase.co`. +L'URL pour accéder à l'API Supabase de votre projet ressemblera à : `https://jnanozjdybtpqgcwhdiz.supabase.co`. -### clés API anon +### anon api keys -Elle générera également une **clé API anon** (`role: "anon"`), comme : `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTQ5OTI3MTksImV4cCI6MjAzMDU2ODcxOX0.sRN0iMGM5J741pXav7UxeChyqBE9_Z-T0tLA9Zehvqk` que l'application devra utiliser pour contacter la clé API exposée dans notre exemple dans +Elle générera aussi une **anon API key** (`role: "anon"`), comme : `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTQ5OTI3MTksImV4cCI6MjAzMDU2ODcxOX0.sRN0iMGM5J741pXav7UxeChyqBE9_Z-T0tLA9Zehvqk` que l'application devra utiliser pour contacter l'API exposée dans notre exemple dans -Il est possible de trouver l'API REST pour contacter cette API dans les [**docs**](https://supabase.com/docs/reference/self-hosting-auth/returns-the-configuration-settings-for-the-gotrue-server), mais les points de terminaison les plus intéressants seraient : +Il est possible de trouver l'API REST pour contacter cette API dans les [**docs**](https://supabase.com/docs/reference/self-hosting-auth/returns-the-configuration-settings-for-the-gotrue-server), mais les endpoints les plus intéressants seraient :
-Inscription (/auth/v1/signup) +Signup (/auth/v1/signup) ``` POST /auth/v1/signup HTTP/2 Host: id.io.net @@ -99,61 +99,171 @@ Priority: u=1, i ```
-Donc, chaque fois que vous découvrez un client utilisant supabase avec le sous-domaine qui lui a été accordé (il est possible qu'un sous-domaine de l'entreprise ait un CNAME sur leur sous-domaine supabase), vous pourriez essayer de **créer un nouveau compte sur la plateforme en utilisant l'API supabase**. +Ainsi, chaque fois que vous découvrez un client utilisant supabase avec le sous-domaine qui lui a été attribué (il est possible qu'un sous-domaine de l'entreprise ait un CNAME pointant vers leur sous-domaine supabase), vous pouvez essayer de **créer un nouveau compte sur la plateforme en utilisant l'API supabase**. -### clés API secret / service_role +### Clés API secret / service_role -Une clé API secrète sera également générée avec **`role: "service_role"`**. Cette clé API doit rester secrète car elle pourra contourner **Row Level Security**. +Une clé API secrète sera également générée avec **`role: "service_role"`**. Cette clé API doit rester secrète car elle pourra contourner la **Row Level Security**. La clé API ressemble à ceci : `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTcxNDk5MjcxOSwiZXhwIjoyMDMwNTY4NzE5fQ.0a8fHGp3N_GiPq0y0dwfs06ywd-zhTwsm486Tha7354` ### JWT Secret -Un **JWT Secret** sera également généré afin que l'application puisse **créer et signer des jetons JWT personnalisés**. +Un **JWT Secret** sera également généré afin que l'application puisse **créer et signer des tokens JWT personnalisés**. ## Authentification ### Inscription > [!TIP] -> Par **défaut**, supabase permettra aux **nouveaux utilisateurs de créer des comptes** sur votre projet en utilisant les points de terminaison API mentionnés précédemment. +> Par **défaut** supabase permettra aux **nouveaux utilisateurs de créer des comptes** sur votre projet en utilisant les endpoints API mentionnés précédemment. -Cependant, ces nouveaux comptes, par défaut, **devront valider leur adresse e-mail** pour pouvoir se connecter au compte. Il est possible d'activer **"Autoriser les connexions anonymes"** pour permettre aux personnes de se connecter sans vérifier leur adresse e-mail. Cela pourrait donner accès à **des données inattendues** (ils obtiennent les rôles `public` et `authenticated`).\ -C'est une très mauvaise idée car supabase facture par utilisateur actif, donc les gens pourraient créer des utilisateurs et se connecter et supabase facturera pour ceux-ci : +Cependant, ces nouveaux comptes, par défaut, **devront valider leur adresse e‑mail** pour pouvoir se connecter au compte. Il est possible d'activer **"Allow anonymous sign-ins"** pour permettre aux personnes de se connecter sans vérifier leur adresse e‑mail. Cela pourrait donner accès à des **données inattendues** (ils obtiennent les rôles `public` et `authenticated`).\ +C'est une très mauvaise idée car supabase facture par utilisateur actif, donc des personnes pourraient créer des utilisateurs, se connecter et supabase facturera pour ceux-ci :
-### Mots de passe et sessions +#### Auth : application côté serveur des restrictions d'inscription -Il est possible d'indiquer la longueur minimale des mots de passe (par défaut), les exigences (aucune par défaut) et d'interdire l'utilisation de mots de passe compromis.\ -Il est recommandé de **renforcer les exigences car celles par défaut sont faibles**. +Masquer le bouton d'inscription dans le frontend ne suffit pas. Si le **serveur Auth autorise toujours les inscriptions**, un attaquant peut appeler l'API directement avec la clé publique `anon` et créer des utilisateurs arbitraires. -- Sessions utilisateur : Il est possible de configurer le fonctionnement des sessions utilisateur (délai d'expiration, 1 session par utilisateur...) +Test rapide (depuis un client non authentifié) : +```bash +curl -X POST \ +-H "apikey: " \ +-H "Authorization: Bearer " \ +-H "Content-Type: application/json" \ +-d '{"email":"attacker@example.com","password":"Sup3rStr0ng!"}' \ +https://.supabase.co/auth/v1/signup +``` +Durcissement attendu : +- Désactiver les inscriptions par email/mot de passe dans le Dashboard : Authentication → Providers → Email → Disable sign ups (invite-only), ou définir l'équivalent dans GoTrue. +- Vérifier que l'API renvoie maintenant un code 4xx à l'appel précédent et qu'aucun nouvel utilisateur n'est créé. +- Si vous dépendez des invitations ou du SSO, assurez-vous que tous les autres providers sont désactivés sauf si explicitement nécessaires. + +## RLS and Views: contournement d'écriture via PostgREST + +Utiliser une Postgres VIEW pour « masquer » des colonnes sensibles et l'exposer via PostgREST peut modifier la façon dont les privilèges sont évalués. Dans PostgreSQL : +- Les vues ordinaires s'exécutent par défaut avec les privilèges du propriétaire de la view (definer semantics). Dans PG ≥15, vous pouvez opter pour `security_invoker`. +- Row Level Security (RLS) s'applique aux tables de base. Les propriétaires des tables contournent le RLS sauf si `FORCE ROW LEVEL SECURITY` est défini sur la table. +- Les vues updatables peuvent accepter des INSERT/UPDATE/DELETE qui sont ensuite appliqués à la table de base. Sans `WITH CHECK OPTION`, des écritures qui ne correspondent pas au prédicat de la view peuvent tout de même réussir. + +Schéma de risque observé sur le terrain : +- Une view ne contenant que certaines colonnes est exposée via Supabase REST et son accès est accordé à `anon`/`authenticated`. +- PostgREST autorise du DML sur la view updatable et l'opération est évaluée avec les privilèges du propriétaire de la view, contournant de fait les politiques RLS prévues sur la table de base. +- Conséquence : des clients à faible privilège peuvent modifier en masse des lignes (p. ex. bios/avatars de profil) qu'ils ne devraient pas pouvoir modifier. + +Écriture illustrative via la view (tentative depuis un client public) : +```bash +curl -X PATCH \ +-H "apikey: " \ +-H "Authorization: Bearer " \ +-H "Content-Type: application/json" \ +-H "Prefer: return=representation" \ +-d '{"bio":"pwned","avatar_url":"https://i.example/pwn.png"}' \ +"https://.supabase.co/rest/v1/users_view?id=eq." +``` +Checklist de durcissement pour les vues et RLS : +- Privilégiez l'exposition des tables de base avec des autorisations explicites au moindre privilège et des politiques RLS précises. +- Si vous devez exposer une vue : +- Rendez-la non modifiable (par ex., inclure des expressions/jointures) ou refusez `INSERT/UPDATE/DELETE` sur la vue pour tous les rôles non fiables. +- Appliquez `ALTER VIEW SET (security_invoker = on)` pour que les privilèges de l'invocateur soient utilisés au lieu de ceux du propriétaire. +- Sur les tables de base, utilisez `ALTER TABLE FORCE ROW LEVEL SECURITY;` afin que même les propriétaires soient soumis au RLS. +- Si vous autorisez des écritures via une vue modifiable, ajoutez `WITH [LOCAL|CASCADED] CHECK OPTION` et des RLS complémentaires sur les tables de base pour garantir que seules les lignes autorisées peuvent être écrites/modifiées. +- Dans Supabase, évitez d'accorder à `anon`/`authenticated` des privilèges d'écriture sur les vues sauf si vous avez vérifié le comportement end-to-end avec des tests. + +Detection tip: +- Depuis un user de test `anon` et `authenticated`, tentez toutes les opérations CRUD sur chaque table/vue exposée. Toute écriture réussie alors que vous attendiez un refus indique une mauvaise configuration. + +### Sondage CRUD piloté par OpenAPI depuis les rôles anon/auth + +PostgREST expose un document OpenAPI que vous pouvez utiliser pour énumérer toutes les ressources REST, puis sonder automatiquement les opérations autorisées depuis des rôles peu privilégiés. + +Récupérez l'OpenAPI (fonctionne avec la clé publique anon) : +```bash +curl -s https://.supabase.co/rest/v1/ \ +-H "apikey: " \ +-H "Authorization: Bearer " \ +-H "Accept: application/openapi+json" | jq '.paths | keys[]' +``` +Modèle de sonde (exemples): +- Lire une seule ligne (s'attendre à 401/403/200 selon RLS): +```bash +curl -s "https://.supabase.co/rest/v1/?select=*&limit=1" \ +-H "apikey: " \ +-H "Authorization: Bearer " +``` +- Tester que UPDATE est bloqué (utilisez un filtre inexistant pour éviter d'altérer les données pendant les tests) : +```bash +curl -i -X PATCH \ +-H "apikey: " \ +-H "Authorization: Bearer " \ +-H "Content-Type: application/json" \ +-H "Prefer: return=minimal" \ +-d '{"__probe":true}' \ +"https://.supabase.co/rest/v1/?id=eq.00000000-0000-0000-0000-000000000000" +``` +- Vérifier si INSERT est bloqué : +```bash +curl -i -X POST \ +-H "apikey: " \ +-H "Authorization: Bearer " \ +-H "Content-Type: application/json" \ +-H "Prefer: return=minimal" \ +-d '{"__probe":true}' \ +"https://.supabase.co/rest/v1/" +``` +- Vérifier que DELETE est bloqué: +```bash +curl -i -X DELETE \ +-H "apikey: " \ +-H "Authorization: Bearer " \ +"https://.supabase.co/rest/v1/?id=eq.00000000-0000-0000-0000-000000000000" +``` +Recommandations: +- Automatisez les tests précédents pour `anon` et pour un utilisateur `authenticated` minimal, et intégrez-les dans la CI pour détecter les régressions. +- Traitez chaque table/vue/fonction exposée comme une surface de premier ordre. Ne supposez pas qu'une vue “hérite” de la même posture RLS que ses tables de base. + +### Mots de passe & sessions + +Il est possible d'indiquer la longueur minimale du mot de passe (par défaut), les exigences (aucune par défaut) et d'interdire l'utilisation de leaked passwords.\ +Il est recommandé d'**améliorer les exigences car celles par défaut sont faibles**. + +- Sessions utilisateur : Il est possible de configurer le fonctionnement des sessions (timeouts, 1 session par utilisateur...) - Protection contre les bots et les abus : Il est possible d'activer Captcha. ### Paramètres SMTP -Il est possible de définir un SMTP pour envoyer des e-mails. +Il est possible de configurer un serveur SMTP pour envoyer des e-mails. ### Paramètres avancés -- Définir le temps d'expiration des jetons d'accès (3600 par défaut) -- Détecter et révoquer les jetons de rafraîchissement potentiellement compromis et le délai d'expiration -- MFA : Indiquer combien de facteurs MFA peuvent être enregistrés à la fois par utilisateur (10 par défaut) -- Max Direct Database Connections : Nombre maximum de connexions utilisées pour l'authentification (10 par défaut) -- Max Request Duration : Temps maximum autorisé pour qu'une demande d'authentification dure (10s par défaut) +- Définir le temps d'expiration des access tokens (3600 par défaut) +- Activer la détection et la révocation des refresh tokens potentiellement compromis ainsi que le timeout +- MFA : Indiquer combien de facteurs MFA peuvent être enregistrés simultanément par utilisateur (10 par défaut) +- Max Direct Database Connections : Nombre maximal de connexions utilisées pour l'auth (10 par défaut) +- Max Request Duration : Durée maximale autorisée pour une requête Auth (10s par défaut) ## Stockage > [!TIP] -> Supabase permet **de stocker des fichiers** et de les rendre accessibles via une URL (il utilise des buckets S3). +> Supabase permet **de stocker des fichiers** et de les rendre accessibles via une URL (il utilise S3 buckets). -- Définir la limite de taille de fichier à télécharger (la valeur par défaut est de 50 Mo) -- La connexion S3 est donnée avec une URL comme : `https://jnanozjdybtpqgcwhdiz.supabase.co/storage/v1/s3` -- Il est possible de **demander une clé d'accès S3** qui est formée par un `access key ID` (par exemple, `a37d96544d82ba90057e0e06131d0a7b`) et une `secret access key` (par exemple, `58420818223133077c2cec6712a4f909aec93b4daeedae205aa8e30d5a860628`) +- Définir la taille maximale d'upload (par défaut 50MB) +- La connexion S3 est fournie avec une URL comme : `https://jnanozjdybtpqgcwhdiz.supabase.co/storage/v1/s3` +- Il est possible de **demander des S3 access key** qui sont composées d'un `access key ID` (ex. `a37d96544d82ba90057e0e06131d0a7b`) et d'un `secret access key` (ex. `58420818223133077c2cec6712a4f909aec93b4daeedae205aa8e30d5a860628`) -## Fonctions Edge +## Edge Functions -Il est possible de **stocker des secrets** dans supabase également, qui seront **accessibles par des fonctions edge** (elles peuvent être créées et supprimées depuis le web, mais il n'est pas possible d'accéder directement à leur valeur). +Il est aussi possible de **stocker des secrets** dans supabase qui seront **accessibles by edge functions** (ils peuvent être créés et supprimés depuis le web, mais il n'est pas possible d'accéder directement à leur valeur). + +## References + +- [Building Hacker Communities: Bug Bounty Village, getDisclosed’s 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}}