Powrót do bloga
13 maja 2026

Błędna konfiguracja Supabase RLS: Jak brakująca polityka ujawniła profile wszystkich użytkowników

Viktor Bulanek
Founder & CTO, Penetrify
MSc IT Security · 20+ years in security · 4x Ex-CTO

Aplikacja wyglądała na gotową do produkcji. Czysty interfejs użytkownika, działająca integracja ze Stripe, dopracowany proces onboardingu. Była promowana na Product Hunt, zdobyła ponad 200 użytkowników w pierwszym tygodniu i już generowała MRR. Założyciel zbudował ją w 48 godzin podczas weekendu hackathonowego, używając Next.js i Supabase – stosu technologicznego wybieranego przez każdego, kto chce szybko dostarczać produkty.

Osiem minut po rozpoczęciu skanowania Penetrify, oznaczyliśmy Krytyczne odkrycie: rekord profilu każdego użytkownika — imię, adres e-mail, metadane konta — był czytelny dla każdego innego uwierzytelnionego użytkownika za pośrednictwem automatycznie generowanego REST API Supabase. Nie była wymagana żadna zaawansowana wiedza atakującego. Wystarczyło zamienić swój identyfikator użytkownika na inny w adresie URL.

To jest historia tego, jak do tego doszło, dlaczego jest to bardziej powszechne, niż ktokolwiek chce przyznać, i jak dokładnie wyglądała poprawka.


Czym jest Supabase Row Level Security — i co się dzieje, gdy jest wyłączone

Supabase jest zbudowany na PostgreSQL i udostępnia tabele bazy danych bezpośrednio przez REST API (za pośrednictwem PostgREST) oraz bibliotekę kliencką JavaScript. Jest to naprawdę potężne narzędzie do szybkiego rozwoju: możesz wykonywać zapytania do bazy danych z poziomu frontendu bez pisania ani jednej trasy API.

Mechanizm, który uniemożliwia użytkownikom odczytywanie danych innych użytkowników, nazywa się Row Level Security (RLS). RLS to funkcja PostgreSQL, która pozwala definiować zasady kontrolujące, które wiersze dany użytkownik bazy danych może SELECT, INSERT, UPDATE lub DELETE. W aplikacji Supabase zazwyczaj napisałbyś taką zasadę:

-- Zezwalaj użytkownikom tylko na odczytywanie własnego profilu
CREATE POLICY "Users can view own profile"
  ON profiles
  FOR SELECT
  USING (auth.uid() = user_id);

Gdy RLS jest włączone i taka zasada jest wdrożona, zapytanie o profil innego użytkownika zwraca zero wierszy — baza danych egzekwuje kontrolę dostępu na poziomie wiersza, zanim jakiekolwiek dane dotrą do aplikacji.

Gdy RLS jest wyłączone lub nie ma żadnej zasady, tabela zachowuje się tak, jakby każdy wiersz był publiczny dla każdego uwierzytelnionego żądania. REST API Supabase, dostępne pod adresem https://[project].supabase.co/rest/v1/profiles, zwróci wszystkie wiersze w tabeli — dane każdego użytkownika — każdemu, kto posiada ważny JWT z tego projektu.


Co znaleźliśmy: Pięć odkryć, dwa z nich Krytyczne

Skan trwał osiem minut w trybie szybkim na działającej aplikacji. Oto odkrycia w kolejności ważności:

KRYTYCZNE — Supabase RLS nie włączone w tabeli profili

Tabela profili miała wyłączone RLS. Każdy uwierzytelniony użytkownik mógł ją odpytać za pośrednictwem punktu końcowego REST Supabase i pobrać wszystkie rekordy użytkowników. Żądanie, które to zademonstrowało:

GET /rest/v1/profiles?select=*
Authorization: Bearer [any valid user JWT]

HTTP/1.1 200 OK
[
  {"id": "uuid-1", "email": "user1@example.com", "full_name": "...", ...},
  {"id": "uuid-2", "email": "user2@example.com", "full_name": "...", ...},
  ... (wszystkie ponad 200 rekordów użytkowników)
]

Zgodnie z RODO, jest to ujawnienie danych osobowych dotykające każdego zarejestrowanego użytkownika. Klucz anonimowy Supabase jest celowo osadzony w pakiecie JavaScript frontendu — jest bezpieczny do publicznego ujawnienia, gdy RLS jest prawidłowo skonfigurowane, ponieważ sam klucz nie daje podwyższonego dostępu. Bez RLS staje się kluczem głównym do wszystkich danych użytkowników.

