LangChain提示模板注入漏洞分析:CVE-2025-65106安全风险与修复

本文详细分析了LangChain中影响ChatPromptTemplate及相关提示模板类的模板注入漏洞(CVE-2025-65106)。该漏洞允许攻击者通过模板语法访问Python对象内部属性,在特定条件下可能导致敏感信息泄露。文章涵盖了漏洞原理、影响范围、攻击向量示例以及官方修复方案。

LangChain 因提示模板中的属性访问而易于受到模板注入攻击 · CVE-2025-65106 · GitHub 咨询数据库

漏洞详情

: 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}})。然而,我们决定将此功能限制为继承自 dict、list 和 tuple 类型的简单原语,作为防御性加固,因为不受信任的模板可能利用属性访问来触及 __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", "用户问题: {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: 无效的变量名
    template_format="f-string"
)

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

  • getattr() 后备机制替换为严格的类型检查
  • 仅允许遍历到 dict、list 和 tuple 类型中
  • 阻止对任意 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: 不允许访问属性

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

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

补救措施

立即行动

  • 审核代码中任何从不受信任来源获取模板字符串的位置
  • 更新到已修补版本的 langchain-core
  • 审查模板使用情况,确保模板结构与用户数据的分离

最佳实践

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

参考

漏洞信息

  • 发布日期: 2025年11月19日
  • 严重程度: 高 (CVSS 分数:8.3)
  • 弱点: CWE-1336 模板引擎中特殊元素的不当中和
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计