AI 애플리케이션을 만들 때 우리는 보통 외부 입력은 검증하지만, AI가 생성한 출력까지 의심하진 않습니다. “내 시스템이 만든 데이터니까 안전하다”고 생각하죠. 하지만 크리스마스 이브에 공개된 LangChain의 치명적 취약점은 이 가정이 얼마나 위험한지 보여줍니다.

보안 회사 Cyata의 연구원 Yarden Porat이 발견한 이 취약점(CVE-2025-68664)은 LangChain의 핵심 라이브러리에서 발견되었습니다. 특정 통합 기능이나 부가 패키지가 아닌, 모든 LangChain 애플리케이션이 의존하는 langchain-core의 직렬화 함수에서 발견된 문제입니다. 단 하나의 텍스트 프롬프트만으로 환경변수에 저장된 API 키를 탈취하거나, 특정 조건에서는 임의 코드 실행까지 가능합니다.
출처: All I Want for Christmas Is Your Secrets: LangGrinch hits LangChain Core (CVE-2025-68664) – Cyata
무슨 일이 일어났나
LangChain은 객체를 저장하거나 전송할 때 특별한 형식으로 직렬화합니다. LangChain 내부 객체를 직렬화하면 'lc'라는 키를 붙여서 “이건 LangChain 객체야”라고 표시하죠. 문제는 dumps() 함수가 일반 사용자 데이터를 직렬화할 때 발생합니다.
사용자 데이터 안에 우연히(또는 악의적으로) 'lc' 키가 포함된 딕셔너리가 있어도, dumps()는 “이건 그냥 사용자 데이터니까 ‘lc’ 키를 이스케이프 처리해야지”라고 하지 않았습니다. 그냥 그대로 직렬화했죠. 나중에 load()로 역직렬화할 때는 “진짜 LangChain 내부 객체”와 “사용자가 만든 가짜 객체”를 구분하지 못하고 둘 다 LangChain 객체로 처리합니다.
공격자는 이 허점을 이용해 악의적인 딕셔너리를 주입할 수 있습니다. 예를 들어 LLM 응답의 메타데이터 필드에 {"lc": 1, "type": "secret", "id": ["OPENAI_API_KEY"]}라는 구조를 포함시키면, 이게 직렬화된 후 역직렬화될 때 시스템이 정상적인 LangChain 비밀 객체로 착각하고 환경변수에서 OPENAI_API_KEY 값을 가져와 반환합니다.
더 놀라운 건 공격 경로입니다. LangChain 공식 보안 권고문은 12가지 취약한 흐름을 나열하는데, 가장 흔한 케이스는 다음과 같습니다:
- 스트리밍 이벤트 (
astream_eventsv1) - 메시지 히스토리 (
RunnableWithMessageHistory) - 로깅과 캐시
- LangChain Hub에서 매니페스트 로드
가장 위험한 지점은 LLM 응답 필드입니다. LLM이 반환하는 메타데이터 필드인 additional_kwargs나 response_metadata는 프롬프트 인젝션을 통해 공격자가 조작할 수 있고, 이 데이터가 스트리밍 작업 중 직렬화-역직렬화 과정을 거치면서 취약점이 트리거됩니다.
왜 위험한가
이 취약점의 파급력은 세 가지 요인에서 나옵니다.
규모: LangChain은 월간 약 9,800만 건의 다운로드를 기록하는, 세계에서 가장 널리 쓰이는 AI 프레임워크 중 하나입니다. 핵심 라이브러리의 문제라는 건 수많은 프로덕션 시스템이 동시에 노출되었다는 뜻입니다.
진입 장벽: 복잡한 공격 체인이 필요 없습니다. 연구원은 AWS Bedrock용 채팅 클래스(ChatBedrockConverse)를 역직렬화 과정에서 인스턴스화하면 생성자가 자동으로 HTTP GET 요청을 보낸다는 점을 발견했습니다. 엔드포인트 URL과 헤더를 공격자가 제어할 수 있고, secrets_from_env 기능(패치 전 기본값 True)과 결합하면 환경변수를 HTTP 헤더에 담아 외부로 전송할 수 있습니다. 공격자는 LLM 응답조차 볼 필요 없이 프롬프트 인젝션만으로 비밀키를 훔칠 수 있습니다.
신뢰 경계의 혼란: 전통적인 보안 모델에서 “내 시스템이 생성한 데이터”는 신뢰할 수 있었습니다. 하지만 AI 시대에는 LLM 출력이라는 외부 요소가 시스템 내부로 들어옵니다. LangChain을 쓰는 개발자 대부분은 자신의 직렬화 출력을 신뢰했을 겁니다. 그런데 그 출력에 사용자가 조작한 LLM 응답 필드가 포함되어 있다면? 신뢰 경계가 무너지는 순간입니다.
2년 반 동안 발견되지 않은 이유
연구원 Yarden Porat은 크리스마스 전날 밤, 가장 비축제적인 일을 하고 있었습니다. 직렬화 코드를 들여다보며 “왜 이게 신뢰받는 거지?”라고 묻고 있었죠.
흥미로운 건 이 버그가 “잘못된 코드” 때문이 아니었다는 점입니다. 코드가 없어서 생긴 문제였습니다. dumps() 함수는 단순히 'lc' 키를 포함한 사용자 제어 딕셔너리를 이스케이프하지 않았습니다. 뭔가 잘못됐다는 걸 눈치채는 것보다 뭔가 빠졌다는 걸 알아채는 게 훨씬 어렵습니다. 특히 역직렬화 함수(load())를 감사할 때는 더더욱 그렇죠.
연구원은 처음엔 블라인드 SSRF를 통한 환경변수 유출 경로를 찾았지만, RCE(원격 코드 실행)를 목표로 더 파고들었습니다. 그 과정에서 진짜 문제를 발견했습니다. 직렬화 단계에서의 이스케이프 누락이었습니다.
어떻게 대응할 것인가
LangChain 팀은 빠르게 대응했습니다. 버전 0.3.81과 1.2.5에서 패치가 릴리스되었고, 단순히 버그만 고친 게 아니라 보안 기본값을 대폭 강화했습니다.
즉시 조치사항:
langchain-core를 패치 버전으로 업그레이드하세요.- 단순히
langchain패키지 버전만 확인하지 말고, 실제 프로덕션 환경에 설치된langchain-core버전을 검증하세요.
방어 원칙:
- LLM 출력을 신뢰하지 않는 입력으로 취급하세요.
additional_kwargs,response_metadata, 도구 출력, 검색된 문서, 메시지 히스토리 모두 신뢰할 수 없다고 가정해야 합니다. - 역직렬화 기능을 재검토하세요. 패치 이후에도 환경변수에서 비밀을 로드하는 기능(
secrets_from_env)은 입력을 신뢰할 수 있을 때만 활성화하세요. 프로젝트가 기본값을 바꾼 데는 이유가 있습니다. - 허용 목록을 명시적으로 관리하세요. 새로운
allowed_objects파라미터로 역직렬화할 수 있는 클래스를 명시적으로 제한하세요.
더 큰 의미: AI 거버넌스의 시작
이 취약점은 단순히 “라이브러리 버그” 그 이상입니다. AI 시대의 새로운 보안 패턴을 보여주는 사례죠.
애플리케이션은 자신이 안전하게 생성했다고 믿는 데이터를 역직렬화합니다. 하지만 그 직렬화된 출력에는 신뢰할 수 없는 소스(프롬프트 인젝션으로 조작된 LLM 출력 포함)의 영향을 받은 필드가 포함될 수 있습니다. 내부 마커로 쓰이는 단 하나의 예약 키('lc')가 비밀 탈취와 실행 경로로 향하는 피벗 포인트가 됩니다.
연구원이 속한 Cyata는 이런 질문들을 던집니다:
- 우리는 에이전트를 어디서 실행하고 있는가?
- 프로덕션에 어떤 버전이 배포되어 있는가?
- 어떤 서비스가 민감한 비밀에 접근하는가?
- LLM 출력이 그 경계를 어디서 넘나드는가?
대부분의 조직은 이 질문에 빠르고 확실하게 답할 수 없습니다. 이건 개발자 문제가 아니라 가시성과 거버넌스 문제입니다.
AI 에이전트 프레임워크가 프로덕션 시스템의 핵심 인프라가 되는 시대입니다. 직렬화 형식, 오케스트레이션 파이프라인, 도구 실행, 캐시, 추적 시스템은 더 이상 “배관”이 아닙니다. 보안 경계의 일부입니다. 크리스마스 주간에 이런 권고문이 떨어졌을 때 필요한 건 영웅적 대응이 아니라, 실제 인벤토리와 강제된 가드레일을 바탕으로 한 침착하고 통제된 대응입니다.
이번 취약점은 한 가지를 명확히 보여줍니다. AI 시대에는 “누가 만든 데이터인가”가 아니라 “그 데이터가 어떤 영향을 받았는가”를 물어야 합니다.
참고자료:
- LangChain serialization injection vulnerability (CVE-2025-68664) – GitHub Security Advisory
- LangChain Core 0.3.81 Release – LangChain GitHub
- LangChain Core 1.2.5 Release – LangChain GitHub

답글 남기기