FlawOpen

flawopen.com/Teardowns/cve-2026-90970-gitlab-ai-gateway-prompt-template-sandbox-escape

● CVE-2026-90970 · CVSS 9.9[4] · 严重
安全研究 · FlawOpen

CVE-2026-90970:GitLab AI Gateway 提示模板沙箱逃逸

精心构造的 Duo Agent Platform 流程可能逃逸 AI Gateway 提示模板沙箱并执行命令。自托管 Gateway 管理员应升级到对应 GitLab 分支的修复版本。[1], [4]

CVSS 向量CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H[4]
💡 通俗易懂的原理解析 (ELI5)

把提示模板想成一名根据对话记录填写报告的工作人员。他应该把获准使用的字段当作普通文本抄录。但特制模板可以让他触发文件夹中的“恢复”操作,使应用根据保存的数据重建活动对象,而不是只复制文字。这样就越过了“显示数据”和“让应用解释数据”的边界。研究者报告记录了通过 parse_raw() 和 pickle 的具体路径;GitLab 公告确认了沙箱逃逸和命令执行影响,但没有披露每个步骤。[3], [6]

核心概念与专有名词

AI Gateway
连接 GitLab AI 功能与模型提供商的服务;自托管 Gateway 镜像单独版本化。[5], [1]
Prompt template
将固定指令与运行时值组合的模板,例如流程输入和对话上下文。[3], [6]
Sandbox escape
利用受限环境中的行为越过其原本要强制执行的边界。[3], [6]
CWE-1336
未正确中和模板引擎使用的特殊元素;CVE 记录将该弱点归类于此。[4], [1]

框架映射

  • 本指南所述弱点的 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 GatewayGitLab 表示该托管服务已完成修复。确认部署使用的是 GitLab 托管的 Gateway;此情况不适用自托管镜像升级说明。

攻击执行流程分解

步骤 1

用户可以配置自定义流程

公告所述前置条件是用户已认证并可访问 Duo Agent Platform。公告没有说明更具体的角色或全部必要流程能力。[1], [3]

步骤 2

流程提供特制的模板配置

特制流程配置到达 Gateway 提示模板沙箱。公开公告未提供完整安全复现或准确模板表达式。[1], [3]

步骤 3

模板执行越过沙箱边界

GitLab 公开工作项 #1850 报告,模板可以对对话历史中的 LangChain HumanMessage 调用 parse_raw() 并触发 pickle 反序列化。这是研究人员记录的路径;不要推断它涵盖所有受影响流程,也不要将其当作厂商补丁。[3]

步骤 4

Gateway 上可能执行命令

确认的影响是在 AI Gateway 上执行任意命令,即 Gateway 服务器端执行,而非仅仅模型未遵循指令。[1], [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)

工程与系统安全加固清单

参考来源