LangChain模板注入漏洞深度解析:CVE-2025-65106安全风险与修复方案

本文详细分析了LangChain提示模板系统中存在的模板注入漏洞(CVE-2025-65106)。攻击者通过控制模板字符串结构,可访问Python对象的内部属性,如__class__和__globals__,从而提取敏感信息。漏洞涉及f-string、Mustache和Jinja2三种模板格式,影响版本包括langchain-core 1.0.0至1.0.6及0.3.79以下版本。

LangChain Vulnerable to Template Injection via Attribute Access in Prompt Templates · CVE-2025-65106

漏洞详情

包管理器: pip 受影响包: langchain-core (pip)

受影响版本:

  • = 1.0.0, <= 1.0.6

  • <= 0.3.79

已修复版本:

  • 1.0.7
  • 0.3.80

描述

背景

LangChain的提示模板系统存在一个模板注入漏洞,允许攻击者通过模板语法访问Python对象的内部属性。此漏洞影响那些接受来自不可信来源的模板字符串(而不仅仅是模板变量)的应用程序,涉及ChatPromptTemplate及相关提示模板类。模板允许属性访问(.)和索引([]),但不支持方法调用(())。属性访问和索引的组合可能根据传递给模板的对象类型实现利用。当模板变量是简单字符串(常见情况)时,影响有限。然而,当使用带有聊天消息对象的MessagesPlaceholder时,攻击者可以遍历对象属性和字典查找(例如__globals__)以获取敏感数据,如环境变量。该漏洞特指应用程序从不可信来源接受模板字符串(结构)的情况,而不仅仅是模板变量(数据)。大多数应用程序要么不使用模板,要么使用硬编码模板,因此不受影响。

受影响组件:

  • langchain-core 包
  • 模板格式:
    • F-string模板 (template_format="f-string") - 漏洞已修复
    • Mustache模板 (template_format="mustache") - 防御性加固
    • Jinja2模板 (template_format="jinja2") - 防御性加固

影响

能够控制模板字符串(而不仅仅是模板变量)的攻击者可以:

  • 通过属性遍历访问Python对象属性和内部属性
  • 从对象内部(例如 __class__, __globals__)提取敏感信息
  • 根据传递给模板的对象,可能升级到更严重的攻击

攻击向量

1. F-string 模板注入

修复前:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from langchain_core.prompts import ChatPromptTemplate

malicious_template = ChatPromptTemplate.from_messages(
    [("human", "{msg.__class__.__name__}")],
    template_format="f-string"
)

# 注意:这需要为 "msg.__class__.__name__" 传递一个占位符变量。
result = malicious_template.invoke({"msg": "foo", "msg.__class__.__name__": "safe_placeholder"})
# 先前返回
# >>> result.messages[0].content
# >>> 'str'

2. Mustache 模板注入

修复前:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import HumanMessage

msg = HumanMessage("Hello")

# 攻击者控制模板字符串
malicious_template = ChatPromptTemplate.from_messages(
    [("human", "{{question.__class__.__name__}}")],
    template_format="mustache"
)

result = malicious_template.invoke({"question": msg})
# 先前返回: "HumanMessage" (getattr() 暴露了内部属性)

3. Jinja2 模板注入

修复前:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import HumanMessage

msg = HumanMessage("Hello")

# 攻击者控制模板字符串
malicious_template = ChatPromptTemplate.from_messages(
    [("human", "{{question.parse_raw}}")],
    template_format="jinja2"
)

result = malicious_template.invoke({"question": msg})
# 可以访问对象上的非双下划线属性/方法

根本原因

  • F-string 模板: 实现使用Python的 string.Formatter().parse() 来从模板字符串中提取变量名。此方法返回完整的字段表达式,包括属性访问语法:

    1
    2
    3
    4
    5
    
    from string import Formatter
    
    template = "{msg.__class__} and {x}"
    print([var_name for (_, var_name, _, _) in Formatter().parse(template)])
    # 返回: ['msg.__class__', 'x']
    

    提取的名称未经验证以确保是简单的标识符。因此,包含属性遍历和索引表达式(例如 {obj.__class__.__name__}{obj.method.__globals__[os]})的模板字符串被接受并在后续格式化期间被求值。虽然f-string模板不支持带()的方法调用,但它们支持[]索引,这可能允许通过像__globals__这样的字典进行遍历以访问敏感对象。

  • Mustache 模板: 设计上,使用 getattr() 作为回退以支持访问对象上的属性(例如,User对象上的{{user.name}})。然而,我们决定将其限制为更简单的原语,这些原语是dictlisttuple类型的子类,作为防御性加固,因为不可信的模板可能利用属性访问来触及任意对象上的内部属性(如__class__)。

  • Jinja2 模板: Jinja2默认的SandboxedEnvironment阻止双下划线属性(例如__class__),但允许访问对象上的其他属性和方法。虽然LangChain中的Jinja2模板通常与受信任的模板字符串一起使用,但作为深度防御措施,我们限制了环境以阻止对传递给模板的对象的所有属性和方法访问。

