Krytyczna luka w LangChain Core (CVE-2025-68664) – „LangGrinch” umożliwia wyciek sekretów i nadużycia deserializacji

securitybeztabu.pl 2 dni temu

Wprowadzenie do problemu / definicja luki

Pod koniec grudnia 2025 ujawniono krytyczną podatność w langchain-core (Python) – podstawowej bibliotece ekosystemu LangChain – która pozwala atakującemu „przemycić” spreparowaną strukturę danych do procesu serializacji/deserializacji i w efekcie wyciągać sekrety (np. zmienne środowiskowe) oraz inicjować niebezpieczne ścieżki wykonania w ramach obiektów frameworka. Luka otrzymała identyfikator CVE-2025-68664 i przydomek LangGrinch.

Równolegle opisano podobny problem w implementacji LangChain.js (CVE-2025-68665), dotyczący sposobu serializacji w JavaScript/TypeScript.

W skrócie

  • CVE-2025-68664 (Python / langchain-core): podatność typu serialization injection w dumps()/dumpd(), oceniona jako krytyczna (CVSS 9.3).
  • Mechanizm nadużycia opiera się o wewnętrzny znacznik "lc", który LangChain traktuje jako sygnał, iż dane reprezentują „prawdziwy” obiekt frameworka, a nie zwykły słownik.
  • Najczęstszy wektor: pola odpowiedzi LLM (np. additional_kwargs, response_metadata) sterowane pośrednio przez prompt injection, a następnie serializowane i deserializowane w przepływach strumieniowych.
  • Poprawki: aktualizacja do langchain-core 0.3.81 lub 1.2.5 (w zależności od gałęzi).
  • Dodatkowo: analogiczna luka w LangChain.js (CVE-2025-68665, CVSS 8.6) – poprawione m.in. w @langchain/core 0.3.80 / 1.1.8 i langchain 0.3.37 / 1.2.3.

Kontekst / historia / powiązania

LangChain i langchain-core stały się fundamentem wielu wdrożeń „agentowych” (orchestracja narzędzi, pamięć, streaming, logowanie zdarzeń). Problem LangGrinch jest groźny nie dlatego, iż dotyczy rzadkiego modułu, ale dlatego, iż dotyka mechanizmu wymiany/utrwalania danych (serializacja), który bywa używany „w tle” w popularnych API i integracjach.

W praktyce to kolejny przykład klasycznej kategorii błędów (deserializacja danych niezaufanych), ale w nowym kontekście: LLM output jako dane wejściowe. Wiele zespołów przez cały czas traktuje odpowiedź modelu jak „bezpieczny tekst”, podczas gdy jest to treść, którą przeciwnik może kształtować promptami, danymi w RAG, a czasem choćby treścią zewnętrznych źródeł.

Analiza techniczna / szczegóły luki

Na czym polega „serialization injection” w LangChain?

W LangChain istnieje wewnętrzny format, który opisuje obiekty frameworka jako struktury danych. Klucz lc jest częścią tego mechanizmu: sygnalizuje, iż dana struktura ma być traktowana jak serializowany obiekt LangChain.

W CVE-2025-68664 problem polegał na tym, iż funkcje dumps() i dumpd() nie „uciekały” (nie neutralizowały) słowników zawierających lc w dowolnych, swobodnych danych. Gdy taki wynik został później przepuszczony przez load()/loads(), wstrzyknięta struktura mogła zostać zinterpretowana jako legalny obiekt LangChain, a nie zwykłe dane użytkownika.

Co realnie umożliwia atak?

Z advisory wynika kilka praktycznych wektorów:

  • Ekstrakcja sekretów z env – historycznie ryzykowny wariant, bo wcześniejsze domyślne ustawienia pozwalały automatycznie pobierać sekrety ze zmiennych środowiskowych podczas deserializacji (secrets_from_env było domyślnie włączone).
  • Instancjonowanie klas w „zaufanych” przestrzeniach nazw (langchain_core, langchain, langchain_community) – choćby jeżeli to nie jest „dowolna klasa z systemu”, przez cały czas mogą istnieć konstruktory z efektami ubocznymi (połączenia sieciowe, operacje na plikach, itp.).
  • Powiązanie z prompt injection – ponieważ pola typu additional_kwargs/response_metadata mogą zostać ukształtowane przez atakującego (np. przez wymuszenie specyficznego JSON-a w odpowiedzi), a potem trafić do serializacji w strumieniowaniu.

Cyata opisuje też scenariusze, w których instancjonowane obiekty mogą powodować wyjściowe żądania sieciowe albo prowadzić do dalszych eskalacji, jeżeli aplikacja po deserializacji wykona kolejne kroki „ufając” obiektom.

Co zmieniły poprawki (i dlaczego mogą „boleć”)?

