当Prompt变成Shell:我读微软关于AI-Agent框架RCE漏洞的一篇研究

原文地址:
https://www.microsoft.com/en-us/security/blog/2026/05/07/prompts-become-shells-rce-vulnerabilities-ai-agent-frameworks/

这篇文章是我基于微软安全团队原始研究做的中文转写与技术整理。文中配图均保留自原博客,并按原始论证链路穿插到正文中,方便直接在博客里阅读和复盘。

我读完微软这篇文章后,一个最直接的感受是:AI Agent 的安全问题,已经不能再被理解成“模型说错话”这么简单了。

一旦模型被接上插件、代码执行器、搜索能力、文件读写能力、向量检索能力,它面对的就不再只是“内容风险”,而是执行风险。这篇研究最有价值的地方,不是又证明了 prompt injection 很危险,而是它把一个很多人嘴上都在说、但落到工程里常常模糊不清的判断讲透了:

当自然语言能稳定映射到工具参数时,Prompt 就可能成为 Shell 的前置层。

微软这次选取的研究对象是 Semantic Kernel。如果把 LangChain、CrewAI、Semantic Kernel 这类框架视为 Agent 世界里的“操作系统层”,那它们一旦在“模型输出 → 工具参数 → 本地执行”这条链路上出现设计缺口,影响就不会停留在单个 demo,而是会蔓延到大量建立在其上的 AI 应用。

我先说结论:这篇研究真正想提醒我们的是什么?

在我的理解里,这篇文章想传达三件事:

  1. 模型本身并不是漏洞点。
    模型是在按设计工作:理解语言、匹配工具、填充参数。
  2. 真正的漏洞常出现在框架和工具层对“模型产出”的过度信任。
    只要某个参数最终进入 eval()、文件写入、下载、命令调用、路径拼接,这条链就可能变成 exploit path。
  3. LLM 不是安全边界。
    你不能把“模型应该不会这么做”当成防护策略,必须用传统安全工程的思路去审视 Agent:输入校验、最小权限、路径约束、执行隔离、端点检测、运行时审计,一个都不能少。

背景:微软在 Semantic Kernel 里找到了两条非常典型的漏洞链

微软在研究 Semantic Kernel 时披露了两个核心漏洞:

  • CVE-2026-26030In-Memory Vector Store 相关路径可被提示词注入驱动,最终演变为主机级 RCE。
  • CVE-2026-25592SessionsPythonPlugin 暴露出的文件写入能力可被模型调用,进而把“沙箱里生成的恶意脚本”落到宿主机危险路径,实现逃逸后的持久化与执行。

文章最震撼的一句其实是:只靠一条 prompt,就足以让运行 Agent 的设备弹出 calc.exe

这并不是浏览器 0day、内存破坏或者恶意附件造成的,而是 Agent 做了它“被允许做”的事:解析自然语言、选工具、把参数送进代码路径。

图 1:微软给出的整体示意图,展示了 CVE-2026-26030 如何从本地模型交互一路走到代码执行。

代表性案例:CVE-2026-26030 如何把提示词注入推进到 RCE

这个案例之所以值得反复看,是因为它特别像很多团队日常会写出来的“正常功能”。

微软为了演示这条链,构建了一个“酒店查询 Agent”:

  • In-Memory Vector Store 存放酒店数据;
  • 对外暴露 search_hotels(city=...) 这样的查询函数;
  • 当用户说“帮我找巴黎的酒店”时,模型就会调用这个搜索插件;
  • 插件再做过滤与 embedding 相似度计算,最终返回结果。

这个设计本身看起来完全合理,甚至很典型。

图 2:微软构造的 Semantic Kernel 酒店查询 Agent,背后接了 In-Memory Vector collection。

问题 1:默认过滤逻辑把模型控制的参数直接拼进了 Python lambda

微软指出,默认过滤函数最终会形成类似下面这种逻辑:

  • 输入正常城市名时,会生成一个等价于 lambda x: x.city == 'Paris' 的表达式;
  • 这个表达式随后进入 eval() 执行。

