LangChain序列化注入漏洞致密秘钥泄露技术分析

本文详细分析了LangChain JavaScript库中存在的序列化注入漏洞(CVE-2025-68665),该漏洞允许攻击者通过特制数据提取环境变量中的秘密信息,并深入探讨了漏洞的技术原理、影响范围和修复方案。

LangChain序列化注入漏洞致密秘钥泄露技术分析

漏洞详情

包信息

  • npm: @langchain/core (npm)

  • 受影响版本: >=1.0.0, <1.1.8 或 <0.3.80

  • 已修复版本: 1.1.8, 0.3.80

  • npm: langchain (npm)

  • 受影响版本: >=1.0.0, <1.2.3 或 <0.3.37

  • 已修复版本: 1.2.3, 0.3.37

技术描述

漏洞背景

LangChain JS的toJSON()方法(以及随后使用JSON.stringify()字符串化对象时)存在序列化注入漏洞。该方法在序列化kwargs中的自由格式数据时,未对包含’lc’键的对象进行转义。’lc’键被LangChain内部用来标记序列化对象。当用户控制的数据包含此键结构时,在反序列化过程中会被视为合法的LangChain对象而非普通用户数据。

攻击面

核心漏洞位于Serializable.toJSON()方法:该方法未能转义kwargs(例如additional_kwargsmetadataresponse_metadata)中包含’lc’键的用户控制对象。当这些未转义的数据后来通过load()反序列化时,注入的结构会被视为合法的LangChain对象而非普通用户数据。

此转义漏洞启用了多种攻击向量:

  1. 通过用户数据注入:恶意LangChain对象结构可以通过用户控制的字段如metadataadditional_kwargsresponse_metadata注入
  2. 秘密提取:当启用secretsFromEnv时(该选项没有明确的默认值,实际上默认行为为true),注入的秘密结构可以提取环境变量
  3. 通过导入映射进行类实例化:注入的构造器结构可以使用攻击者控制的参数实例化提供的导入映射中的任何类

关于导入映射的说明:类必须明确包含在导入映射中才能被实例化。核心导入映射包括标准类型(消息、提示、文档),用户可以通过importMapoptionalImportsMap选项扩展此映射。这种架构自然地限制了攻击面——因为用户通过他们提供的导入映射控制哪些类可用,所以不需要allowedObjects参数。

受影响的应用程序

如果应用程序满足以下条件,则易受攻击:

  1. 通过JSON.stringify()在Serializable对象上序列化不可信数据,然后使用load()反序列化——如果用户控制的数据(例如来自LLM响应、元数据字段或用户输入)包含’lc’键结构,那么信任自己的序列化输出会使您易受攻击。
  2. 使用load()直接反序列化不可信数据——直接反序列化可能包含注入’lc’结构的不可信数据。
  3. 使用LangGraph检查点——检查点序列化/反序列化路径可能受影响。

最常见的攻击向量是通过LLM响应字段,如additional_kwargsresponse_metadata,这些字段可以通过提示注入控制,然后在流操作中序列化/反序列化。

漏洞影响

控制序列化数据的攻击者可以通过注入{"lc": 1, "type": "secret", "id": ["ENV_VAR"]}来提取环境变量秘密(当secretsFromEnv: true时)。他们还可以通过注入构造器结构来使用受控参数实例化任何类,可能触发网络调用或文件操作等副作用。

关键严重性因素

  • 影响序列化路径——信任自己序列化输出的应用程序易受攻击
  • 当与secretsFromEnv: true结合时,可实现秘密提取
  • additional_kwargs中的LLM响应可以通过提示注入控制

漏洞利用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { load } from "@langchain/core/load";

// 攻击者将秘密结构注入用户控制的数据
const attackerPayload = JSON.stringify({
  user_data: {
    lc: 1,
    type: "secret",
    id: ["OPENAI_API_KEY"],
  },
});

process.env.OPENAI_API_KEY = "sk-secret-key-12345";

// 使用secretsFromEnv: true时,秘密被提取
const deserialized = await load(attackerPayload, { secretsFromEnv: true });

console.log(deserialized.user_data); // "sk-secret-key-12345" - 秘密泄露!

安全加固变更

此补丁为load()引入了以下变更:

  1. secretsFromEnv默认值改为false:禁用从环境变量自动加载秘密。未在secretsMap中找到的秘密现在会抛出错误,而不是从process.env加载。这种故障安全行为确保立即捕获缺失的秘密,而不是静默地继续使用null值。

  2. 新的maxDepth参数(默认为50):防止通过可能导致堆栈溢出的深度嵌套JSON结构进行拒绝服务攻击。

  3. toJSON()中的转义机制:现在在序列化期间将包含’lc’键的用户控制对象包装在{"__lc_escaped__": {...}}中,并在反序列化期间作为纯数据解包。

  4. JSDoc安全警告:所有导入映射选项(importMapoptionalImportsMapoptionalImportEntrypoints)现在都包含安全警告,提示切勿从用户输入填充它们。

迁移指南

大多数用户无需更改

如果您使用核心导入映射反序列化标准LangChain类型(消息、文档、提示),您的代码将无需更改即可工作:

1
2
3
4
import { load } from "@langchain/core/load";

// 使用默认设置即可工作
const obj = await load(serializedData);

从环境变量加载秘密

secretsFromEnv现在默认为false,缺失的秘密会抛出错误。如果您需要加载秘密:

1
2
3
4
5
6
7
8
9
import { load } from "@langchain/core/load";

// 显式提供秘密(推荐)
const obj = await load(serializedData, {
  secretsMap: { OPENAI_API_KEY: process.env.OPENAI_API_KEY },
});

// 或显式选择从环境变量加载(仅用于可信数据)
const obj = await load(serializedData, { secretsFromEnv: true });

警告:仅当信任序列化数据时才启用secretsFromEnv。不可信数据可能提取任何环境变量。

注意:如果遇到秘密引用但未在secretsMap中找到(且secretsFromEnv为false或环境中没有该秘密),则会抛出错误。这种故障安全行为确保您知道缺失的秘密,而不是静默地接收null值。

深度嵌套结构

如果您有超过默认深度限制50的合法深度嵌套数据:

1
2
3
import { load } from "@langchain/core/load";

const obj = await load(serializedData, { maxDepth: 100 });

自定义导入映射

如果您提供自定义导入映射,请确保它们仅包含可信模块:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { load } from "@langchain/core/load";
import * as myModule from "./my-trusted-module";

// 正确 - 明确仅包含可信模块
const obj = await load(serializedData, {
  importMap: { my_module: myModule },
});

// 错误 - 切勿从用户输入填充
const obj = await load(serializedData, {
  importMap: userProvidedImports, // 危险!
});

安全评分

  • 严重等级: 高
  • CVSS总体评分: 8.6/10
  • CVSS v3基础指标: AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N
  • EPSS评分: 0.061%(第19百分位)

弱点分类

  • CWE-ID: CWE-502
  • 弱点描述: 不可信数据的反序列化 - 产品在反序列化不可信数据时,未能充分确保生成的数据有效。

参考链接

致谢

  • ccurme(修复开发者)
  • mdrxy(修复开发者)
  • 0xn3va(报告者)
  • yardenporat353(报告者)
  • VladimirEliTokarev(报告者)
  • hntrl(修复开发者)
  • siewer(报告者)
  • jacoblee93(修复验证者)
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计