KRYTYCZNE — Weryfikacja adresu e-mail nie jest egzekwowana na chronionych punktach końcowych

Supabase domyślnie umożliwia rejestrację za pomocą adresu e-mail i hasła i wysyła e-mail potwierdzający w celu weryfikacji adresu. Jednak trasy API backendu aplikacji nie sprawdzały, czy użytkownik wysyłający żądanie potwierdził swój adres e-mail, przed udzieleniem dostępu do chronionej funkcjonalności.

Atakujący mógł zarejestrować się za pomocą victim@example.com (prawdziwego adresu e-mail użytkownika), całkowicie pominąć weryfikację adresu e-mail i natychmiast uzyskać dostęp do chronionych tras API aplikacji, tak jakby był właścicielem tego adresu e-mail. W połączeniu z problemem RLS oznaczało to, że nieuwierzytelniony atakujący mógł wyeksfiltrować całą bazę danych użytkowników, tworząc tymczasowe konto z dowolnym adresem e-mail.

ŚREDNI — IDOR na /api/export

Aplikacja posiadała punkt końcowy eksportu, który akceptował identyfikator użytkownika jako parametr zapytania:

GET /api/export?userId=abc123

Nie przeprowadzono weryfikacji własności po stronie serwera. Każdy uwierzytelniony użytkownik mógł eksportować dane dowolnego innego użytkownika, podstawiając swój własny identyfikator identyfikatorem celu. Identyfikatory użytkowników były ujawniane w odpowiedziach API w całej aplikacji, co czyniło enumerację trywialną.

ŚREDNI — Brak limitowania szybkości na punkcie końcowym logowania

Punkt końcowy logowania akceptował żądania uwierzytelnienia z szybkością około 500 żądań na sekundę bez dławienia lub blokady. Atak typu credential stuffing na bazę użytkowników aplikacji nie napotkałby żadnych przeszkód.

ŚREDNI — Tokeny JWT przechowywane w localStorage bez rotacji

Tokeny JWT Supabase były przechowywane w localStorage, dostępne dla każdego kodu JavaScript działającego na stronie. Nie nastąpiła rotacja tokenów przy zmianach uprawnień. Udany atak XSS na dowolnej stronie zapewniłby atakującemu trwałą, ważną sesję.


Dlaczego tak się dzieje: Pułapka domyślnych ustawień Supabase

To nie jest historia o nieostrożnym deweloperze. To historia o domyślnych ustawieniach.

Kiedy tworzysz nową tabelę w Supabase, RLS jest domyślnie wyłączone. Własna dokumentacja Supabase jasno to opisuje i zaleca włączenie RLS, ale przykłady szybkiego startu — te, które deweloperzy faktycznie śledzą, budując o 2 w nocy podczas hackathonu — często pomijają RLS dla zwięzłości. Widzisz działające zapytanie w przykładzie, kopiujesz wzorzec, wdrażasz go.

Pulpit nawigacyjny Supabase pokazuje żółtą ikonę ostrzegawczą przy tabelach bez RLS. Łatwo to przeoczyć, gdy skupiasz się na interfejsie użytkownika, integracji płatności, procesie wdrażania. Nie ma błędu, awarii podczas działania, żadnego oczywistego objawu. Wszystko działa idealnie. Użytkownicy mogą się rejestrować, logować i korzystać z aplikacji. Luka jest całkowicie cicha.

Najgroźniejsze błędy bezpieczeństwa to te, które wyglądają dokładnie jak prawidłowe zachowanie.

Ten wzorzec pojawia się w prawie każdej aplikacji opartej na Supabase, którą skanujemy, a która została zbudowana z myślą o szybkości. Nie dlatego, że deweloperzy nie dbają o bezpieczeństwo, ale dlatego, że presja czasu podczas hackathonu lub sprintu wdrożeniowego nie pozostawia miejsca na przeczytanie każdej sekcji dokumentacji.


Rozwiązanie: Dwie godziny, bez przepisywania kodu

Założyciel naprawił wszystkie pięć ustaleń w mniej niż dwie godziny tego samego wieczoru, w którym nadszedł raport. Oto dokładnie, co zostało zrobione:

RLS — 15 minut w panelu Supabase

