Rynek działał od ośmiu miesięcy. Dwóch współzałożycieli, żaden z doświadczeniem w dziedzinie bezpieczeństwa, zbudowało go na Bubble.io z wykorzystaniem Stripe Connect do płatności. Przetworzył ponad 40 000 USD w transakcjach, miał 1500 zarejestrowanych użytkowników i stale rósł dzięki poczcie pantoflowej.
Dwanaście minut po rozpoczęciu skanowania Penetrify, pojawiło się krytyczne odkrycie: tajny klucz API Stripe był osadzony w pliku JavaScript po stronie klienta, dostarczanym do przeglądarki każdego odwiedzającego. Był tam od momentu uruchomienia. Cztery miesiące pełnego dostępu do odczytu/zapisu do całego ich konta Stripe, dostępne dla każdego, kto otworzył DevTools.
To historia o tym, jak biznes wart 40 000 USD prawie stał się przestrogą.
Klucz API Stripe, którego nigdy nie powinieneś ujawniać
Stripe wydaje dwa typy kluczy API: klucze publiczne i klucze tajne.
Klucz publiczny (pk_live_...) jest przeznaczony do użytku w kodzie frontendowym. Może wykonywać tylko ograniczone operacje — tworzenie tokenów metod płatności, potwierdzanie płatności — i nie ma dostępu do wrażliwych danych konta. Można go bezpiecznie ujawnić w kodzie JavaScript po stronie klienta.
Klucz tajny (sk_live_...) to zupełnie inna sprawa. Posiadając tajny klucz Stripe, możesz:
- Wyświetlić wszystkich klientów, metody płatności i subskrypcje
- Odczytać pełne odciski kart i adresy rozliczeniowe dla wszystkich klientów
- Wykonywać dowolne zwroty środków dla każdej transakcji
- Tworzyć obciążenia dla dowolnej zapisanej metody płatności
- Modyfikować lub usuwać harmonogramy wypłat i dane kont bankowych
- Uzyskać dostęp do wszystkich Połączonych Kont (w tym przypadku, wszystkich kont płatniczych sprzedawców na rynku)
- Pobrać dane kont bankowych dla każdego sprzedawcy, który dołączył za pośrednictwem Stripe Connect
Tajny klucz Stripe to klucz główny do całej Twojej infrastruktury płatniczej. Powinien znajdować się wyłącznie w kodzie po stronie serwera, w zmiennych środowiskowych, które nigdy nie są wysyłane do przeglądarki.
Jak trafił do pakietu
Bubble.io to platforma no-code, która pozwala budować pełnowymiarowe aplikacje internetowe bez pisania kodu. Posiada wbudowany system przepływu pracy dla logiki po stronie serwera oraz konektor API dla usług zewnętrznych. Dla dwuosobowego zespołu bez doświadczenia inżynierskiego jest to legalny sposób na szybkie wprowadzenie prawdziwego produktu na rynek.
Tajny klucz Stripe trafił do frontendu z powodu, który będzie znany każdemu, kto budował na platformie no-code pod presją czasu: działał.
Podczas rozwoju, zespół musiał wykonywać wywołania API Stripe z przepływów pracy Bubble. Dodali tajny klucz do konfiguracji konektora API. W pewnym momencie konfiguracji — czy to z powodu niezrozumienia wykonania po stronie klienta vs. po stronie serwera, czy też nieoczywistego szczegółu konfiguracji Bubble — klucz został włączony do ładunku JavaScript wysyłanego do przeglądarki. Przepływy płatności działały. Transakcje były przetwarzane poprawnie. Nie było błędu, ostrzeżenia, żadnego oczywistego symptomu.
Platforma Bubble domyślnie poprawnie obsługuje wiele operacji po stronie serwera, ale granica między tym, co działa w przeglądarce, a tym, co działa na serwerach Bubble, nie zawsze jest wizualnie oczywista w kreatorze no-code. Jest to dobrze udokumentowane ryzyko w społeczności deweloperów Bubble, ale łatwo je przeoczyć, gdy skupiasz się na tym, aby produkt działał, zamiast audytować, jakie dane Twoja aplikacja wysyła do klienta.
Co faktycznie oznaczało ujawnienie
Bądźmy precyzyjni, co było zagrożone przez te cztery miesiące.
Każdy, kto odwiedził rynek, mógł otworzyć Chrome DevTools, przejść do zakładki Sources lub Network, przeszukać pliki JavaScript w poszukiwaniu sk_live_ i znaleźć klucz. Nie wymaga to żadnych narzędzi hakerskich, specjalnej wiedzy ani żadnej luki w zabezpieczeniach poza możliwością kliknięcia prawym przyciskiem myszy i sprawdzenia strony internetowej.
Mając ten klucz, mogli:
- Opróżnić saldo Stripe firmy poprzez wystawianie zwrotów za zakończone transakcje, cofając już zarobione przychody
- Wyliczyć wszystkie rekordy klientów, w tym imiona, adresy e-mail, częściowe dane kart i adresy rozliczeniowe — naruszenie danych podlegające zgłoszeniu zgodnie z RODO i różnymi amerykańskimi przepisami stanowymi dotyczącymi prywatności
- Uzyskać dostęp do danych kont bankowych sprzedawców dla każdego sprzedawcy, który połączył swoje konto wypłat przez Stripe Connect — imiona, numery kont, numery rozliczeniowe
- Zmodyfikować harmonogramy wypłat, aby przekierować wypłaty sprzedawców na konta kontrolowane przez atakującego
- Tworzyć fałszywe obciążenia dla dowolnej zapisanej metody płatności klienta, do limitów Stripe dla danego konta
Każdy z tych wyników zakończyłby działalność firmy. W połączeniu stanowią one rodzaj katastrofalnego naruszenia, które trafia na prelekcje konferencji bezpieczeństwa.
Klucz Stripe był ujawniony przez 4 miesiące. W tym czasie rynek odwiedziło około 8000 osób. Każda z nich mogła go znaleźć.
Pozostałe Ustalenia
Klucz Stripe był najpoważniejszym odkryciem, ale skanowanie ujawniło cztery dodatkowe problemy:
ŚREDNI — Błędna konfiguracja zasad prywatności Bubble
Zasady prywatności Bubble kontrolują, które pola bazy danych są widoczne dla różnych ról użytkowników. Rekordy profili sprzedawców — które zawierały dane kont bankowych wprowadzone podczas wdrażania Stripe Connect — były widoczne dla każdego uwierzytelnionego użytkownika za pośrednictwem interfejsu API danych Bubble. Nawet bez klucza Stripe, każdy zalogowany kupujący mógłby wyszukać informacje finansowe sprzedawcy.
ŚREDNI — Wyliczanie kont za pomocą resetowania hasła
Proces resetowania hasła zwracał różne odpowiedzi dla zarejestrowanych i niezarejestrowanych adresów e-mail. Żądanie dla zarejestrowanego adresu e-mail zwracało „Sprawdź swoją skrzynkę odbiorczą”; żądanie dla niezarejestrowanego adresu e-mail zwracało „Nie znaleziono konta”. Pozwala to atakującemu na zbudowanie listy adresów e-mail posiadających konta na platformie — przydatne do ukierunkowanego phishingu lub credential stuffing.
ŚREDNI — Brak Content Security Policy
Aplikacja nie zwracała nagłówka Content-Security-Policy. Funkcjonalność wyszukiwania odzwierciedlała dane wejściowe dostarczone przez użytkownika bez kodowania w niektórych kontekstach, co umożliwiało odbite XSS. Bez CSP, ładunek XSS mógłby wyeksfiltrować tokeny sesji, wykonywać uwierzytelnione wywołania API w imieniu ofiary lub wstrzykiwać złośliwe skrypty na stronę dla innych odwiedzających.
NISKI — CORS wildcard
API zwracało Access-Control-Allow-Origin: *, umożliwiając dowolnej stronie internetowej wykonywanie żądań cross-origin do punktów końcowych API i odczytywanie odpowiedzi. Dla punktów końcowych zwracających wrażliwe dane, umożliwia to eksfiltrację danych cross-site ze złośliwej strony odwiedzanej przez ofiarę.
Reakcja: Natychmiastowa i Skuteczna
Założyciele zareagowali w ciągu godziny od otrzymania raportu.
Pierwszym krokiem było obrócenie skompromitowanego klucza Stripe. W panelu Stripe, w sekcji Developers → API Keys, klucz tajny produkcyjny można „obrócić” — generując nowy klucz i natychmiast unieważniając stary. Zajęło to około dwóch minut. Od tego momentu nikt, kto wyodrębnił ujawniony klucz, nie mógł go już używać.
Drugim krokiem było przeprowadzenie audytu dzienników zdarzeń Stripe pod kątem nieautoryzowanej aktywności. Panel Stripe zapewnia pełny dziennik każdego wywołania API wykonanego za pomocą Twoich kluczy, w tym adres IP i znacznik czasu każdego żądania. Założyciele przejrzeli dziennik zdarzeń z poprzednich czterech miesięcy, szukając anomalnych wywołań — zwrotów, których nie wystawili, klientów, których nie utworzyli, zmian wypłat, których nie dokonali. Nie znaleźli żadnych. Klucz został ujawniony, ale — o ile można było ustalić — nie był aktywnie wykorzystywany.
Trzecim krokiem było naprawienie konfiguracji Bubble. Pracując z deweloperem Bubble, którego zatrudnili na kilka godzin, przenieśli wszystkie wywołania Stripe API do serwerowych przepływów pracy (backend workflows) Bubble, gdzie klucze API nie są przesyłane do przeglądarki. Zasady prywatności Bubble również zostały poprawione, aby ograniczyć dostęp do danych finansowych sprzedawcy tylko do odpowiedniego konta sprzedawcy.
Jak znaleźć to we własnej aplikacji
Jeśli używasz aplikacji Bubble, Webflow lub innej aplikacji no-code, która integruje się ze Stripe, oto jak sprawdzić, czy masz ten problem:
- Otwórz swoją aplikację w oknie incognito.
- Otwórz Chrome DevTools (F12) → zakładka Network.
- Odśwież stronę.
- W zakładce Network poszukaj plików JavaScript. Dla każdego z nich kliknij i wyszukaj (Ctrl+F)
sk_live_. - Sprawdź również zakładkę Sources. Użyj Ctrl+Shift+F, aby przeszukać wszystkie załadowane skrypty pod kątem
sk_live_.
Jeśli znajdziesz swój tajny klucz Stripe w którymkolwiek z tych plików, natychmiast go zmień, zanim zrobisz cokolwiek innego, a następnie zbadaj, jak się tam znalazł.
To samo sprawdzenie dotyczy każdego innego wrażliwego klucza API: OpenAI, Twilio, SendGrid, AWS, Mailchimp. Każdy klucz z dostępem do zapisu lub dostępem do wrażliwych danych, który znajdziesz w klienckim kodzie JavaScript, powinien być traktowany jako skompromitowany i natychmiast zmieniony.
Dlaczego ten wzorzec się utrzymuje
Ujawnienie tajnego klucza w pakietach frontendowych nie jest nową klasą podatności. Jest to znane, dobrze udokumentowane ryzyko od czasu, gdy aplikacje internetowe zaczęły używać zewnętrznych API. Dlaczego więc to się powtarza?
Odpowiedź jest taka, że doświadczenie deweloperskie to ułatwia. Platformy no-code i nowoczesne frameworki JavaScript zacierają granice między klientem a serwerem w sposób, który nie występował we wcześniejszych modelach tworzenia stron internetowych. Zmienne środowiskowe z prefiksem NEXT_PUBLIC_ są celowo wysyłane do przeglądarki; te bez prefiksu nie są. Kontekst wykonania Bubble zależy od typu przepływu pracy, którego używasz. Konfiguracje pakietów Vite i webpack określają, co trafia do przeglądarki.
Te granice są udokumentowane, ale nie są egzekwowane na poziomie narzędzi. Nie ma błędu podczas kompilacji, gdy przypadkowo ujawnisz tajny klucz. Aplikacja działa poprawnie. Ujawnienie jest ciche, nieokreślone i rośnie każdego dnia, w którym klucz pozostaje ważny.
Jedyną niezawodną obroną jest jawne skanowanie w poszukiwaniu go — i szybka zmiana, gdy go znajdziesz.
