研究

Manus:警告出现时,邮件里的代码已执行

Salt Labs 近期公开的研究记录,Manus 曾把邮件中的混淆代码当作解码任务执行,随后才要求用户批准,研究者由此进入带有连接器凭据的用户沙箱。研究报告称问题已解决,这次失误仍说明审批必须守在第一次执行之前。

浅纸手绘概念图:邮件被送进正在工作的云端电脑,审批提醒停在已经启动的执行步骤之后。
文章导航

让 AI 看一下最新邮件,本来只是一个读取任务。Salt Labs 在 Manus 上做的受控研究,却让邮件里的内容进入了 Node.js 运行环境。屏幕随后显示安全警告,要求用户批准危险操作。到这个时候,代码已经跑过了。

Salt Labs 的研究报告称,这项问题已经解决。这次失误留下了一个值得追问的问题:用户只让代理读邮件,邮件里的内容怎么就变成了需要运行的程序?

1 邮件正文,借用了用户的操作权限

Manus 可以连接 Gmail,再按用户要求查找和处理邮件。这里的用户已经授权自己的邮箱,研究者则能向这个邮箱发送消息。只要代理在正常任务中读到这封邮件,正文就有机会进入它的工作上下文。发送邮件的人没有因此获得用户身份,邮件中的要求也没有因此成为用户的新指令。

这类授权关系很容易在自然语言界面里被抹平。用户要求的是查看邮件,正文却要求先做另一件事才看得懂邮件。代理若把后一个要求当成完成前一个任务的必要步骤,就会替陌生发件人调用自己的工具。收件箱允许外人投递,工具权限来自用户,两者在这一刻接到了一起。

Manus 的沙箱文档说明,每项任务可以获得隔离的云端计算环境,其中有文件系统、浏览器、网络和软件工具,也会保存完成任务所需的配置及令牌。这个环境能写代码、运行程序,适合处理复杂工作。邮件里的内容一旦被送去执行,接触到的就包括这份任务环境原有的能力。

沙箱隔离主要约束程序可以影响哪些系统。任务内部仍有另一个问题:什么内容有权让程序开始执行?报告中的代码运行在收件人的任务沙箱,公开研究没有描述虚拟机逃逸。对连接了邮箱的任务而言,留在沙箱内部也足以接触有价值的数据和凭据。

2 为了读懂内容,先把内容当程序运行

研究者先尝试直接让代理执行命令,代理要求明确批准。换成 Base64 后,部分测试能让代理通过命令行工具或 Python 解码,但输出仍被视为不可信内容,后续执行被拦下。后来有效的是 JSFuck,一种只用 []()!+六个字符表达 JavaScript 的写法。邮件把它包装成需要解码才能读取的消息,代理随后借助 Node.js 处理。

JSFuck 的难点在于它本身就是 JavaScript 表达式。项目文档展示了表达式怎样利用数组、布尔值和类型转换构造字符串,再访问和调用函数。例如 ![] + []可以得到字符串 "false",从这类字符串中取字符,便能拼出属性名。表达式继续组合,能够构造和执行函数。满屏括号看不出普通单词,JavaScript 引擎仍会把这些字符当作代码执行。

Base64 的字节解码可以输出一段文本,不必执行它。把 JSFuck 交给 JavaScript 引擎求值,则可能在得到所谓明文的同时完成函数调用。对这样的输入,解码器到底是一个只读转换器,还是一个有副作用的解释器,会直接改变安全结果。给动作起名叫“查看内容”,没有改变它实际启动了哪种运行环境。

Node.js 的 --eval选项用于执行传入脚本。运行环境允许访问什么,要看启动配置、进程权限和暴露的能力。研究报告记录,代理最初完成简单 JavaScript 求值,随后同一路径能够触发系统命令,并建立回连到研究者控制端的连接。外来邮件中的程序已经能在用户的任务环境里运行,并通过这个环境的网络与外部通信。

3 审批放在执行后,拒绝也来不及

这条路径中,代理后来确实认出了危险内容。Salt Labs 记录的时序是先调用 Node.js 处理消息,载荷执行,然后出现要求用户批准的警告。直接命令测试把危险动作挡在执行前,JSFuck 路径却在出现警告前就完成了第一次执行。

