diff --git a/src/pentesting-ci-cd/supabase-security.md b/src/pentesting-ci-cd/supabase-security.md index d1c5ba0deb..63cc704215 100644 --- a/src/pentesting-ci-cd/supabase-security.md +++ b/src/pentesting-ci-cd/supabase-security.md @@ -127,6 +127,114 @@ This is a very bad idea because supabase charges per active user so people could
+#### Auth: Server-side signup enforcement + +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. + +Quick test (from an unauthenticated client): + +```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 +``` + +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: Write bypass 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 don’t 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 owner’s 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: " \ + -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." +``` + +Hardening checklist for views and RLS: +- Prefer exposing base tables with explicit, least-privilege grants and precise RLS policies. +- If you must expose a view: + - Make it non-updatable (e.g., include expressions/joins) or deny `INSERT/UPDATE/DELETE` on the view to all untrusted roles. + - Enforce `ALTER VIEW SET (security_invoker = on)` so the invoker’s privileges are used instead of the owner’s. + - On base tables, use `ALTER TABLE FORCE ROW LEVEL SECURITY;` so even owners are subject to RLS. + - If allowing writes via an updatable view, add `WITH [LOCAL|CASCADED] CHECK OPTION` and complementary RLS on base tables to ensure only allowed rows can be written/changed. +- In Supabase, avoid granting `anon`/`authenticated` any write privileges on views unless you have verified end-to-end behavior with tests. + +Detection tip: +- From `anon` and an `authenticated` test user, attempt all CRUD operations against every exposed table/view. Any successful write where you expected denial indicates a misconfiguration. + +### OpenAPI-driven CRUD probing from anon/auth roles + +PostgREST exposes an OpenAPI document that you can use to enumerate all REST resources, then automatically probe allowed operations from low-privileged roles. + +Fetch the OpenAPI (works with the public anon key): + +```bash +curl -s https://.supabase.co/rest/v1/ \ + -H "apikey: " \ + -H "Authorization: Bearer " \ + -H "Accept: application/openapi+json" | jq '.paths | keys[]' +``` + +Probe pattern (examples): +- Read a single row (expect 401/403/200 depending on RLS): +```bash +curl -s "https://.supabase.co/rest/v1/?select=*&limit=1" \ + -H "apikey: " \ + -H "Authorization: Bearer " +``` +- Test UPDATE is blocked (use a non-existing filter to avoid altering data during testing): +```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" +``` +- Test INSERT is blocked: +```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/" +``` +- Test DELETE is blocked: +```bash +curl -i -X DELETE \ + -H "apikey: " \ + -H "Authorization: Bearer " \ + "https://.supabase.co/rest/v1/?id=eq.00000000-0000-0000-0000-000000000000" +``` + +Recommendations: +- Automate the previous probes for both `anon` and a minimally `authenticated` user and integrate them in CI to catch regressions. +- Treat every exposed table/view/function as a first-class surface. Don’t assume a view “inherits” the same RLS posture as its base tables. + ### Passwords & sessions It's possible to indicate the minimum password length (by default), requirements (no by default) and disallow to use leaked passwords.\ @@ -160,7 +268,13 @@ It's possible to set an SMTP to send emails. It's possible to **store secrets** in supabase also which will be **accessible by edge functions** (the can be created and deleted from the web, but it's not possible to access their value directly). -{{#include ../banners/hacktricks-training.md}} - +## 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}}