Powrót do bloga
13 maja 2026

Klucz API OpenAI w nagłówkach odpowiedzi HTTP: Wykryto w 7 minut

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

Panel rozliczeniowy OpenAI pokazywał opłaty, których założyciel nie potrafił wyjaśnić. Aplikacja miała około 800 użytkowników w modelu freemium, ale użycie API zbliżało się do 2000 USD miesięcznie — znacznie wyższe niż uzasadniała aktywność użytkowników na platformie. Założyciel założył, że gdzieś mają nieefektywny prompt i zanotował, aby to zbadać.

Siedem minut po rozpoczęciu skanowania Penetrify, powód stał się jasny: klucz API OpenAI był przekazywany użytkownikom w nagłówkach odpowiedzi HTTP każdego pośredniczonego wywołania API. 800 użytkowników to widziało. Niektórzy z nich go używali.


Architektura: Skąd pochodził wyciek

Aplikacja była backendem FastAPI obsługującym frontend React. Jej główną funkcjonalnością było pośredniczenie w żądaniach użytkowników do API OpenAI, dodawanie niestandardowych promptów systemowych, przechowywanie historii rozmów i stosowanie autorskiej warstwy inżynierii promptów założyciela. To jest powszechny wzorzec dla produktów typu AI wrapper — wartość nie tkwi w modelu, lecz w produkcie zbudowanym wokół niego.

Sposób działania aplikacji:

  1. Użytkownik wysyła prompt z frontendu React
  2. Frontend wysyła go do POST /api/generate
  3. Handler FastAPI dodaje prompt systemowy i wywołuje API OpenAI
  4. FastAPI zwraca ukończenie do frontendu

Gdzieś w implementacji routingu FastAPI, nagłówek Authorization z wychodzącego żądania OpenAI — zawierający klucz API w formacie tokenu Bearer — był przekazywany z powrotem w odpowiedzi. Jest to specyficzna klasa błędu przekazywania nagłówków: aplikacja przekazywała nagłówki odpowiedzi z nadrzędnego wywołania API OpenAI zamiast konstruować własne nagłówki odpowiedzi.

Nagłówki odpowiedzi dla każdego wywołania /api/generate zawierały:

HTTP/1.1 200 OK
Content-Type: application/json
Authorization: Bearer sk-proj-...[OpenAI API key]
...
{"completion": "..."}

Każdy użytkownik, który kiedykolwiek korzystał z funkcji generowania — wszystkich 800 — otrzymał klucz API w nagłówkach odpowiedzi swoich żądań. Było to widoczne w zakładce Sieć Narzędzi Deweloperskich przeglądarki, w każdym proxy HTTP oraz w każdym programistycznym kliencie, który odczytywał nagłówki odpowiedzi.


Co daje klucz API OpenAI

Klucz API OpenAI bez ograniczeń użytkowania daje posiadaczowi pełny dostęp do limitu API odpowiadającego konta. Oznacza to:

  • Nieograniczony dostęp do modeli na koszt właściciela klucza — GPT-4o, o1, o3, generowanie obrazów, embeddingi, dostrajanie
  • Brak limitu na żądanie do momentu osiągnięcia miesięcznego limitu wydatków konta
  • Dostęp do wszystkich dostrojonych modeli utworzonych przez konto
  • Możliwość odczytu przechowywanych plików, jeśli konto korzysta z Files API

Dla indywidualnego założyciela, którego aplikacja przetwarza 200–400 USD miesięcznie w ramach legalnego użytkowania, zewnętrzne nadużycie klucza może podnieść miesięczny rachunek do 2000 USD, 5000 USD lub więcej — w zależności od tego, jak szeroko klucz krąży i co generują osoby nadużywające.

Model kosztów nadużycia API OpenAI jest asymetryczny: atakujący nie płaci nic, właściciel klucza płaci za wszystko.


Niewyjaśnione skoki rozliczeniowe, wyjaśnione

Po zidentyfikowaniu ekspozycji klucza, skoki rozliczeniowe nabrały sensu. Założyciel pobrał panel użycia OpenAI, filtrowany według punktu końcowego i czasu. Wzorzec skoków pokazywał żądania o dużej objętości, które nie korelowały z aktywnością użytkowników na platformie — żądania o 3 nad ranem, żądania z zakresów IP, które nie pasowały do żadnej znanej geografii użytkowników, żądania dotyczące typów modeli, których aplikacja nie używała.