Włączenie RLS i dodanie odpowiednich zasad nie wymagało zmian w kodzie. W panelu Supabase, w sekcji Edytor tabel → profile → Zasady RLS:

-- Enable RLS on the table (one toggle in the dashboard)
-- Then add policies:

CREATE POLICY "Users can view own profile"
  ON profiles FOR SELECT
  USING (auth.uid() = user_id);

CREATE POLICY "Users can update own profile"
  ON profiles FOR UPDATE
  USING (auth.uid() = user_id);

-- For admin access (if needed):
CREATE POLICY "Service role has full access"
  ON profiles
  USING (auth.role() = 'service_role');

Po włączeniu RLS i dodaniu tych zasad, zapytanie zbiorcze zwróciło zero wierszy dla każdego uwierzytelnionego użytkownika, który wysyłał żądanie danych, które nie należały do niego.

Weryfikacja e-mail — jedno sprawdzenie w middleware

Trasy API Next.js już odczytywały obiekt użytkownika Supabase z JWT przy każdym żądaniu. Dodanie wymuszenia weryfikacji adresu e-mail było jednowierszowym sprawdzeniem w middleware uwierzytelniania:

const { data: { user } } = await supabase.auth.getUser()
if (!user?.email_confirmed_at) {
  return res.status(403).json({ error: 'Email verification required' })
}

IDOR na /api/export — jednolinijkowa poprawka middleware

Poprawka polegała na zastąpieniu parametru userId dostarczonego przez użytkownika własnym identyfikatorem uwierzytelnionego użytkownika, wyodrębnionym ze zweryfikowanego JWT:

// Before (vulnerable)
const userId = req.query.userId

// After (fixed)
const { data: { user } } = await supabase.auth.getUser()
const userId = user.id  // always the authenticated user — can't be spoofed

Ograniczanie szybkości zapytań — wbudowane ograniczanie szybkości zapytań Vercel

Aplikacja była hostowana na Vercel. Dodanie ograniczania szybkości zapytań do punktu końcowego logowania wymagało dodania pakietu @upstash/ratelimit i opakowania trasy — około 20 linii kodu. Założyciel wdrożył tę poprawkę następnego ranka.


Narażenie na RODO

Problem z RLS dotyczył wszystkich ponad 200 zarejestrowanych użytkowników. Ich pełne rekordy profili — adresy e-mail, nazwy wyświetlane i wszelkie inne pola przechowywane w tabeli profili — były czytelne dla każdego innego uwierzytelnionego użytkownika przez cały czas działania aplikacji.

Zgodnie z art. 33 RODO, naruszenie ochrony danych osobowych, które „może skutkować ryzykiem naruszenia praw lub wolności osób fizycznych”, musi zostać zgłoszone właściwemu organowi nadzorczemu w ciągu 72 godzin od stwierdzenia naruszenia. To, czy to konkretne naruszenie przekroczyło ten próg, zależałoby od wrażliwości przechowywanych danych i tego, ile danych użytkowników zostało faktycznie uzyskanych — ale okno narażenia było otwarte.

Założyciel zmienił wszystkie wrażliwe wartości konfiguracyjne, włączył RLS i naprawił pozostałe problemy tego samego wieczoru. W logach Supabase nie znaleziono dowodów nieautoryzowanego dostępu. Problem został opanowany, zanim stał się incydentem.


Jak sprawdzić własną aplikację Supabase

Jeśli używasz aplikacji opartej na Supabase, oto szybki audyt, który możesz przeprowadzić w pięć minut:

  1. Otwórz pulpit nawigacyjny Supabase → Edytor tabel. Każda tabela bez zielonej ikony tarczy ma wyłączony RLS. Jeśli ta tabela zawiera dane użytkownika, prawdopodobnie są one narażone.
  2. Otwórz narzędzia deweloperskie przeglądarki w swojej aplikacji produkcyjnej → zakładka Sieć → filtruj według adresu URL projektu Supabase. Szukaj żądań do /rest/v1/[tablename]?select=*. Jeśli widzisz żądania zwracające wiele rekordów, sprawdź, czy powinny być one ograniczone do uwierzytelnionego użytkownika.
  3. Sprawdź swój proces weryfikacji e-mail. Zarejestruj nowe konto, pomiń e-mail potwierdzający i spróbuj uzyskać bezpośredni dostęp do chronionych części aplikacji. Jeśli możesz uzyskać dostęp do chronionej funkcjonalności bez potwierdzenia adresu e-mail, Twój backend nie wymusza weryfikacji.
  4. Sprawdź swoje punkty końcowe eksportu/pobierania. Każdy punkt końcowy, który akceptuje identyfikator użytkownika jako parametr, powinien porównać go z JWT uwierzytelnionego użytkownika. Jeśli parametr może być swobodnie podstawiony w celu uzyskania dostępu do danych innego użytkownika, jest to IDOR.

