Seguridad de Supabase
Tip
Aprende y practica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Consulta los subscription plans!
- Únete al 💬 Discord group o al telegram group o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud github repos.
Información básica
Según su landing page: Supabase es una alternativa de código abierto a Firebase. Comienza tu proyecto con una base de datos Postgres, Authentication, instant APIs, Edge Functions, Realtime subscriptions, Storage y Vector embeddings.
Subdominio
Básicamente, cuando se crea un proyecto, el usuario recibirá un subdominio supabase.co como: jnanozjdybtpqgcwhdiz.supabase.co
Configuración de la base de datos
Tip
Estos datos pueden accederse desde un enlace como
https://supabase.com/dashboard/project/<project-id>/settings/database
Esta base de datos será desplegada en alguna región de AWS, y para conectarse a ella sería posible hacerlo conectándose a: postgres://postgres.jnanozjdybtpqgcwhdiz:[YOUR-PASSWORD]@aws-0-us-west-1.pooler.supabase.com:5432/postgres (este fue creado en us-west-1).
La contraseña es una contraseña que el usuario configuró previamente.
Por lo tanto, dado que el subdominio es conocido y se usa como nombre de usuario y las regiones de AWS son limitadas, podría ser posible intentar un brute force contra la contraseña.
Esta sección también contiene opciones para:
- Restablecer la contraseña de la base de datos
- Configurar connection pooling
- Configurar SSL: Rechazar conexiones en texto plano (por defecto están habilitadas)
- Configurar tamaño de disco
- Aplicar restricciones y bloqueos de red
Configuración de la API
Tip
Estos datos pueden accederse desde un enlace como
https://supabase.com/dashboard/project/<project-id>/settings/api
La URL para acceder a la API de supabase en tu proyecto será como: https://jnanozjdybtpqgcwhdiz.supabase.co.
anon API keys
También generará una anon API key (role: "anon"), como: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTQ5OTI3MTksImV4cCI6MjAzMDU2ODcxOX0.sRN0iMGM5J741pXav7UxeChyqBE9_Z-T0tLA9Zehvqk que la aplicación necesitará usar para contactar la API expuesta en nuestro ejemplo en
Es posible encontrar el API REST para contactar esta API en la docs, pero los endpoints más interesantes serían:
Registro (/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>Login (/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>
Entonces, siempre que descubras un cliente que use supabase con el subdominio que se le asignó (es posible que un subdominio de la empresa tenga un CNAME apuntando sobre su subdominio de supabase), puedes intentar **crear una nueva cuenta en la plataforma usando la API de supabase**.
### secret / service_role api keys
También se generará una secret API key con **`role: "service_role"`**. Esta API key debe mantenerse en secreto porque podrá eludir **Row Level Security**.
La API key se ve así: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpuYW5vemRyb2J0cHFnY3doZGl6Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTcxNDk5MjcxOSwiZXhwIjoyMDMwNTY4NzE5fQ.0a8fHGp3N_GiPq0y0dwfs06ywd-zhTwsm486Tha7354`
### JWT Secret
También se generará un **JWT Secret** para que la aplicación pueda **crear y firmar JWT tokens personalizados**.
## Autenticación
### Registros
> [!TIP]
> Por **defecto** supabase permitirá que **nuevos usuarios creen cuentas** en tu proyecto usando los endpoints de la API mencionados anteriormente.
Sin embargo, por defecto estas cuentas nuevas **necesitarán validar su dirección de email** para poder iniciar sesión en la cuenta. Es posible habilitar **"Allow anonymous sign-ins"** para permitir que la gente inicie sesión sin verificar su correo. Esto podría otorgar acceso a **datos inesperados** (obtienen los roles `public` y `authenticated`).\
Esto es una muy mala idea porque supabase cobra por usuario activo, así que la gente podría crear usuarios e iniciar sesión y supabase cobrará por ellos:
<figure><img src="../images/image (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
#### Auth: Imposición del registro en el servidor
Ocultar el botón de registro en el frontend no es suficiente. Si el **Auth server aún permite registros**, un atacante puede llamar a la API directamente con la `anon` key pública y crear usuarios arbitrarios.
Prueba rápida (desde un cliente no autenticado):
```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
Endurecimiento esperado:
- Deshabilitar registros por email/contraseña en el Dashboard: Authentication → Providers → Email → Disable sign ups (invite-only), o configurar el ajuste equivalente de GoTrue.
- Verificar que la API ahora devuelve 4xx para la llamada previa y que no se crea ningún usuario nuevo.
- Si dependes de invites o SSO, asegúrate de que todos los demás providers estén deshabilitados a menos que sean necesarios explícitamente.
RLS y Views: Bypass de escritura vía PostgREST
Usar una Postgres VIEW para “ocultar” columnas sensibles y exponerla vía PostgREST puede cambiar cómo se evalúan los privilegios. En PostgreSQL:
- Las vistas ordinarias se ejecutan con los privilegios del propietario de la view por defecto (definer semantics). En PG ≥15 puedes optar por
security_invoker. - Row Level Security (RLS) se aplica en las tablas base. Los propietarios de la tabla evitan RLS a menos que
FORCE ROW LEVEL SECURITYesté establecido en la tabla. - Las updatable views pueden aceptar INSERT/UPDATE/DELETE que luego se aplican a la tabla base. Sin
WITH CHECK OPTION, las escrituras que no coinciden con el predicado de la view pueden seguir teniendo éxito.
Patrón de riesgo observado en entornos reales:
- Una view con columnas reducidas se expone vía Supabase REST y se concede a
anon/authenticated. - PostgREST permite DML sobre la updatable view y la operación se evalúa con los privilegios del propietario de la view, eludiendo efectivamente las políticas RLS previstas en la tabla base.
- Resultado: clientes con bajos privilegios pueden editar masivamente filas (ej., bios/avatars de perfil) que no deberían poder modificar.
Ejemplo de escritura vía view (intentado desde un cliente público):
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>"
Lista de verificación de endurecimiento para views y RLS:
- Prefiere exponer las tablas base con permisos explícitos de mínimo privilegio y políticas RLS precisas.
- Si debes exponer una view:
- Hazla no actualizable (p. ej., incluye expresiones/joins) o deniega
INSERT/UPDATE/DELETEen la view a todos los roles no confiables. - Aplica
ALTER VIEW <v> SET (security_invoker = on)para que se usen los privilegios del invocador en lugar de los del propietario. - En las tablas base, usa
ALTER TABLE <t> FORCE ROW LEVEL SECURITY;para que incluso los propietarios estén sujetos a RLS. - Si permites escrituras vía una view actualizable, añade
WITH [LOCAL|CASCADED] CHECK OPTIONy políticas RLS complementarias en las tablas base para asegurar que solo las filas permitidas puedan ser escritas/modificadas. - En Supabase, evita otorgar a
anon/authenticatedprivilegios de escritura en views a menos que hayas verificado el comportamiento end-to-end con pruebas.
Detection tip:
- Desde
anony un usuario de pruebaauthenticated, intenta todas las operaciones CRUD contra cada tabla/view expuesta. Cualquier escritura exitosa donde esperabas negación indica una mala configuración.
Exploración CRUD impulsada por OpenAPI desde roles anon/auth
PostgREST expone un documento OpenAPI que puedes usar para enumerar todos los recursos REST y luego sondear automáticamente las operaciones permitidas desde roles de bajo privilegio.
Obtén el OpenAPI (funciona con la anon key pública):
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[]'
Patrón de sondeo (ejemplos):
- Leer una sola fila (esperar 401/403/200 dependiendo de 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>"
- Comprobar que UPDATE está bloqueado (usa un filtro inexistente para evitar alterar datos durante las pruebas):
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"
- Comprobar que INSERT está bloqueado:
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>"
- Probar que DELETE está bloqueado:
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"
Recomendaciones:
- Automatiza las pruebas anteriores para ambos
anony un usuario mínimamenteauthenticatede intégralas en CI para detectar regresiones. - Trata cada table/view/function expuesto como una superficie de primera clase. No asumas que una view “hereda” la misma postura RLS que sus tablas base.
Contraseñas & sesiones
Es posible indicar la longitud mínima de contraseña (por defecto), requisitos (no hay por defecto) y prohibir el uso de leaked passwords.
Se recomienda mejorar los requisitos ya que los predeterminados son débiles.
- User Sessions: Es posible configurar cómo funcionan las sesiones de usuario (tiempos de expiración, 1 sesión por usuario…)
- Bot and Abuse Protection: Es posible habilitar Captcha.
SMTP Settings
Es posible configurar un SMTP para enviar correos.
Advanced Settings
- Establecer tiempo de expiración para los access tokens (3600 por defecto)
- Detectar y revocar refresh tokens potencialmente comprometidos y caducarlos
- MFA: Indicar cuántos factores MFA pueden registrarse simultáneamente por usuario (10 por defecto)
- Max Direct Database Connections: Número máximo de conexiones usadas para auth (10 por defecto)
- Max Request Duration: Tiempo máximo permitido para que dure una solicitud de Auth (10s por defecto)
Storage
Tip
Supabase permite almacenar archivos y hacerlos accesibles mediante una URL (usa S3 buckets).
- Establecer el límite de tamaño de archivo para uploads (por defecto es 50MB)
- La conexión S3 se proporciona con una URL como:
https://jnanozjdybtpqgcwhdiz.supabase.co/storage/v1/s3 - Es posible solicitar S3 access key que se forman por un
access key ID(p. ej.a37d96544d82ba90057e0e06131d0a7b) y unsecret access key(p. ej.58420818223133077c2cec6712a4f909aec93b4daeedae205aa8e30d5a860628)
Edge Functions
Es posible almacenar secrets en supabase también, los cuales serán accesibles por edge functions (pueden crearse y eliminarse desde la web, pero no es posible acceder directamente a su valor).
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
Aprende y practica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Consulta los subscription plans!
- Únete al 💬 Discord group o al telegram group o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud github repos.
HackTricks Cloud