邮件内容进入代理后先启动程序执行,随后才出现审批提醒。对照路径把审批放在启动程序之前,拒绝时程序尚未运行。
图 1:审批保护哪一步,取决于它在执行顺序中的位置。准备、解码和预览也可能运行程序。查看原图。

可以用一个只改内存数组的本地模型看清顺序。下面这段代码把一条记录当作执行副作用,随后才追加审批提醒。运行后的数组先有 "code ran",再有 "approval requested",提醒不会撤销已经写入的第一条记录。

const events = [];
const source = 'events.push("code ran")';
Function('events', source)(events);
events.push('approval requested');
console.log(events);

把审批提前,并在拒绝时不调用 Function,数组里便只有 "approval requested"。这是本站实际运行的内存顺序模型,用来解释审批位置,并未复现 Manus 的模型决策或产品修补。产品验收还需要沿邮件读取到工具启动的实际过程观察第一次副作用发生在哪里。

因此,预览或解码任务也应计入执行权限。如果一个工具接收陌生内容时可能运行其中的程序,审批要在启动这个工具前完成。审批记录还应对应将要执行的内容、工具和可访问资源。用户批准一次普通解码,不应顺带批准后面换入的程序。把消息解析为静态结构、保留文本供查看,则可以在更少权限的环境里完成。

4 代码留在沙箱里,仍可能接触邮箱

任务沙箱需要替用户调用 Gmail,连接器凭据因而成了影响范围的重要部分。Salt Labs 报告在其测试环境中找到了 Gmail OAuth 令牌,并记录其他已连接服务的凭据通过环境变量可达。Google Drive、GitHub 的后果取决于用户有没有连接,以及授予了哪些权限。

OAuth 令牌是授予应用的访问许可。Bearer Token 的标准定义说明,持有有效令牌的一方能够行使它代表的权限,无需再证明自己知道用户密码。攻击者在任务运行环境里取得这类令牌,就可能绕开原来的代理界面,直接使用相应服务允许的能力。令牌的有效期、授权范围和服务端限制仍然约束后续访问。

Gmail 的权限表把读邮件、发邮件及修改邮件分开。gmail.readonly用于读取邮件及设置,gmail.send允许代表用户发信,权限更广的范围又会允许更多操作。本次公开报告没有列出每个测试账户的完整授权范围,邮箱读取、发信和进一步账户恢复应分别判断。

仅清理沙箱中的异常进程,也不能保证已经复制出去的令牌失效。发现连接器凭据可能外泄时,需要从服务提供方撤销对应授权,再重新建立必要连接。Manus 帮助文档说明移除连接器会清除其保存的登录信息,Google 账户设置则可以撤销第三方应用对账户的访问。只改密码是否足够,要看该服务怎样处理已有授权,不能省掉令牌这一项。

5 把第一次执行从读取任务里分出来

Salt Labs 在披露时称该问题已解决,未公布 Manus 内部修补差异、完整受影响版本或每项防护的实现。要判断修补是否覆盖了这一类问题,需要检查邮件中的内容还能否在审批前启动程序,以及这个程序能接触哪些凭据。

对自己开发代理的团队,最有用的检查是让正常读取与程序执行走不同的授权路径。读取邮件先取得正文,解析器只输出数据。确实需要运行不可信程序时,执行环境不应顺带拿到邮箱令牌、云盘凭据和无关文件,网络也只开放任务必要的目的地。这样即使内容影响了模型的选择,可用权限仍由工具和运行环境明确限制。

给 JavaScript 换一个上下文并不自动实现这种隔离。Node.js 的 node:vm文档明确提醒它不能用作运行不可信代码的安全机制。虚拟化、进程限制和凭据隔离分别控制不同能力,具体选择应接受真实授权测试。测试时可以放入假令牌,观察受限程序能否读到,再检查它能否访问指定文件或发起受限网络请求。这样才能看到权限限制是否生效,避免只检查代理最后有没有显示警告。

用户把邮箱接给代理,是为了少处理一些邮件。这份授权应当让代理替用户读懂消息,执行权限仍须由用户的任务决定,不能随邮件里陌生人的要求一起改变。