Ktoś wydobył klucz — być może kilka osób — i używał go bezpośrednio przeciwko OpenAI API, całkowicie omijając aplikację. Żądania trafiały bezpośrednio do OpenAI, wykorzystując wydobyte dane uwierzytelniające, a nie przez aplikację założyciela.

Klucz był ujawniony od około pierwszego tygodnia publicznego uruchomienia aplikacji. W momencie skanowania był aktywny i wyciekał przez kilka miesięcy.


Pozostałe Ustalenia

Ujawnienie klucza OpenAI było najbardziej bezpośrednio szkodliwym ustaleniem, ale zgłoszono trzy dodatkowe problemy:

MEDIUM — IDOR na /api/history/:userId

Aplikacja przechowywała historię rozmów dla każdego użytkownika i udostępniała ją pod przewidywalnym punktem końcowym:

GET /api/history/abc123

Obsługa trasy pobierała historię rozmów dla identyfikatora użytkownika w parametrze ścieżki bez sprawdzania, czy użytkownik żądający był właścicielem tych rekordów. Każdy uwierzytelniony użytkownik mógł odczytać historię rozmów dowolnego innego użytkownika, podstawiając jego identyfikator. Ponieważ rozmowy zawierały podpowiedzi dostarczone przez użytkownika, było to również naruszenie prywatności: atakujący mógł odczytać, jakie pytania inni użytkownicy zadawali narzędziu AI.

MEDIUM — Tryb debugowania FastAPI włączony w środowisku produkcyjnym

Aplikacja działała z FastAPI(debug=True). W trybie debugowania, każdy nieobsłużony wyjątek zwraca pełny ślad stosu w odpowiedzi HTTP, w tym wewnętrzne ścieżki plików, wersje zależności i nazwy zmiennych środowiskowych (choć nie ich wartości). Te informacje są bezpośrednio przydatne do planowania dalszych ataków — znajomość dokładnej wersji FastAPI, Pydantic i Python znacząco zawęża listę możliwych CVE.

Tryb debugowania domyślnie włącza również interaktywną dokumentację FastAPI pod adresami /docs i /redoc, która była dostępna w środowisku produkcyjnym i dokumentowała każdy wewnętrzny punkt końcowy API, w tym te nieprzeznaczone do dostępu użytkowników.

LOW — HTTP nie przekierowuje do HTTPS

Wersja HTTP aplikacji serwowała pełną zawartość bez przekierowywania do HTTPS. W sieciach publicznych lub współdzielonych, atakujący przeprowadzający atak typu man-in-the-middle mógł przechwycić niezaszyfrowane sesje i wydobyć tokeny sesji, podpowiedzi użytkowników oraz odpowiedzi API.


Naprawa: Wdrożona Tego Samego Wieczoru

Założyciel wdrożył poprawki dla wszystkich ustaleń w ciągu trzech godzin od otrzymania raportu.

Najpierw zmień klucz

Zanim dotknięto jakiegokolwiek kodu, natychmiastowym działaniem było unieważnienie skompromitowanego klucza w panelu OpenAI i wygenerowanie nowego. To natychmiastowo przerwało wszelkie trwające nadużycia. Rotacja kluczy OpenAI jest natychmiastowa — stary klucz przestaje działać w momencie jego usunięcia.

Napraw błąd przekazywania nagłówków

Główną przyczyną było to, że trasa FastAPI używała generycznego klienta HTTP, który przekazywał wszystkie nagłówki odpowiedzi z nadrzędnego wywołania OpenAI. Naprawa polegała na konstruowaniu jawnych nagłówków odpowiedzi, zamiast przekazywania tych z góry strumienia:

# Przed (podatne) — przekazywanie wszystkich nagłówków z góry strumienia
upstream_response = await client.post(
    "https://api.openai.com/v1/chat/completions",
    headers={"Authorization": f"Bearer {settings.OPENAI_API_KEY}", ...},
    json=payload
)
return Response(
    content=upstream_response.content,
    headers=dict(upstream_response.headers)  # ← to przekazuje nagłówek Authorization z powrotem
)

# Po (naprawione) — jawna konstrukcja odpowiedzi
upstream_response = await client.post(
    "https://api.openai.com/v1/chat/completions",
    headers={"Authorization": f"Bearer {settings.OPENAI_API_KEY}", ...},
    json=payload
)
completion_data = upstream_response.json()
return JSONResponse(content={"completion": completion_data["choices"][0]["message"]["content"]})
# Tylko dane, które jawnie chcemy zwrócić — brak przekazywania nagłówków z góry strumienia