W przypadku Pythona łatka nie tylko naprawia błąd escapowania, ale też wprowadza utwardzenie bezpieczeństwa:

  • domyślna allowlista (allowed_objects="core"),
  • secrets_from_env domyślnie False,
  • blokada szablonów Jinja2 przez init_validator (zmiana potencjalnie „breaking” dla części użytkowników).

Praktyczne konsekwencje / ryzyko

Największe ryzyka dla zespołów budujących aplikacje i agentów LLM:

  • Wyciek kluczy API (LLM provider, narzędzia, bazy wektorowe, systemy zewnętrzne), jeżeli środowisko wykonawcze ma sekrety w zmiennych środowiskowych, a ścieżka deserializacji została osiągnięta.
  • Nieoczekiwane zachowanie agenta – atakujący może „dosztukować” struktury, które zmienią sposób działania łańcucha, logowania, pamięci, narzędzi lub dalszego generowania odpowiedzi (w praktyce: prompt injection + nadużycie serializacji).
  • Efekty uboczne w zaufanych klasach – choćby bez pełnego RCE, sam fakt inicjowania ruchu wychodzącego, odczytów plików czy nietypowych operacji może być bolesny (SSRF, exfil, kosztowe DoS).

Rekomendacje operacyjne / co zrobić teraz

  1. Zaktualizuj zależności natychmiast
    • Python: przejdź na langchain-core 0.3.81 albo 1.2.5 (zależnie od używanej linii).
    • JS: @langchain/core 0.3.80 / 1.1.8 oraz langchain 0.3.37 / 1.2.3.
  2. Załóż, iż output LLM to dane niezaufane
    • Traktuj additional_kwargs, response_metadata, metadata jak payload z internetu.
    • Jeśli logujesz/serializujesz odpowiedzi modelu – wprowadź walidację i/lub filtrację (np. blokada klucza lc w danych swobodnych).
  3. Usuń automatyczne ładowanie sekretów z env przy deserializacji
    • Po łatkach domyślnie jest bezpieczniej, ale warto audytować kod: czy gdziekolwiek jawnie włączasz secrets_from_env / secretsFromEnv.
  4. Ogranicz deserializację do minimum
    • Jeśli musisz używać load()/loads(): trzymaj się allowlisty i nie deserializuj niczego, co może pochodzić od użytkownika/LLM/RAG/cache/hub bez walidacji.
  5. Sprawdź „wrażliwe” ścieżki z advisory
    • Python: szczególnie przypadki użycia streamingu i narzędzi, które wewnętrznie serializują zdarzenia (np. astream_events w wersji v1, Runnable.astream_log() i inne wskazane w advisory).
  6. Dodaj kontrolę w pipeline
    • SCA/Dependabot + blokada wdrożeń z podatnymi wersjami.
    • SBOM i alertowanie przy wykryciu langchain-core w podatnym zakresie.

Różnice / porównania z innymi przypadkami

  • Python (CVE-2025-68664): podatność w dumps()/dumpd() + twarde zmiany bezpieczeństwa w load()/loads() (allowlista, wyłączenie sekretów z env, blokada Jinja2).
  • JavaScript (CVE-2025-68665): podatność w Serializable.toJSON() / JSON.stringify() + deserializacja przez load(); hardening obejmuje m.in. jawne wyłączenie secretsFromEnv oraz limit głębokości (maxDepth) przeciw DoS.

W obu światach wspólny mianownik jest ten sam: framework myli dane użytkownika z danymi strukturalnymi (bo klucz lc ma specjalne znaczenie), a to tworzy „most” między prompt injection a klasycznymi kategoriami błędów bezpieczeństwa.

Podsumowanie / najważniejsze wnioski

LangGrinch (CVE-2025-68664) to sygnał ostrzegawczy dla zespołów budujących agentów i aplikacje LLM: jeżeli jakikolwiek fragment odpowiedzi modelu trafia do serializacji/deserializacji, to w praktyce traktujesz LLM jak niezaufanego nadawcę danych. Najważniejsze działania to szybka aktualizacja do wersji naprawionych, ograniczenie deserializacji, wyłączenie automatycznego ładowania sekretów z env i wprowadzenie allowlist / walidacji struktur.

Źródła / bibliografia

  1. The Hacker News – opis CVE-2025-68664 i CVE-2025-68665 oraz podatne/naprawione wersje (The Hacker News)
  2. GitHub Advisory (langchain-core, CVE-2025-68664 / GHSA-c67j-w6g6-q2cm) – wektory ataku, wpływ, zmiany hardening (GitHub)
  3. GitHub Advisory (LangChain.js, CVE-2025-68665 / GHSA-r399-636x-v7f6) – zakres npm, hardening load() (GitHub)
  4. Cyata Research – kontekst „LangGrinch”, scenariusze ryzyka w agentach (Cyata)
Idź do oryginalnego materiału