图 3:原文中的默认过滤函数定义。

看到这里,问题其实已经很明确了:

一旦 kwargs[param.name] 完全由模型控制,且没有严格清洗,那么这就是一个经典的注入落点。

也就是说,攻击者不需要“攻破模型”,只需要通过提示词注入让模型把一个恶意字符串作为 city 参数传进去,就有机会把“数据查询”改写成“可执行表达式”。

问题 2:开发者意识到了风险,但 blocklist 思路依然挡不住动态语言

文章里提到,框架其实并不是完全没防护。它做了几层检查:

  1. 只允许 lambda 表达式;
  2. 解析 AST,阻断明显危险标识符;
  3. 执行时移除一部分内建函数能力。

图 4:原文给出的 blocklist / AST 校验实现。

但微软团队也很直白地点出了问题:

在 Python 这种动态语言里,用 blocklist 去防“表达式级代码注入”,天然就脆。

因为攻击者可以:

  • 用你没列进黑名单的属性名绕过;
  • 用不同语法路径重新拿回危险能力;
  • 通过类型系统、类层级、运行时特性,间接摸到本该被禁掉的对象;
  • 利用 AST 检测盲区(例如某些下标访问、替代写法)绕过节点级检查。

这也是我特别认同这篇文章的一点:它不是在骂某一行代码写得蠢,而是在指出一种错误的防守范式。

攻击是怎么成立的?

微软展示了它们设计的恶意 prompt。核心目标不是“让模型输出敏感内容”,而是逼着模型去调用 search_hotels(),并把恶意参数塞进去

图 5:原文中的恶意 prompt 示例,要求 Agent 以恶意参数调用 search_hotels

随后,Agent 真的触发了对应的工具调用。

图 6:模型最终发起了携带恶意参数的 search_hotels 调用。

接下来发生的事情,就是整个漏洞链里最关键的一步:

  • 恶意参数逃逸出原本的模板字符串;
  • 进入 eval() 执行环境;
  • 再借助 Python 的类型系统和导入器链路,重新拿到执行系统命令所需的能力;
  • 最终触发任意命令执行。

换句话说,这不是“模型自己学会了攻击”,而是:

模型帮攻击者把一条精心构造的输入,成功送达了本来就危险的执行上下文。

为什么原有防护没挡住?

微软把绕过原因拆得很清楚。我把它整理成更适合工程复盘的四点:

1)危险名字列得不全

攻击链里用到的一些关键属性/方法,根本不在 blocklist 里。只要黑名单不完整,攻击者就总能找到新的路径。

2)结构检查通过了,但语义是恶意的

攻击 payload 仍然被包装成“合法 lambda 表达式”,因此“只允许 lambda”这条规则在结构上是通过的。

3)清空 __builtins__ 不等于安全

哪怕把内建函数删掉,只要攻击者还能从对象图、类型系统、类层级、导入器链路里摸回危险能力,保护就只是表面上的。

4)AST 检查存在盲区

如果校验只盯着 ast.Nameast.Attribute 这类节点,而忽略其他访问路径,那么攻击者完全可以换一种写法继续往前走。

这个漏洞后来怎么修?

微软在负责任披露后,Semantic Kernel 团队做了四层加固。我觉得这部分是最值得开发者直接抄作业的:

  • AST 节点白名单:只允许比较、布尔逻辑、算术、字面量等安全构造;
  • 函数调用白名单:不是“出现调用就算了”,而是继续核验“调的是不是被允许的函数”;
  • 危险属性阻断:重点封堵类层级遍历和对象逃逸常用路径;
  • 名称节点限制:把裸标识符压缩到最小范围,例如只允许 lambda 参数本身。

这背后的思路非常重要:

不要问“还能补哪些黑名单”,而要问“这个执行面里到底允许什么”。

也就是说,真正有效的路线通常不是 blocklist,而是 allowlist + capability minimization。

谁会受影响?

微软给出的判断条件很清楚。如果你的 Agent 同时满足以下条件,就需要重点看:

  • 使用 Python 版 semantic-kernel
  • 版本早于 1.39.4
  • 使用了 In-Memory Vector Store,并依赖其过滤能力作为搜索插件后端。

如果命中这些条件,最直接的动作就是升级到 1.39.4 或更高版本。

但我觉得这篇文章另一个很有价值的提醒是:修补之后,还得追问“在修补之前有没有已经被打过”。

所以微软建议把排查重点放到“脆弱版本部署开始”到“升级完成”之间的窗口里,去看:

  • Agent 宿主进程是否生成过异常子进程;
  • 是否有异常外连;
  • 是否出现异常持久化痕迹;
  • 是否生成过可疑脚本或工具链落地文件。

这其实已经完全回到了传统主机安全与检测响应的范畴。

第二条链:CVE-2026-25592 把“沙箱内执行”推成“宿主机写文件”

如果说前一个漏洞强调的是“参数注入走到 eval()”,那第二个漏洞更像是Agent 工具暴露边界错误带来的后果。

Semantic Kernel 自带一个 SessionsPythonPlugin,它允许 Agent 在 Azure Container Apps 的动态会话沙箱里执行 Python 代码。正常安全模型是这样的:

  • 代码运行在隔离容器里;
  • 容器有自己的文件系统;
  • 宿主机与容器之间有边界;
  • UploadFile / DownloadFile 这类能力本来只是帮助跨边界搬运数据。

表面上看,这个设计也很合理。

真正的问题:一个本不该暴露给模型的下载函数,被注册成了可调用工具

在 .NET SDK 里,DownloadFileAsync 被错误地加上了 [KernelFunction] 属性。这意味着它被正式宣传给了模型:

  • 这是一个可调用工具;
  • 这是它的参数 schema;
  • 你可以用它。

当这一步发生之后,localFilePath 这种直接决定“宿主机把文件写到哪里”的参数,也就变成了模型可控输入

于是问题就从“沙箱很安全”一下子变成了:

攻击者不需要打穿 hypervisor,不需要利用容器逃逸,只需要让模型替他调用这个工具。

第二条攻击链是怎么串起来的?

微软把这条链拆成了三个步骤,我觉得非常适合作为你审计自家 Agent 时的 checklist。

第一步:在沙箱里生成恶意载荷

攻击者先通过 prompt injection,诱导 Agent 使用 ExecuteCode 工具,在容器内生成脚本或 payload。

这时候载荷还被关在沙箱里,单独看似乎没什么问题。

第二步:再利用下载工具,把沙箱里的文件写到宿主机危险路径

随后,攻击者继续注入一条指令,让模型调用 DownloadFileAsync,把刚才生成的文件拉到宿主机指定目录。

Agent 随后发起了真正的下载调用。

在微软的例子里,这个目标路径指向了 Windows 的 Startup 目录。这样一来,文件虽然最初诞生于容器,但最终落点已经是宿主机的自启动位置。

第三步:等待宿主机执行

当用户下次登录时,脚本被自动执行,完整 RCE 也就落地了。

这条链条最值得警惕的地方在于:

沙箱本身不一定被“攻破”了,但它的价值被暴露给模型的桥接工具直接抵消了。

所以很多团队今天谈 Agent 隔离时,容易把注意力过多放在“代码在哪个容器里跑”,却忽略了一个更根本的问题:

模型到底还能调用哪些跨边界工具?这些工具的参数是不是完全可信?

这个问题后来怎么补?

Semantic Kernel 的修复很直接,也很有代表性。

首先,它把根因切掉了:移除 [KernelFunction],让这个函数不再暴露给模型。

一旦模型再也看不到这个工具,prompt injection 自然也就没法直接打到这条危险路径上。

其次,它又补了传统工程防线:

  • 对下载目标路径做规范化处理;
  • 做目录白名单匹配;
  • 保证目标路径必须落在允许范围内。

图 12:原文说明,对上传/下载都增加了 opt-in 式路径保护。

如果你的 Agent 使用的是早于 1.71.0 的 Semantic Kernel .NET SDK,那么这条问题链就需要重点关注。

我读完之后最认同的一句话:LLM 不是安全边界

这一点,微软在文中讲得非常彻底。

过去 Web 安全里,我们早就知道:

  • 不可信输入不能直接进 SQL;
  • 不可信输入不能直接进文件系统 API;
  • 不可信输入不能直接进命令执行路径。

今天 Agent 时代,本质上只是把“输入形式”从表单、URL、JSON,换成了自然语言。但它依然是不可信输入。

也就是说,只要模型能影响:

  • 文件路径;
  • 下载目标;
  • shell 命令;
  • 过滤表达式;
  • 代码执行器入参;
  • 插件函数参数;

那这些值就必须被视为攻击者可控数据

微软把这个问题归纳得很准确:

  • 这不是 AI 模型自己“出 bug”;
  • 而是 Agent 架构和工具设计让“模型驱动执行”变成了可滥用能力;
  • 一旦工具连接到真实系统,prompt injection 的影响就会从“聊天机器人误答”升级成“文件写入、数据窃取、RCE”。

从防守视角看,我认为这篇文章给了 5 个可立即落地的检查点

1)把所有模型可控参数都当成 attacker-controlled input

不要因为参数来自 tool call schema,就默认它“比普通用户输入更可信”。本质上它仍是模型中转后的不可信数据。

2)优先做 allowlist,而不是继续堆 blocklist

尤其在 Python、JavaScript 这类动态语言里,表达式执行和对象访问能力非常灵活,单靠黑名单很容易被绕开。

3)重新审视“本不该给模型”的工具

很多问题不是出在核心业务逻辑,而是某个辅助方法、桥接函数、下载函数、调试函数、批处理函数,意外地被注册成了可调用工具。

4)把端点检测接回 Agent 宿主

如果 Agent 理论上只是做检索、归档、搜索、问答,那它突然:

  • 拉起 cmd.exe / powershell.exe
  • 写入启动目录;
  • 产生异常子进程;
  • 发起不符合业务语义的外连;

这就应该被当成高优先级安全事件看待。

5)不要把“有沙箱”当成万灵药

真正要问的是:

  • 模型是否能调用跨越沙箱边界的工具?
  • 工具是否能接触宿主机路径?
  • 参数有没有最小化、白名单化和路径规范化?
  • 下载/上传是否被限制在明确的受控目录内?

原文最后还给了一个 CTF,这个设计挺有意思

微软把其中一个脆弱案例打包成了一个可交互的 CTF,核心目标是让研究人员在受控环境里亲手感受:

  • prompt injection 怎么越过“语言层”;
  • 工具调用怎么成为真正的 exploit primitive;
  • 为什么“让模型调工具”这件事,本质上已经进入系统安全问题域。

原始 CTF 地址:

最后总结一下

如果只看标题,很多人可能会把这篇文章理解成“微软又报了两个 CVE”。

但在我看来,这篇文章真正重要的意义是:它把 Agent 安全里一个正在被快速放大的现实讲清楚了——

当 Prompt 可以稳定驱动工具调用时,语言就不再只是语言,它已经开始接近执行入口。

所以今天讨论 AI Agent 安全,已经不能只盯着:

  • jailbreak 成不成功;
  • 回复有没有越权;
  • 模型有没有泄露提示词。

更要盯着:

  • prompt 是否能改写工具参数;
  • 工具参数是否进入危险 sink;
  • 框架是否把内部 helper 暴露成了模型工具;
  • 隔离边界是否被“桥接函数”无声打通;
  • 宿主机侧是否具备足够的运行时检测能力。

如果你的团队正在做 Agent 化改造,我会建议把这篇研究当成一份非常典型的审计样本:不是为了照抄某个 exploit,而是为了重新定义你的安全建模方式。


当Prompt变成Shell:我读微软关于AI-Agent框架RCE漏洞的一篇研究
http://example.com/2026/05/08/ms-agent-rce-semantickernel-study/
Author
Jentle
Posted on
May 8, 2026
Licensed under