Napraw IDOR

Punkt końcowy historii rozmów został zaktualizowany, aby wyodrębniać ID użytkownika ze zweryfikowanego JWT, a nie z parametru ścieżki:

@router.get("/api/history")
async def get_history(current_user: User = Depends(get_current_user)):
    # ID użytkownika pochodzi ze zweryfikowanego JWT — nie może zostać sfałszowane
    history = await db.get_history(user_id=current_user.id)
    return history

Wyłącz tryb debugowania

# W config.py
app = FastAPI(
    debug=settings.DEBUG,  # odczytuje ze zmiennej środowiskowej
    docs_url=None if not settings.DEBUG else "/docs",  # ukryj dokumentację w środowisku produkcyjnym
    redoc_url=None if not settings.DEBUG else "/redoc"
)

Po ustawieniu DEBUG=false w środowisku produkcyjnym, interaktywna dokumentacja i szczegółowe odpowiedzi błędów zniknęły natychmiast po kolejnym wdrożeniu.


Dodawanie limitów użycia OpenAI jako siatki bezpieczeństwa

Oprócz naprawy wycieku, założyciel dodał dwa środki obronne, aby ograniczyć zasięg szkód w przypadku przyszłego ujawnienia klucza:

Limity użycia: W panelu OpenAI w sekcji Rozliczenia → Limity użycia, ustaw miesięczny twardy limit i miękki próg powiadomień. Nawet jeśli klucz zostanie ponownie skompromitowany, zdolność atakującego do generowania kosztów jest ograniczona.

Dedykowane klucze dla każdej usługi: Utwórz oddzielny klucz API dla każdej aplikacji lub środowiska. Jeśli klucz zostanie skompromitowany, możesz obrócić tylko ten klucz bez zakłócania działania innych usług, a dzienniki użycia dla każdego klucza są wyraźnie oddzielone — co znacznie ułatwia wykrycie nieautoryzowanego dostępu.


Jak często to się zdarza?

Ujawnienie klucza API w odpowiedziach HTTP jest mniej powszechne niż ujawnienie w pakietach JavaScript, ale regularnie obserwujemy to w aplikacjach opakowujących AI. Wzorzec ten prawie zawsze ma tę samą przyczynę źródłową: programista tworzący warstwę proxy używa generycznego klienta HTTP, który przekazuje nagłówki odpowiedzi, i nie audytuje, co te nagłówki zawierają.

Błąd przekazywania nagłówków jest łatwy do popełnienia, ponieważ często upraszcza implementację. Po co konstruować nową odpowiedź, skoro można przekazać tę z góry? Odpowiedź, w tym przypadku, jest taka, że odpowiedź z góry zawiera dane uwierzytelniające, których nie chcesz udostępniać swoim użytkownikom.

Jeśli Twoja aplikacja pośredniczy w wywołaniach do OpenAI, Anthropic lub dowolnego innego zewnętrznego API, jawnie audytuj nagłówki swoich odpowiedzi. Użyj narzędzia takiego jak curl -v lub narzędzi deweloperskich przeglądarki, aby sprawdzić każdy nagłówek zwracany przez każdy punkt końcowy API. Nagłówki są łatwe do przeoczenia właśnie dlatego, że przez większość czasu są nieciekawe — co czyni je tak skutecznym miejscem ukrycia wycieku.


Kontekst aplikacji YC

Założyciel przygotowywał aplikację YC w momencie skanowania. Połączenie niewyjaśnionych skoków w rozliczeniach, ujawnionego klucza API i luki IDOR wpływającej na historię rozmów wszystkich użytkowników byłoby znaczącym problemem do wyjaśnienia inwestorom — lub, co gorsza, do odkrycia po uzyskaniu finansowania.

Problemy bezpieczeństwa na etapie przedpremierowym lub wczesnego rozwoju można naprawić w ciągu godzin. Te same problemy odkryte po incydencie bezpieczeństwa, powiadomieniu o naruszeniu danych lub wrogiej historii medialnej wymagają miesięcy na odzyskanie i mogą zakończyć działalność firmy, która nie zbudowała jeszcze wystarczającej reputacji, aby przetrwać cykl medialny.

Założyciel ponownie uruchomił Penetrify przed złożeniem aplikacji YC. Raport wrócił czysty.

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