受影响人群

高风险场景 如果您的应用程序符合以下情况,则受到影响:

  • 接受来自不可信来源(用户输入、外部API、数据库)的模板字符串
  • 基于用户提供的模式动态构建提示模板
  • 允许用户自定义或创建提示模板

示例易受攻击代码:

1
2
3
4
5
6
7
8
9
# 用户控制模板字符串本身
user_template_string = request.json.get("template")  # 危险

prompt = ChatPromptTemplate.from_messages(
    [("human", user_template_string)],
    template_format="mustache"
)

result = prompt.invoke({"data": sensitive_object})

低风险/无风险场景 如果符合以下情况,则不受影响:

  • 模板字符串在应用程序代码中是硬编码的
  • 模板字符串仅来自受信任、受控的来源
  • 用户只能提供模板变量的值,而不能控制模板结构本身

示例安全代码:

1
2
3
4
5
6
7
8
# 模板是硬编码的 - 用户只控制变量
prompt = ChatPromptTemplate.from_messages(
    [("human", "User question: {question}")],  # 安全
    template_format="f-string"
)

# 用户输入仅填充 'question' 变量
result = prompt.invoke({"question": user_input})

修复方案

F-string 模板

F-string模板存在明确的漏洞,其中属性访问语法可被利用。我们已添加了严格的验证来防止这种情况:

  • 添加了验证,强制变量名必须是有效的Python标识符
  • 拒绝像 {obj.attr}{obj[0]}{obj.__class__} 这样的语法
  • 只允许简单的变量名:{variable_name}
1
2
3
4
5
# 修复后 - 这些会在模板创建时被拒绝
ChatPromptTemplate.from_messages(
    [("human", "{msg.__class__}")],  # ValueError: Invalid variable name
    template_format="f-string"
)

Mustache 模板(防御性加固)

作为防御性加固,我们限制了Mustache模板支持的功能以减少攻击面:

  • 用严格的类型检查替换了 getattr() 回退
  • 仅允许遍历到dictlisttuple类型
  • 阻止对任意Python对象的属性访问
1
2
3
4
5
6
7
# 加固后 - 属性访问返回空字符串
prompt = ChatPromptTemplate.from_messages(
    [("human", "{{msg.__class__}}")],
    template_format="mustache"
)
result = prompt.invoke({"msg": HumanMessage("test")})
# 返回: "" (访问被阻止)

Jinja2 模板(防御性加固)

作为防御性加固,我们显著限制了Jinja2模板的能力:

  • 引入了_RestrictedSandboxedEnvironment,阻止所有属性/方法访问
  • 仅允许从上下文字典进行简单的变量查找
  • 在任何属性访问尝试时引发SecurityError
1
2
3
4
5
6
# 加固后 - 所有属性访问被阻止
prompt = ChatPromptTemplate.from_messages(
    [("human", "{{msg.content}}")],
    template_format="jinja2"
)
# 引发 SecurityError: Access to attributes is not allowed

重要建议: 由于Jinja2的表达能力和完全沙箱化的难度,我们建议仅将Jinja2模板保留用于受信任的来源。如果您需要接受来自不可信用户的模板字符串,请改用具有新限制的f-string或mustache模板。虽然我们已经加固了Jinja2实现,但模板引擎的性质使得全面的沙箱化具有挑战性。最安全的方法是仅在控制模板源时使用Jinja2模板。

重要提醒: 许多应用程序不需要提示模板。模板对于变量替换和动态逻辑(if语句、循环、条件)很有用。然而,如果您正在构建聊天机器人或对话应用程序,通常可以直接使用消息对象(例如,HumanMessageAIMessageToolMessage)而无需模板。直接构造消息完全避免了与模板相关的安全问题。

补救措施

立即行动

  • 审计您的代码,查找任何模板字符串来自不可信来源的位置
  • 更新到langchain-core的已修复版本
  • 审查模板使用情况,确保模板结构与用户数据的分离

最佳实践

  • 考虑您是否真的需要模板 - 许多应用程序可以直接使用消息对象(HumanMessageAIMessage等)而无需模板
  • 将Jinja2保留给受信任的来源 - 仅在完全控制模板内容时使用Jinja2模板

参考信息

漏洞信息

  • CWE 弱点: CWE-1336 - 模板引擎中使用的特殊元素的不当中和
  • 严重等级: 高
  • CVSS 总体评分: 8.3
  • CVSS v4 向量: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N
  • EPSS 评分: 0.045% (第13百分位)
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计