Skip to content

fr0gb1t/supabase-enum

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🔍 supabase-enum.py

Herramienta de enumeración de schema y columnas para proyectos Supabase mediante técnicas de error-based enumeration sobre la API REST de PostgREST.

Descubre tablas ocultas, verifica si RLS está configurado correctamente, y enumera nombres de columnas — todo sin autenticación y sin acceso al código fuente.


📁 Estructura

supabase/
  supabase_enum.py               # Script principal
  README.md                      # Este archivo
  wordlists/
    tables_common.txt            # ~120 nombres genéricos de tablas
    tables_business.txt          # ~100 nombres orientados a negocios y e-commerce
    columns_common.txt           # ~100 nombres comunes de columnas

⚙️ Instalación

# Única dependencia externa
pip3 install requests

# Dar permisos de ejecución (opcional)
chmod +x supabase_enum.py

🚀 Uso

# Mínimo — usa wordlists por defecto
python3 supabase_enum.py <url> <anon_key>

# Con wordlist de tablas específica
python3 supabase_enum.py <url> <key> -w wordlists/tables_business.txt

# Con enumeración de columnas
python3 supabase_enum.py <url> <key> --columnas

# Columnas con wordlist personalizada
python3 supabase_enum.py <url> <key> --columnas -wc wordlists/columns_common.txt

# Más velocidad
python3 supabase_enum.py <url> <key> -t 20 -r 20

# Sin fase de consulta detallada
python3 supabase_enum.py <url> <key> --no-detalle

# La URL puede incluir /rest/v1 o no — ambas formas funcionan
python3 supabase_enum.py https://abc123.supabase.co <key>
python3 supabase_enum.py https://abc123.supabase.co/rest/v1 <key>

Opciones

Flag Default Descripción
-w / --wordlist wordlists/tables_common.txt Wordlist de nombres de tablas
-wc / --wordlist-cols wordlists/columns_common.txt Wordlist de nombres de columnas
-t / --threads 10 Threads concurrentes
-r / --rate 10 Requests por segundo
--columnas off Activar enumeración de columnas
--no-detalle off Saltar fase 3 (consulta detallada)

🔬 Cómo funciona

El script corre hasta cuatro fases en secuencia:

Fase 1 — Error-based enumeration (hints)

PostgREST tiene un comportamiento particular: cuando pedís una tabla que no existe, devuelve un campo hint sugiriendo la tabla más parecida fonéticamente (distancia de Levenshtein).

GET /rest/v1/customers  →  404
{
  "code": "PGRST205",
  "hint": "Perhaps you meant the table 'public.business_users'"
                                                ^^^^^^^^^^^^^^
                                                tabla real descubierta
}

El script itera la wordlist, filtra las respuestas 404 que contienen hints, y extrae los nombres de tablas reales sugeridos — incluso si esos nombres no estaban en tu wordlist original.

Clave técnica: las respuestas con hints son HTTP 404, no 200. Herramientas como ffuf las descartan por defecto a menos que se use -mc 404 explícitamente. Este script las captura siempre.

Fase 2 — Acceso directo sin auth

Consulta cada tabla directamente y evalúa la respuesta:

Respuesta Interpretación
[{...datos...}] 🔴 Sin RLS — datos reales expuestos
[] 🟡 Ambiguo — vacía O RLS silenciando (no distinguible sin auth)
PGRST301 ✅ RLS activo — acceso denegado explícito

⚠️ Nota sobre []: Supabase silencia los errores de RLS devolviendo [] en lugar de un 403. Una tabla con datos protegidos y una tabla vacía se ven igual desde afuera. El script refleja esta ambigüedad honestamente en el output.

Fase 3 — Consulta detallada

Para cada tabla descubierta, hace una consulta con limit=3&order=id.desc para ver si hay datos reales expuestos y muestra un preview del JSON.

Fase extra — Enumeración de columnas (--columnas)

Descubre los nombres de columnas reales de cada tabla mediante INSERTs con columnas inventadas. Los distintos errores de PostgreSQL revelan si la columna existe o no:

Error recibido Significado
PGRST204 — "column not found" Columna no existe → ignorar
42501 — "row-level security policy" Columna existe (RLS bloqueó el INSERT)
23xxx — constraint violation Columna existe (violó una restricción)
HTTP 201 — INSERT exitoso Columna existe + sin RLS en escritura 🔴

Corre en todas las tablas descubiertas en fases 1 y 2 combinadas.


📊 Ejemplo de output

  +------------------------------------------+
  |        supabase_enum.py                  |
  |   PostgREST schema enumeration tool      |
  +------------------------------------------+

  Target:          https://proyecto.supabase.co/rest/v1
  Wordlist tablas: wordlists/tables_business.txt (98 palabras)
  Wordlist cols:   wordlists/columns_common.txt (102 palabras)
  Threads:         10
  Rate:            10 req/s

[*] Verificando target...
[+] PostgREST confirmado

[*] Fase 1 - Error-based enumeration (buscando hints)
    -> business_users  (via hint de 'customers')
    -> order_items     (via hint de 'items')
    Progreso: 98/98 - completado

[+] 2 tabla(s) descubiertas via hints

[*] Fase 2 - Tablas accesibles sin autenticacion
    [~] orders         -> responde [] (vacia o RLS silenciando)
    [~] business_users -> responde [] (vacia o RLS silenciando)
    Progreso: 100/100 - completado

[~] 2 tabla(s) con respuesta ambigua ([])

