flawopen.com/Teardowns/cve-2026-90970-gitlab-ai-gateway-prompt-template-sandbox-escape
CVE-2026-90970:GitLab AI Gateway 提示模板沙箱逃逸
精心构造的 Duo Agent Platform 流程可能逃逸 AI Gateway 提示模板沙箱并执行命令。自托管 Gateway 管理员应升级到对应 GitLab 分支的修复版本。[1], [4]
把提示模板想成一名根据对话记录填写报告的工作人员。他应该把获准使用的字段当作普通文本抄录。但特制模板可以让他触发文件夹中的“恢复”操作,使应用根据保存的数据重建活动对象,而不是只复制文字。这样就越过了“显示数据”和“让应用解释数据”的边界。研究者报告记录了通过 parse_raw() 和 pickle 的具体路径;GitLab 公告确认了沙箱逃逸和命令执行影响,但没有披露每个步骤。[3], [6]
核心概念与专有名词
框架映射
- 本指南所述弱点的 CWE 参考: CWE-1336
根本原因剖析 (Root Cause)
GitLab 确认,在特定条件下,拥有 Duo Agent Platform 访问权限的已认证用户可能通过特制流程配置逃逸提示模板沙箱,并在 AI Gateway 上执行任意命令。问题归类为 CWE-1336,CVSS 9.9。公告没有披露所有流程条件或确切漏洞函数。2026-09-17 的公开提交加入 PromptSandboxedEnvironment,说明其禁止访问绑定对象的方法,并强化 Jinja 操作与过滤器。这与限制模板操作运行时对象相符,但提交未提及此 CVE,已查公开证据也未将其映射到修复镜像。应明确称其为独立的公开加固改动;没有公开来源将其与此 CVE 或修复镜像关联。 GitLab 公开工作项 #1850 描述了更具体的路径:Jinja 模板对对话历史中的 LangChain HumanMessage 调用 parse_raw(),从而触发 pickle 反序列化。该工作项将此演示归于报告该问题的研究人员,并称其发生在 GitLab staging 环境;厂商公告没有说明所有受影响条件。[1], [3], [4], [2]
自托管 AI Gateway 修复版本对照表
| 部署方式 / 版本线 | AI Gateway 修复版本 | 运维操作 |
|---|---|---|
| 自托管 GitLab 19.2 版本线 | 19.2.4 | 升级到对应的自托管 Gateway 镜像标签,并核实实际运行的镜像。 |
| 自托管 GitLab 19.3 版本线 | 19.3.2 | 升级到对应的自托管 Gateway 镜像标签,并核实实际运行的镜像。 |
| 自托管 GitLab 19.4 版本线 | 19.4.1 | 升级到对应的自托管 Gateway 镜像标签,并核实实际运行的镜像。 |
| 由 GitLab 托管的 AI Gateway | GitLab 表示该托管服务已完成修复。 | 确认部署使用的是 GitLab 托管的 Gateway;此情况不适用自托管镜像升级说明。 |
攻击执行流程分解
模板执行越过沙箱边界
GitLab 公开工作项 #1850 报告,模板可以对对话历史中的 LangChain HumanMessage 调用 parse_raw() 并触发 pickle 反序列化。这是研究人员记录的路径;不要推断它涵盖所有受影响流程,也不要将其当作厂商补丁。[3]
源代码对比:漏洞与安全实现
GitLab 工作项 1850 报告,易受攻击的流程将 LangChain HumanMessage 对象暴露给 Jinja,模板可调用 parse_raw() 并触发 pickle 反序列化。此防御示例不把消息对象传入模板上下文,只传递文本,并拒绝访问可调用属性和方法。它说明需要关闭的边界,但不是 GitLab 发布的补丁。公开提交 89b72644 是独立的安全加固:其标题涉及 Jinja block-set 写入,公开来源没有将其与 CVE-2026-90970 或修复镜像关联。运维人员应按照厂商公布的修复版本升级。[3], [2], [6]
源码摘录: ai_gateway/prompts/base.py(报告中的易受攻击格式化器;示例防御设计)
# GitLab 工作项 1850 中报告的易受攻击路径片段。
from jinja2 import PackageLoader
from jinja2.sandbox import SandboxedEnvironment
from langchain_core.prompts.string import DEFAULT_FORMATTER_MAPPING
from typing import Any
jinja_loader = PackageLoader("ai_gateway.prompts", "definitions")
jinja_env = SandboxedEnvironment(loader=jinja_loader)
def jinja2_formatter(template: str, /, **kwargs: Any) -> str:
return jinja_env.from_string(template).render(**kwargs)
DEFAULT_FORMATTER_MAPPING["jinja2"] = jinja2_formatter
# 这是说明性的防御模式,并非 GitLab 针对该 CVE 发布的补丁。
from typing import Any
from jinja2 import StrictUndefined
from jinja2.sandbox import ImmutableSandboxedEnvironment
class DataOnlyPromptEnvironment(ImmutableSandboxedEnvironment):
def is_safe_attribute(self, obj: Any, attr: str, value: Any) -> bool:
if callable(value):
return False
return super().is_safe_attribute(obj, attr, value)
def is_safe_callable(self, obj: Any) -> bool:
return False
def render_prompt(template: str, history: list[Any]) -> str:
safe_history = []
for message in history:
content = message.content
if not isinstance(content, str):
raise TypeError("prompt history must contain text only")
safe_history.append({"content": content})
env = DataOnlyPromptEnvironment(undefined=StrictUndefined)
return env.from_string(template).render(history=safe_history)
工程与系统安全加固清单
- ✓将自托管 AI Gateway 升级到相应受支持 GitLab 分支的 19.2.4、19.3.2 或 19.4.1,并按安装指南使用匹配镜像标签。[1], [5]
- ✓确认部署使用自托管还是 GitLab 托管 Gateway。GitLab 表示 GitLab.com、GitLab Dedicated 以及使用 GitLab 托管 Gateway 的自管实例已修复。[1]
- ✓模板接收应用活动对象时,不要把模板引擎沙箱视为完整隔离。[6]
- ✓只向模板传递惰性数据;不要将方法、反序列化器、文件/进程句柄或服务对象放入上下文。[6], [2]
- ✓明确限制属性与调用,审计会直接调用方法的内置过滤器,并测试生产环境实际使用的 Jinja 版本和配置。[3], [2], [6]
- ✓添加回归测试,确保不安全对象操作被拒绝且字符串渲染仍正常;测试和文档中不要使用真实命令 payload。[3], [6]
- ✓不要仅凭公告推断存在公开利用活动或完整攻击链。NVD 记录的 CISA Coordinator SSVC 评估在 2026-10-02 为“exploitation: none”;这是带日期的状态,不代表之后没有发生变化。[1], [4]
参考来源
- GitLab AI Gateway Critical Patch Release: 19.2.4, 19.3.2, and 19.4.1
- GitLab 提交 89b72644——独立且未确认关联的加固:“fix(prompts): block Jinja block-set NSRef writes on non-namespace objects”
- GitLab AI Gateway work item 1850 (reporter account; reproduction material omitted)
- NVD: CVE-2026-90970
- GitLab Self-Hosted AI Gateway installation guide
- Jinja 3.1 Sandbox documentation