● CWE-862 · OWASP A01:2021

Reglas de acceso en Supabase y Firebase (CWE-862): cómo la falta de Row Level Security expone datos y cómo corregirlo

Las apps con Supabase y Firebase dejan que el navegador consulte la base de datos directamente con una clave pública, así que lo único que hay entre un visitante y todas las filas es la Row Level Security o las Security Rules. Una tabla creada en SQL sin enable row level security, o una regla de Firestore que se queda en allow read, write: if true, la puede leer y modificar cualquiera. Cómo las políticas con USING y WITH CHECK limitan las filas al usuario con sesión iniciada, con un ejemplo de migración de PostgreSQL.

Explicación en Lenguaje Sencillo (ELI5)

Un edificio de oficinas da a todos los visitantes la misma tarjeta del vestíbulo, impresa en el reverso del folleto. No pasa nada mientras la puerta de cada planta compruebe quién eres antes de abrirse. Una planta se montó con prisas y su puerta no tiene lector, así que la tarjeta del vestíbulo la abre y cualquiera entra a leer los archivos. La clave anon de Supabase es la tarjeta del vestíbulo: está pensada para ser pública. La Row Level Security es el lector de la puerta de cada planta. La solución es poner un lector en cada puerta y decirle exactamente qué archivos puede ver cada persona.

Conceptos Clave y Términos

Clave anon pública
La clave anon de Supabase y la configuración web de Firebase van dentro del paquete de la app, y cualquiera puede copiarlas. Identifican el proyecto, no al usuario, así que no son secretas ni protegen datos por sí solas.
Row Level Security (RLS)
Una función de PostgreSQL que filtra cada consulta mediante políticas. El Table Editor de Supabase la activa en las tablas nuevas, pero las tablas creadas con SQL o migraciones empiezan con la RLS desactivada, y los permisos por defecto dejan que anon y authenticated lean y modifiquen todas las filas.
USING y WITH CHECK
USING decide qué filas existentes puede ver, actualizar o borrar un usuario; WITH CHECK decide qué filas nuevas o modificadas puede escribir. Una política con solo USING en insert o update permite escribir filas de otra organización.
Clave service_role
La clave service_role se salta la RLS por completo. Solo debe estar en servidores y edge functions, nunca en un navegador ni en una app móvil.
Reglas de Firestore en modo de prueba
Los proyectos iniciados en modo de prueba reciben reglas que permiten todas las lecturas y escrituras hasta una fecha, y muchos acaban cambiándolas a allow read, write: if true. Las reglas deben comparar request.auth.uid con el campo de propietario del documento.

Flujo de Ataque Paso a Paso

Paso 1

El atacante abre la app

La URL de Supabase y la clave anon están en el bundle de JavaScript o en el paquete de la app móvil.

Paso 2

Consulta la tabla directamente

Llama al endpoint REST de invoices solo con la clave anon, saltándose las pantallas de la app. Una petición a un proyecto de prueba con la clave anon y sin sesión de usuario ilustra el caso.

Paso 3

La base de datos devuelve todas las filas

La RLS está desactivada y anon tiene permiso de select, así que PostgreSQL no aplica ningún filtro y devuelve las facturas de todas las organizaciones.

Paso 4

Los datos se filtran o se modifican

Con update y delete concedidos también, el atacante puede alterar o borrar registros. En 2025 se asignó la CVE-2025-48757 después de que unos investigadores encontraran que la falta de RLS exponía datos en más de 170 apps generadas por un creador de apps con IA.

Código Fuente: Vulnerable vs. Seguro

IMPLEMENTACIÓN VULNERABLE
-- migration.sql: la RLS nunca se activa en esta tabla
create table public.invoices (
  id bigint generated always as identity primary key,
  org_id uuid not null references public.orgs (id),
  customer text not null,
  amount_cents bigint not null
);

-- la clave anon pública puede leer, modificar y borrar todas las filas
grant select, insert, update, delete on public.invoices to anon, authenticated;
PARCHE SEGURO Y ROBUSTO
-- migration.sql: RLS activada, anon revocado, una política por comando
create table public.invoices (
  id bigint generated always as identity primary key,
  org_id uuid not null references public.orgs (id),
  customer text not null,
  amount_cents bigint not null
);

-- con la RLS activada, sin política no hay acceso
alter table public.invoices enable row level security;
revoke all on public.invoices from anon;

-- los usuarios solo ven facturas de las organizaciones a las que pertenecen
create policy "members read their org's invoices"
  on public.invoices for select to authenticated
  using (org_id in (select org_id from public.memberships where user_id = (select auth.uid())));

-- las filas nuevas deben ser de una organización del usuario; sin políticas de update ni delete
create policy "members add invoices to their org"
  on public.invoices for insert to authenticated
  with check (org_id in (select org_id from public.memberships where user_id = (select auth.uid())));

Lista de Verificación de Seguridad para Ingeniería

Fuentes

← Ver el directorio completo de seguridad Todas las guías de vulnerabilidades →