[*] Fase extra - Enumeracion de columnas
  -> orders
    -> customer_phone   (existe)
    -> status           (existe)
    -> created_at       (existe)
    -> total            (existe, constraint)
    Progreso: 102/102 - completado
    4 columna(s) encontradas: created_at, customer_phone, status, total

==========================================
  RESUMEN
==========================================

  TABLAS DESCUBIERTAS
    business_users - hint via 'customers', responde []
    order_items    - hint via 'items', responde []
    orders         - responde []
    products       - responde []

  ESTADO DE RLS
    [+] Sin tablas con datos expuestos directamente

    [~] Responden [] (ambiguo - vacia o RLS silenciando):
        business_users
        order_items
        orders
        products

  COLUMNAS DESCUBIERTAS

    business_users - sin columnas encontradas

    orders
        created_at                existe
        customer_phone            existe
        status                    existe
        total                     existe (constraint)

==========================================

🕵️ Cómo encontrar las credenciales de Supabase

Las credenciales siempre están en el JavaScript del frontend porque el cliente se inicializa en el browser.

Método 1 — DevTools (más rápido)

Firefox / Chrome → F12 → Debugger → Search (Ctrl+Shift+F)
Buscar: supabase.co

También buscar: createClient(, SUPABASE_URL, NEXT_PUBLIC_SUPABASE

Método 2 — Grep en los bundles de Next.js

# Extraer URLs de chunks JS de la página principal
curl -s https://TARGET.com | grep -oE '/_next/static/chunks/[^"]+\.js' | sort -u

# Buscar credenciales en cada chunk
for chunk in $(curl -s https://TARGET.com | grep -oE '/_next/static/chunks/[^"]+\.js'); do
  resultado=$(curl -s "https://TARGET.com$chunk" | grep -o '"https://[a-z0-9]*.supabase\.co"')
  [ -n "$resultado" ] && echo "$chunk: $resultado"
done

Método 3 — Variables de entorno expuestas

Next.js expone en el bundle todo lo que tenga prefijo NEXT_PUBLIC_:

curl -s https://TARGET.com/_next/static/chunks/main*.js | \
  grep -oE 'NEXT_PUBLIC_[A-Z_]+":"[^"]*"'

Método 4 — Patrones directos en el bundle

# URL del proyecto
grep -oE '"https://[a-z0-9]{20}\.supabase\.co"' bundle.js

# Anon key formato nuevo
grep -oE '"sb_publishable_[a-zA-Z0-9_-]+"' bundle.js

# Anon key formato JWT (empieza con eyJ)
grep -oE '"eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+"' bundle.js

🚩 Señales de que el target usa Supabase

  • Texto en la UI: "Conectá Supabase", "seed inicial", "Supabase project"
  • Requests en el proxy a *.supabase.co/rest/v1/ o *.supabase.co/realtime/
  • Variables NEXT_PUBLIC_SUPABASE_URL en el bundle
  • Errores con código PGRST en cualquier response de la API
  • Stack Next.js + Vercel (combinación muy frecuente con Supabase)

📋 Status codes de PostgREST — referencia rápida

Situación HTTP Body Notas
Tabla existe y accesible 200 [] o [{...}]
Tabla no existe con hint 404 {"hint":"Perhaps..."} ffuf lo descarta si no usás -mc 404
RLS bloquea SELECT 200 [] silencioso Indistinguible de tabla vacía
RLS bloquea INSERT 403 {"code":"42501"} Confirma que la columna existe
Constraint en INSERT 400/409 {"code":"23xxx"} Confirma que la columna existe
Key inválida o revocada 401 {"message":"Invalid API key"}

🔧 Fix para el desarrollador

Si encontrás tablas expuestas, el fix en Supabase es:

-- 1. Activar RLS en cada tabla sensible
ALTER TABLE orders         ENABLE ROW LEVEL SECURITY;
ALTER TABLE order_items    ENABLE ROW LEVEL SECURITY;
ALTER TABLE business_users ENABLE ROW LEVEL SECURITY;

-- 2. Denegar acceso anónimo por defecto
CREATE POLICY "deny anon"
ON orders FOR ALL TO anon
USING (false);

-- 3. Si la tabla debe ser pública (ej: productos del menú)
CREATE POLICY "public read products"
ON products FOR SELECT TO anon
USING (true);

-- 4. Solo usuarios autenticados (ej: orders, users)
CREATE POLICY "authenticated only"
ON orders FOR SELECT TO authenticated
USING (true);

🌐 Otros BaaS con vulnerabilidades similares

BaaS Qué buscar en el bundle Qué testear
Firebase firebaseapp.com, apiKey: Reglas de Firestore / RTDB
Appwrite appwrite.io, new Client() /v1/databases/<id>/collections
PocketBase URL custom, pb.authStore /api/collections/<n>/records
Directus URL custom, createDirectus( /items/<collection>
Hasura hasura.io, x-hasura-admin-secret GraphQL introspection

⚠️ Uso responsable

  • Usar únicamente en aplicaciones propias o con permiso explícito y por escrito del dueño
  • Limitar el rate a ≤ 10 req/s para no afectar el servicio en producción
  • El tier gratuito de Supabase tiene límites de requests — no spamear
  • Reportar los hallazgos al desarrollador de forma responsable antes de hacerlos públicos
  • Esta herramienta es para auditoría de seguridad, no para acceso no autorizado a datos ajenos

About

Error-based schema enumeration tool for Supabase/PostgREST APIs. Discover table and column names without authentication.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages