
저자: 야르덴 포라트
Cyata 연구: LangChain의 LangGrinch 취약점
게시됨: https://cyata.ai/blog/langgrinch-langchain-core-cve-2025-68664/

어제 LangChain은 제가 langchain-core에서 발견한 취약점에 대한 긴급 보안 권고를 발표했습니다: CVE-2025-68664 / GHSA-c67j-w6g6-q2cm.
올해 초 제 연구는 "Vault Fault" 작업에서 비밀 관리자(secret manager)를 해킹하는 데 집중되었습니다. 비밀 관리자는 가장 민감한 자격 증명 주변의 보안 경계로 특별히 설계된 시스템입니다. 한 가지 결론이 반복해서 나타났습니다: 플랫폼이 공격자가 만든 데이터를 우연히 신뢰할 수 있는 구조로 처리할 때, 그 경계는 빠르게 무너집니다. 이번에 "무너지는" 시스템은 여러분의 비밀 관리자가 아닙니다. 그것을 사용할 수 있는 에이전트 프레임워크입니다.
이 취약점이 특별한 주목을 받을 만한 이유:
코어에 있다. 이는 특정 도구의 버그도, 통합의 극단적인 사례도, "커뮤니티 패키지가 이상한 일을 한 것"도 아닙니다. 취약한 API(dumps() / dumpd())는 바로 langchain-core 자체에 있습니다.
피해 범위가 엄청나다. 다운로드 수 기준으로 langchain은 오늘날 전 세계에서 가장 널리 배포된 AI 프레임워크 구성 요소 중 하나입니다. 2025년 12월 말 기준, 공개 패키지 텔레메트리는 수억 건의 설치를 보여주며, pepy.tech는 약 8억 4,700만 회의 총 다운로드를, pypistats는 지난 한 달간 약 9,800만 회의 다운로드를 보고합니다.
단 하나의 프롬프트로 많은 메커니즘을 실행할 수 있다. 여기서 가장 일반적인 실제 경로는 "공격자가 직렬화된 블롭을 보내고 당신이 load()를 호출하는" 것이 아닙니다. 더 미묘합니다: LLM 출력은 additional_kwargs나 response_metadata 같은 필드에 영향을 줄 수 있으며, 이러한 필드는 스트리밍 로그/이벤트 같은 일반적인 프레임워크 기능을 통해 직렬화되고 나중에 역직렬화될 수 있습니다. 간단히 말해, 익스플로잇은 단 하나의 텍스트 프롬프트로 실행될 수 있으며, 이는 예상외로 복잡한 내부 파이프라인으로 캐스케이드됩니다.
계속 읽기 전에, 패치는 이미 1.2.5 및 0.3.81 버전에서 출시되었습니다. 프로덕션에서 LangChain을 사용하고 있다면, 이는 보이는 것보다 더 복잡합니다; 가능한 한 빨리 업데이트하시기 바랍니다.
LangChain은 'lc' 마커를 포함하는 사전이 LangChain 객체를 나타내는 특수한 내부 직렬화 형식을 사용합니다. 취약점은 dumps()와 dumpd()가 우연히 예약된 'lc' 키를 포함하는 사용자 제어 사전을 올바르게 이스케이프하지 않았다는 점이었습니다.
따라서 공격자가 LangChain 오케스트레이션 루프가 'lc' 키를 포함한 콘텐츠를 직렬화한 후 역직렬화하도록 만들 수 있다면, 안전하지 않은 임의 객체를 인스턴스화하여 공격자에게 유리한 다양한 경로를 실행할 수 있습니다.
이 권고는 표준 이벤트 스트리밍, 로깅, 메시지 히스토리/메모리 또는 캐시와 같은 실제 사용 사례에서 매우 흔한 12가지 취약한 흐름을 나열합니다:

가장 파괴적인 영향은 다음과 같습니다:
환경 변수에서 비밀 추출. 권고에 따르면 이는 secrets_from_env=True로 역직렬화할 때 발생합니다. 주목할 점은 어제까지 이것이 기본값이었다는 것입니다. 🙂
사전 승인된 네임스페이스(langchain_core, langchain_openai, langchain_aws, langchain_anthropic… 포함)에서 객체 인스턴스화로, 생성자에서 부작용(네트워크 호출, 파일 작업 등)을 잠재적으로 유발합니다.
특정 조건에서 LangChain 객체의 인스턴스화는 임의 코드 실행으로 이어질 수 있습니다.
이는 CWE-502: 신뢰할 수 없는 데이터의 역직렬화로 분류되며, CVSS CNA 점수는 **9.3(치명적)**입니다.
크리스마스 이브에 저는 가장 축제답지 않은 일을 하고 있었습니다: 직렬화 코드를 살펴보며 "잠깐만… 왜 이게 신뢰할 수 있는 것으로 간주되지?"라고 묻고 있었습니다.
보안 연구는 종종 외부에서 보기에는 극적으로 보입니다. 현실은 대개 세심한 코드 읽기, 작은 가설들, 그리고 "이상하네"라는 순간들의 느린 축적입니다.
이것은 Cyata에서 많은 일이 시작되는 방식으로 시작되었습니다: AI 스택의 실제 위험을 평가할 때 우리가 끊임없이 던지는 단순한 질문에서:
AI 애플리케이션에서 신뢰 경계는 어디에 있으며, 개발자들은 그 경계가 어디에 있는지 정말로 알고 있을까?
LangChain은 강력한 프레임워크이며, 대부분의 현대 프레임워크처럼 복잡한 구조화된 데이터를 이동해야 합니다: 메시지, 도구 호출, 스트리밍 이벤트, 트레이스, 캐시 및 "실행 가능한(runnable)" 것들.
이전 연구들을 살펴보면, LangChain 도구와 통합에 대한 광범위한 연구는 이미 있었지만 핵심 라이브러리의 발견 사항은 매우 적었습니다.
저는 역방향 작업으로 연구를 시작했습니다. 흥미로운 지점(싱크)을 찾은 다음, 공격자가 어떻게 그 지점에 도달할 수 있는지 알아내는 방식입니다. 역직렬화는 명백한 목표였습니다.
의미 있는 것을 찾는 데 꽤 많은 시간이 걸렸습니다. 하지만 얼마 지나지 않아 공격자가 제어하는 역직렬화 프리미티브를 가정할 때, 환경 변수 유출에 사용될 수 있는 블라인드 SSRF를 실행할 수 있다는 것을 발견했습니다(자세한 내용은 곧 설명). 결과가 제 주요 목표인 RCE가 아닌 비밀 유출에 국한되었기 때문에, 저는 역직렬화를 계속 감사(audit)하며 시간을 들였습니다.
버그는 나쁜 코드 조각이 아니라, 코드의 부재였습니다. dumps()는 단지 'lc' 키를 포함하는 사용자 제어 사전을 이스케이프하지 않았습니다. 직렬화 경로에서의 누락된 이스케이프였지, 역직렬화가 아니었습니다.

무언가 잘못된 것을 알아차리는 것이 무언가 없는 것을 알아차리는 것보다 훨씬 쉽습니다. 특히 dumps()가 아닌 load()를 감사할 때 말이죠. 가장 철저하게 검토된 AI 프레임워크 중 하나에서. 2년 반 동안.
그 지점부터 연구는 구조화된 작업이 되었습니다:
신뢰할 수 없는 콘텐츠(주로 임의의 사전)가 직렬화로 들어가는 위치를 식별 (LLM 출력, 프롬프트 인젝션, 사용자 입력, 외부 도구, 추출된 문서).
이러한 직렬화된 데이터가 언제 역직렬화되는지 식별.
공격자가 임의 객체 인스턴스화를 통해 무엇을 달성할 수 있는지 식별.
그 시점에서 핵심 발견은 책임 있는 공개를 위해 충분히 명확하고 실행 가능했습니다: dumps() / dumpd()에서 'lc' 키를 가진 사전 주변에 이스케이프 갭이 있었습니다.
이후 권고는 우리가 실제로 자주 보는 것을 포착했습니다: additional_kwargs 및 response_metadata 같은 필드는 LLM 출력과 프롬프트 인젝션에 의해 영향받을 수 있으며, 이러한 필드는 많은 흐름에서 직렬화-역직렬화될 수 있습니다.
LangChain 팀의 공으로: 대응과 후속 조치는 단호했습니다. 단순히 버그를 패치하는 것뿐만 아니라, 우리가 지금 살고 있는 세상에 너무 관대했던 기본값을 강화했습니다.
LangChain 프로젝트는 이 발견에 대해 $4,000 USD의 포상금을 지급하기로 결정했습니다. LangChain이 포상금 프로그램을 운영한 플랫폼인 huntr에 따르면, 이는 프로젝트에서 지금까지 지급된 최대 금액이 될 것이며, 지금까지의 포상금은 최대 $125였습니다.
LangChain은 구조화된 사전 형식을 사용하여 특정 객체를 직렬화합니다. 'lc' 키는 "이것은 LangChain 직렬화 구조다"라는 것을 나타내기 위해 내부적으로 사용되며, 단순한 임의의 사용자 데이터가 아닙니다.
이것은 일반적인 패턴이지만 보안 불변식을 만듭니다: 'lc'를 포함할 수 있는 모든 사용자 데이터는 주의해서 처리되어야 합니다. 그렇지 않으면 공격자가 "내부 객체처럼 보이는" 사전을 만들어 역직렬화기를 속여 값을 얻게 할 수 있습니다.
패치는 업데이트된 문서에서 의도를 명확히 합니다: 직렬화 중에 'lc' 키를 포함하는 단순 사전은 래핑을 통해 이스케이프됩니다.
이로 인해 역직렬화 중에 이러한 사전이 실제 직렬화된 LangChain 객체와 혼동되는 것을 방지합니다.
LangChain의 load()/loads() 함수는 임의의 클래스를 인스턴스화하지 않습니다. 어떤 클래스가 역직렬화될 수 있는지 제어하는 화이트리스트를 확인합니다. 기본적으로 이 화이트리스트에는 langchain_core, langchain_openai, langchain_aws 및 기타 에코시스템 패키지의 클래스가 포함됩니다.
핵심은 이것입니다: 화이트리스트의 대부분의 클래스는 무해한 생성자를 가지고 있습니다. 악용 가능한 경로를 찾으려면 인스턴스화할 때 의미 있는 작업을 수행하는 클래스를 찾기 위해 에코시스템을 파고들어야 했습니다. 제가 찾은 것들은 아래에 자세히 설명되어 있지만, 발견을 기다리는 다른 것들도 있을 수 있습니다.
LangChain의 loads() 함수는 역직렬화 중에 환경 변수의 값을 해석하는 secret 유형을 지원합니다. 패치 이전에는 이 secrets_from_env 기능이 기본적으로 활성화되어 있었습니다:
if (
value.get("lc") == 1
and value.get("type") == "secret"
and value.get("id") is not None
):
[key] = value["id"]
if key in self.secrets_map:
return self.secrets_map[key]
if self.secrets_from_env and key in os.environ and os.environ[key]:
return os.environ[key] # <-- Возвращение переменной окружения
return None
역직렬화된 객체가 공격자에게 반환된다면, 예를 들어 LLM 컨텍스트 내의 메시지 히스토리 같은 경우, 환경 변수가 유출될 수 있습니다.
하지만 더 흥미로운 경로는 간접적인 프롬프트 인젝션입니다. LLM 응답을 전혀 볼 수 없는 공격자도 올바른 클래스를 인스턴스화하여 비밀을 유출할 수 있습니다. _langchain_aws_의 _ChatBedrockConverse_는 _loads_의 기본 화이트리스트에 있으면서도, 생성 시 GET 요청을 수행합니다. GET 엔드포인트는 공격자가 제어하며, 특정 HTTP 헤더는 secrets_from_env 기능을 통해 환경 변수로 채워질 수 있습니다.

