Skip to content

Commit 23bc398

Browse files
authored
Merge pull request #208 from HackTricks-wiki/update_Building_Hacker_Communities__Bug_Bounty_Village__g_20250915_123837
Building Hacker Communities Bug Bounty Village, getDisclosed...
2 parents 09891dd + 57e3068 commit 23bc398

File tree

1 file changed

+116
-2
lines changed

1 file changed

+116
-2
lines changed

src/pentesting-ci-cd/supabase-security.md

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,114 @@ This is a very bad idea because supabase charges per active user so people could
127127

128128
<figure><img src="../images/image (1) (1) (1) (1) (1) (1).png" alt=""><figcaption></figcaption></figure>
129129

130+
#### Auth: Server-side signup enforcement
131+
132+
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.
133+
134+
Quick test (from an unauthenticated client):
135+
136+
```bash
137+
curl -X POST \
138+
-H "apikey: <SUPABASE_ANON_KEY>" \
139+
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
140+
-H "Content-Type: application/json" \
141+
-d '{"email":"[email protected]","password":"Sup3rStr0ng!"}' \
142+
https://<PROJECT_REF>.supabase.co/auth/v1/signup
143+
```
144+
145+
Expected hardening:
146+
- Disable email/password signups in the Dashboard: Authentication → Providers → Email → Disable sign ups (invite-only), or set the equivalent GoTrue setting.
147+
- Verify the API now returns 4xx to the previous call and no new user is created.
148+
- If you rely on invites or SSO, ensure all other providers are disabled unless explicitly needed.
149+
150+
## RLS and Views: Write bypass via PostgREST
151+
152+
Using a Postgres VIEW to “hide” sensitive columns and exposing it via PostgREST can change how privileges are evaluated. In PostgreSQL:
153+
- Ordinary views execute with the privileges of the view owner by default (definer semantics). In PG ≥15 you can opt into `security_invoker`.
154+
- Row Level Security (RLS) applies on base tables. Table owners bypass RLS unless `FORCE ROW LEVEL SECURITY` is set on the table.
155+
- 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.
156+
157+
Risk pattern observed in the wild:
158+
- A reduced-column view is exposed through Supabase REST and granted to `anon`/`authenticated`.
159+
- 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.
160+
- Result: low-privileged clients can mass-edit rows (e.g., profile bios/avatars) they should not be able to modify.
161+
162+
Illustrative write via view (attempted from a public client):
163+
164+
```bash
165+
curl -X PATCH \
166+
-H "apikey: <SUPABASE_ANON_KEY>" \
167+
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
168+
-H "Content-Type: application/json" \
169+
-H "Prefer: return=representation" \
170+
-d '{"bio":"pwned","avatar_url":"https://i.example/pwn.png"}' \
171+
"https://<PROJECT_REF>.supabase.co/rest/v1/users_view?id=eq.<victim_user_id>"
172+
```
173+
174+
Hardening checklist for views and RLS:
175+
- Prefer exposing base tables with explicit, least-privilege grants and precise RLS policies.
176+
- If you must expose a view:
177+
- Make it non-updatable (e.g., include expressions/joins) or deny `INSERT/UPDATE/DELETE` on the view to all untrusted roles.
178+
- Enforce `ALTER VIEW <v> SET (security_invoker = on)` so the invoker’s privileges are used instead of the owner’s.
179+
- On base tables, use `ALTER TABLE <t> FORCE ROW LEVEL SECURITY;` so even owners are subject to RLS.
180+
- 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.
181+
- In Supabase, avoid granting `anon`/`authenticated` any write privileges on views unless you have verified end-to-end behavior with tests.
182+
183+
Detection tip:
184+
- 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.
185+
186+
### OpenAPI-driven CRUD probing from anon/auth roles
187+
188+
PostgREST exposes an OpenAPI document that you can use to enumerate all REST resources, then automatically probe allowed operations from low-privileged roles.
189+
190+
Fetch the OpenAPI (works with the public anon key):
191+
192+
```bash
193+
curl -s https://<PROJECT_REF>.supabase.co/rest/v1/ \
194+
-H "apikey: <SUPABASE_ANON_KEY>" \
195+
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
196+
-H "Accept: application/openapi+json" | jq '.paths | keys[]'
197+
```
198+
199+
Probe pattern (examples):
200+
- Read a single row (expect 401/403/200 depending on RLS):
201+
```bash
202+
curl -s "https://<PROJECT_REF>.supabase.co/rest/v1/<table>?select=*&limit=1" \
203+
-H "apikey: <SUPABASE_ANON_KEY>" \
204+
-H "Authorization: Bearer <SUPABASE_ANON_KEY>"
205+
```
206+
- Test UPDATE is blocked (use a non-existing filter to avoid altering data during testing):
207+
```bash
208+
curl -i -X PATCH \
209+
-H "apikey: <SUPABASE_ANON_KEY>" \
210+
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
211+
-H "Content-Type: application/json" \
212+
-H "Prefer: return=minimal" \
213+
-d '{"__probe":true}' \
214+
"https://<PROJECT_REF>.supabase.co/rest/v1/<table_or_view>?id=eq.00000000-0000-0000-0000-000000000000"
215+
```
216+
- Test INSERT is blocked:
217+
```bash
218+
curl -i -X POST \
219+
-H "apikey: <SUPABASE_ANON_KEY>" \
220+
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
221+
-H "Content-Type: application/json" \
222+
-H "Prefer: return=minimal" \
223+
-d '{"__probe":true}' \
224+
"https://<PROJECT_REF>.supabase.co/rest/v1/<table_or_view>"
225+
```
226+
- Test DELETE is blocked:
227+
```bash
228+
curl -i -X DELETE \
229+
-H "apikey: <SUPABASE_ANON_KEY>" \
230+
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
231+
"https://<PROJECT_REF>.supabase.co/rest/v1/<table_or_view>?id=eq.00000000-0000-0000-0000-000000000000"
232+
```
233+
234+
Recommendations:
235+
- Automate the previous probes for both `anon` and a minimally `authenticated` user and integrate them in CI to catch regressions.
236+
- 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.
237+
130238
### Passwords & sessions
131239

132240
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.
160268

161269
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).
162270

163-
{{#include ../banners/hacktricks-training.md}}
164-
271+
## References
165272

273+
- [Building Hacker Communities: Bug Bounty Village, getDisclosed’s Supabase Misconfig, and the LHE Squad (Ep. 133) – YouTube](https://youtu.be/NI-eXMlXma4)
274+
- [Critical Thinking Podcast – Episode 133 page](https://www.criticalthinkingpodcast.io/episode-133-building-hacker-communities-bug-bounty-village-getdisclosed-and-the-lhe-squad/)
275+
- [Supabase: Row Level Security (RLS)](https://supabase.com/docs/guides/auth/row-level-security)
276+
- [PostgreSQL: Row Security Policies](https://www.postgresql.org/docs/current/ddl-rowsecurity.html)
277+
- [PostgreSQL: CREATE VIEW (security_invoker, check option)](https://www.postgresql.org/docs/current/sql-createview.html)
278+
- [PostgREST: OpenAPI documentation](https://postgrest.org/en/stable/references/api.html#openapi-documentation)
166279

280+
{{#include ../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)