Te cztery sprawdzenia zajmują mniej niż dziesięć minut i obejmują najczęstsze luki bezpieczeństwa Supabase, które obserwujemy w aplikacjach produkcyjnych.


Szerszy wzorzec

To studium przypadku nie jest niczym niezwykłym. Jest to reprezentatywne dla tego, co znajdujemy w większości aplikacji opartych na Supabase, które zostały zbudowane podczas hackathonu, szybkiego sprintu wdrożeniowego lub przez samodzielnego założyciela bez doświadczenia w dziedzinie bezpieczeństwa.

Połączenie szybkich narzędzi, doskonałego doświadczenia deweloperskiego i domyślnych ustawień publicznych, które faworyzują łatwość użycia nad bezpieczeństwem, oznacza, że wiele aplikacji jest dostarczanych z tymi lukami. Luki te są możliwe do naprawienia — często w ciągu kilku minut, rzadko wymagając czegoś więcej niż polityki SQL lub jednolinijkowej kontroli middleware. Trudność polega na tym, aby w ogóle wiedzieć o ich istnieniu.

Zespół Supabase wykonał znaczącą pracę, aby RLS był bardziej widoczny w panelu administracyjnym i dokumentacji. Ale przepaść między „dokumentacja mówi, aby to włączyć” a „każda aplikacja produkcyjna faktycznie ma to włączone” pozostaje duża. Dopóki zautomatyzowane testy bezpieczeństwa nie staną się częścią standardowej listy kontrolnej uruchamiania, ten wzorzec będzie się powtarzał.

Frequently Asked Questions

Jakie typy podatności wykrywa Penetrify?

Penetrify wykrywa wszystkie kategorie podatności OWASP Top 10, w tym SQL injection, XSS, CSRF, IDOR, złamaną autentykację, błędne konfiguracje zabezpieczeń i ujawnianie wrażliwych danych. Testuje również bezpieczeństwo API, zarządzanie sesją oraz typowe błędy konfiguracji w Supabase, Firebase i Bubble.

Jak długo trwa test penetracyjny AI?

Szybkie skanowanie kończy się w 15–30 minut. Standardowe skanowanie trwa 1–2 godziny z szerszym zakresem. Głęboke skanowanie może trwać kilka godzin dla złożonych aplikacji.

Co zawiera raport Penetrify?

Każdy raport zawiera podsumowanie wykonawcze, ogólny wynik bezpieczeństwa, znaleziska sklasyfikowane według wagi (Krytyczne, Wysokie, Średnie, Niskie), szczegółowe kroki reprodukcji oraz konkretne wskazówki dotyczące naprawy napisane dla deweloperów – nie dla specjalistów ds. zgodności.

Related articles

CI/CD Penetration Testing: Jak wbudować bezpieczeństwo w każde wdrożenie
Dowiedz się, jak zintegrować Penetration Testing ze swoim potokiem CI/CD. Obejmuje SAST, DAST, bramki jakości oraz testowanie wspomagane sztuczną inteligencją, bez spowalniania dostarczania.
Autonomiczne skanowanie podatności OWASP: Jak AI zastępuje regułowe testowanie bezpieczeństwa
Dowiedz się, jak autonomiczne skanowanie podatności OWASP wykorzystuje AI, aby wyjść poza dopasowywanie sygnatur. Obejmuje OWASP Top 10 2025, testowanie agentowe oraz dlaczego skanery oparte na regułach nie są wystarczające.
Symulacja wieloetapowych łańcuchów ataków: Dlaczego skanowanie pojedynczych luk to za mało
Dowiedz się, jak symulacja wieloetapowych łańcuchów ataków wykrywa połączone exploity, które umykają skanerom podatności. Praktyczne przykłady, mapowanie MITRE ATT&CK oraz przewodnik wdrożeniowy.

Explore more

Compare alternatives →Security glossary →CI/CD integration →Security statistics →
Powrót do bloga