이 검증기는 _ChatBedrockConverse_가 인스턴스화될 때 실행됩니다. 공격자는 _endpoint_url_을 제어하여 외부 요청을 발생시킵니다. _secrets_from_env_와 결합하면, aws_access_key_id 헤더는 AWS 키뿐만 아니라 어떤 환경 변수로도 채워질 수 있습니다.
우리는 보안 팀에 시간을 주기 위해 여기에 완성된 익스플로잇을 의도적으로 공개하지 않습니다. 몇 달 후 Huntr 사이트에서 자동으로 공개할 것입니다.
_loads()_의 기본 화이트리스트에 있는 클래스 중에는 _PromptTemplate_가 있습니다. 이 클래스는 템플릿에서 프롬프트를 생성하며, 사용 가능한 템플릿 형식 중 하나는 Jinja2입니다.
템플릿이 Jinja2로 렌더링될 때 임의의 Python 코드가 실행될 수 있습니다. 우리는 loads() 함수 단독으로 직접 실행하는 방법을 찾지 못했지만, 역직렬화된 객체에 대한 후속 호출이 렌더링을 트리거하면 코드 실행이 이어집니다.
우리는 _loads()_에서 직접 코드를 실행할 수 있는 경로가 있을 수 있다고 의심하지만 아직 확인하지 못했습니다. 테스트할 만한 확실한 아이디어나 단서가 있다면, 듣고 싶습니다 – 이것이 바로 보안 커뮤니티가 가설을 증거로 바꾸는 데 도움을 주는 지점입니다. 🤝
또한 주목할 점: 이전 버전에서는 Chain 클래스도 화이트리스트에 있었습니다. 이 클래스는 템플릿 렌더링으로 이어지는 흐름을 허용할 수 있는 특수 기능을 가지고 있었습니다.
취약한 버전의 langchain-core를 사용한다면 애플리케이션이 잠재적으로 노출될 수 있습니다. 가장 일반적인 취약한 패턴 중 일부는 다음과 같습니다(총 12가지 흐름이 식별됨):
그럼에도 불구하고, 시스템 동작은 충분히 복잡해서 빠른 코드 검토로 모든 도달 가능한 경로를 찾아낼 수 있다고 가정하는 것은 위험합니다. 가장 안전한 방법은 패치된 버전으로 업데이트하고, 업데이트하기 전까지 안전하다고 가정하지 않는 것입니다.
또한 권고는 제가 가장 중요한 실제 현실의 요점이라고 생각하는 것을 지적합니다:
가장 일반적인 공격 벡터는 additional_kwargs 또는 response_metadata 같은 LLM 응답 필드를 통해 발생합니다. 이 필드는 프롬프트 인젝션을 통해 제어된 후 스트리밍 작업에서 직렬화/역직렬화될 수 있습니다.
이것은 바로 조직이 허를 찔리는 "AI와 클래식 보안의 만남"의 교차점입니다. LLM 출력은 신뢰할 수 없는 입력입니다. 프레임워크가 이 출력의 일부를 나중에 구조화된 객체로 처리한다면, 공격자가 이를 조작하려 시도할 것이라고 가정해야 합니다.
langchain-core를 패치된 버전으로 업데이트하세요. langchain, langchain-community 또는 다른 에코시스템 패키지를 사용한다면, 프로덕션 환경에 실제로 설치된 langchain-core 버전을 확인하세요.
additional_kwargs, response_metadata, 도구 출력, 추출된 문서 및 메시지 히스토리를 반대가 증명되지 않는 한 신뢰할 수 없는 것으로 취급하세요. 이는 로그/이벤트를 스트리밍하고 나중에 로더로 재수화(rehydrate)하는 경우 특히 중요합니다.
업데이트 후에도 원칙을 지키세요: 직렬화된 입력을 신뢰하지 않는다면 환경 변수에서 비밀 해석을 활성화하지 마세요. 프로젝트가 이유가 있어 기본값을 변경했습니다.
제 보고를 기반으로, LangChainJS(GHSA-r399-636x-v7f6 / CVE-2025-68665)에도 유사한 메커니즘을 가진 밀접하게 관련된 권고가 있습니다: 직렬화 중 'lc' 마커 혼동으로 인해 특정 구성에서 비밀 유출과 안전하지 않은 인스턴스화가 가능합니다.
조직이 Python과 JavaScript LangChain 스택을 모두 운영한다면, 이는 패턴이 에코시스템 간에 확산된다는 사실을 상기시키는 것으로 간주하세요: 마커 직렬화, 신뢰할 수 없는 모델 출력 및 후속 역직렬화는 반복적인 위험 형태입니다.
우리는 AI 에이전트 프레임워크가 프로덕션 시스템 내에서 핵심 인프라가 되는 단계에 접어들고 있습니다. 직렬화 형식, 오케스트레이션 파이프라인, 도구 실행, 캐시 및 트레이스는 더 이상 "배관"이 아닙니다 – 그것들은 보안 경계의 일부입니다.
이 취약점은 "단순한 라이브러리 버그"가 아닙니다. 이것은 더 큰 패턴의 사례 연구입니다:
애플리케이션은 안전하게 생성되었다고 믿는 데이터를 역직렬화할 수 있습니다.
하지만 이 직렬화된 출력은 신뢰할 수 없는 소스(프롬프트 인젝션으로 조작된 LLM 출력 포함)의 영향을 받는 필드를 포함할 수 있습니다.
내부 마커로 사용되는 단일 예약 키가 비밀과 실행에 인접한 동작으로의 전환점이 될 수 있습니다.
Cyata에서 우리의 일은 AI 시스템 주변에 가시성, 위험 평가, 통제 및 거버넌스를 구축하도록 조직을 돕는 것입니다 – 에이전트가 어디에서 실행되는지, 어떤 버전이 배포되었는지, 어떤 데이터가 이를 통해 흐르는지 빠르게 답할 수 없다면, 이와 같은 권고가 발표될 때 사실상 맹목적으로 비행하는 것이기 때문입니다.
이 글을 읽는 보안 리더라면, 여기 불편한 진실이 있습니다:
대부분의 조직은 현재 빠르고 확실하게 답할 수 없습니다:
우리는 어디에서 에이전트를 사용하는가?
프로덕션에 어떤 버전이 배포되어 있는가?
어떤 서비스가 민감한 비밀에 접근할 수 있는가?
LLM 출력이 이러한 경계를 교차하는 곳은 어디인가?
이것은 "개발자의 문제"가 아닙니다. 이것은 가시성과 거버넌스의 문제입니다.
그리고 바로 여기서 Cyata가 등장합니다.
Cyata에서 우리는 실용적인 결과에 집중합니다: 빌더의 속도를 늦추지 않으면서 AI와 에이전트의 위험을 줄이는 것. 이와 같은 취약점은 "그냥 패치"인 경우가 드뭅니다. 그것들은 팀이 에이전트가 어디서 실행되는지 발견하고, 실제 신뢰 경계를 이해하며, 빠르게 움직이는 프레임워크에서 더 안전한 기본값을 보장하는 방법의 공백을 드러냅니다.
무엇이 어디에서 실행되고 어떻게 연결되어 있는지 파악하세요.
CVE의 첫 번째 질문에 빠르게 답하세요: 우리는 영향받는가, 어떤 흐름에서인가?
에이전트 런타임과 환경 간 통합(IDEs, CI, 서비스, 작업(job), 호스팅 에이전트)을 발견하세요.
사용 중인 프레임워크, 패키지 및 버전을 추적하세요.
단순히 "라이브러리가 존재한다"가 아니라 실제 영향 범위를 기반으로 중요한 것을 우선순위화하세요.
더 빠른 트리아지(triage)를 지원하세요: 무엇이 인터넷에 노출되어 있는지, 무엇이 비밀과 관련되어 있는지, 무엇이 높은 권한으로 실행되는지.
최고 위험 경로를 식별하세요: 권한 있는 컨텍스트(비밀을 가진 서비스, 광범위한 도구 권한, 프로덕션 네트워크 접근)로 흘러 들어가는 신뢰할 수 없는 콘텐츠.
'구조화된 필드'가 신뢰 경계를 교차할 수 있는 위치를 강조하세요(메타데이터, 도구 출력, 스트리밍 이벤트, 캐시된 아티팩트).
모든 의존성이 모든 곳에서 패치되기 전에도 노출을 줄이세요.
더 안전한 운영 기본값을 장려하세요: 최소 권한, 격리 경계 및 팀 간에 확장되는 정책 검사.
위험한 패턴 주변에 게이트웨이를 제공하세요(예: 신뢰할 수 없는 데이터의 역직렬화, 관대한 객체 부활, 안전하지 않은 스트리밍-캐시-재수화 흐름).
신뢰할 수 없는 컨텍스트에서 민감한 기능을 게이트하거나 제한하세요(예: 환경의 비밀 접근, 고권한 도구 실행 또는 권한 있는 워커에서 위험한 코드 경로 실행).
'안전한 에이전트 사용'을 반복 가능하고, 감사 가능하며, 드리프트하기 어렵게 만드세요.
승인된 프레임워크, 버전 및 구성에 대한 정책을 정의하세요.
소유자와 근거와 함께 예외를 추적하고 기한을 제한하세요.
보안 검토와 컴플라이언스를 지원하는 감사 추적과 함께, 시간에 따른 드리프트와 위험한 기능 사용을 모니터링하세요.
크리스마스 보안 권고가 떨어질 때, 목표는 영웅주의가 아닙니다 – 그것은 실제 인벤토리와 확보된 게이트웨이로 뒷받침되는 차분하고 통제된 대응입니다.
Huntr를 통해 보고서 제출 – 2025년 12월 4일
LangChain 메인테이너에 의해 인정됨 – 2025년 12월 5일
권고 및 CVE 게시 – 2025년 12월 24일