外观
Copilot Studio Python 沙箱逃逸与多租户影响
摘要
DEF CON 34 的 Copilot Studio 研究分析了 Microsoft 托管 Python 工具使用的 Plex 沙箱。研究者报告,静态字符串过滤和对象访问限制可被拆分字符串、动态属性访问及 Python 反射链组合绕过;在其测试配置中,编码变换配合提示词注入达到 100% 成功。该结果针对特定模型、提示词和早期发布环境,不代表所有 Copilot Studio 请求都可逃逸。
逃逸后,作者称其读取到应用源代码、沙箱与 gRPC 定义、生产 TLS 私钥、140 余个欧盟机构租户标识和 75 个环境变量,并观察到测试容器间复用同一 Administrator 哈希。MSRC 将问题评为 Moderate,指出无直接出站网络且容器存活约 2–5 分钟;作者则观察到最长约 9 小时的旧容器、通过模型响应带出数据以及跨租户共享迹象。双方陈述反映不同测试条件,本文分别保留。
产品入口与 Plex 执行架构
Power Platform 的自定义提示词构建器可以调用 Microsoft 托管的 Python Code Interpreter。模型根据用户自然语言生成 Python,再代表用户执行。研究者观察到的 Plex 架构由以下部分组成:
text
Power Platform Gateway :443
-> gRPC
-> Hyper-V 边界内的 Worker
-> CodeInterpreterGrpcServiceShim.exe(YARP 反向代理)
-> CodeInterpreterShim.exe / python server
-> make_sandbox()
-> exec(user_python)(同一 Worker 进程内)
-> connector_client
-> SidecarService同一 Plex 架构也用于作者此前研究的 Dataverse Plugin 沙箱。这个复用本身并非安全漏洞,但意味着容器隔离、gRPC 接口、身份和镜像资产的设计问题可能跨产品出现。
沙箱做了哪些限制
初始测试中,模型直接读取环境变量或列出文件会被阻断。沙箱隐藏 import 和危险模块,并对生成代码及运行时属性访问做检查。简单文件通配符一度能绕过“列出文件”的语义限制,研究者由此取得 sandbox.py 和证书等文件;但同一提示词有时成功、有时失败,连续失败后护栏似乎会更敏感。
这说明系统并不是一个固定 Python 解析器加固定策略,而是至少包含模型生成、护栏判定、受限运行时和结果摘要多个概率性阶段。研究者从网络响应中看到模型思考与工具执行结构后,开始针对真正的运行时控制而不是单句护栏进行测试。
核心创新与差异
Python 沙箱反射绕过并非新机制。本研究的增量有三点:
- 反射链由 Agent 自动生成和多次改写,静态敏感词过滤需要面对运行时字符串重组。
- 即使容器没有任意出站网络,代码结果仍会进入提示词响应,模型回复本身就是数据出口。
- Worker 中存在源代码、生产证书、租户信息和共享身份材料,使单容器读取升级为多租户风险。
因此应把“Python 对象能力控制失效”“Worker 放入了过多控制面资产”“输出和连接器构成出口”作为三道不同的安全控制,而不是笼统写成一次沙箱逃逸。
MRO 与双下划线反射链的原理
沙箱删除 import 并隐藏 os、subprocess 等模块,但没有切断 Python 对象图。任何对象都带有类型信息,所有类最终继承自 object;运行时还保留已加载类及其函数全局变量。研究者利用的能力链可以抽象为:
text
任意对象
-> 类型(class)
-> 方法解析顺序(Method Resolution Order, MRO)
-> object 根类
-> 当前解释器已加载的子类集合
-> 某个类初始化函数的全局命名空间
-> sys.modules
-> 沙箱试图隐藏的模块和 Worker 自身代码这里没有调用 import。沙箱原规则主要做字符串检查,研究者把双下划线属性名拆成多个片段,再由 getattr 在运行时组合。检查器看到的字符串不含完整敏感属性,Python 解释器最终访问的却仍是同一个对象能力。
演讲在 GPT-4.1 mini 的特定提示词和测试配置中,把 XOR 编码、提示词注入与反射组合后报告 100% 成功率。这个结果说明该配置下绕过已稳定,不代表所有模型版本、地区和沙箱镜像都具有相同成功率。
作者取得了哪些资产
材料称逃逸后可读取:
- 完整应用源代码,包括代码解释器沙箱和 gRPC 定义。
- 生产 TLS 证书及私钥。
- 140 余个欧盟机构租户标识。
- 75 个环境变量,其中包含 Azure AD 客户端标识、租户标识和 audience。
- Service Fabric 拓扑、集群名、节点类型与区域信息。
- sidecar 地址、端口、内部主机名和完整容器文件布局。
- 监控工具、启动脚本、特权服务路径以及遗留开发产物。
读取到租户标识不等于访问了这些租户的数据;客户端标识也不一定是 Secret。真正严重的是生产私钥和跨租户共享身份材料,因为它们不应出现在执行不可信 Python 的 Worker 内。
“没有出站网络”为何仍不足
MSRC 将问题评为 Moderate,并给出三项缓解因素:容器没有直接出站网络;只支持明确接口的 gRPC;容器生存期约 2–5 分钟。作者逐项给出不同观察:
提示词响应就是出口
Python 执行结果会被模型摘要并返回。研究者称通过这条通道带出了证书、注册表转储、沙箱代码和环境变量。即使 TCP/HTTP 被完全阻断,只要任意文件内容可以进入对话响应,机密性影响仍然存在。
Connector 是受限但真实的网络能力
Worker 的 connector_client 不能访问任意 URL,但可以调用已正确配置的连接器。接口定义位于 core_interfaces.py,实现位于 workerservice.py。这不是通用网络出口,却可能在用户已配置连接器的情况下访问外部或内部服务,因此需要单独授权和目标限制。
容器寿命观察存在明显分歧
作者称持续观察到运行 2 小时以上的容器,最长约 9 小时,并在幻灯片中展示系统启动时间。这与 MSRC 的 2–5 分钟说明不一致,可能来自 Worker 复用、容器层级或测试环境差异。公开材料不足以裁定哪个数字适用于所有生产环境,本文同时保留两方陈述。
多租户与共享 Administrator 哈希
环境变量显示 CS_CLUSTERRING=mt,作者据此判断 Worker 属于多租户环境。研究团队在此前的 Dataverse Plugin 沙箱研究中读取本地 SAM 数据库,并发现 Administrator 账户哈希在后续容器、不同 Power Platform 环境和不同租户环境中相同。
这项观察说明镜像或部署过程可能复用本地管理凭据,但材料没有公开被遮盖的后续利用内容,也没有证明该哈希可从一个租户登录另一个租户的服务。本文将它记录为高风险隔离信号,不把它写成已完成跨租户接管。
去武器化 PoC:验证属性能力而不枚举运行时
本站不公开类索引、模块定位、文件读取或凭据提取链。以下本地夹具验证防护是否在“最终属性名”上生效,而不是只扫描源代码文本:
python
DENY = {"__class__", "__mro__", "__subclasses__", "__globals__"}
def is_dunder(name: str) -> bool:
return len(name) > 4 and name.startswith("__") and name.endswith("__")
def safe_getattr(obj, name, *default):
if not isinstance(name, str) or is_dunder(name) or name in DENY:
raise AttributeError("DRY_RUN_ATTRIBUTE_BLOCKED")
return getattr(obj, name, *default)
tests = [
"__class__",
"__" + "class__",
bytes.fromhex("5f5f636c6173735f5f").decode(),
]
for name in tests:
assert_raises(AttributeError, safe_getattr, object(), name)还应将 vars、描述符、序列化、异常对象和回调返回值纳入对象能力测试,确保没有第二个属性访问函数绕过策略。更根本的验证是在独立低权限进程中运行无害代码,并证明进程无法看到宿主模块、控制面文件、其他租户目录或 Secret。
修复及其覆盖范围
演讲展示新版 safe_getattr:名称一旦属于双下划线魔术属性就抛出 AttributeError,并在注释中直接说明用于阻断 MRO traversal。研究者称该修复最初只出现在 Power Platform 的“early release cycle”环境,且源码注释引用了其 MSRC 工单描述。
这种补丁能关闭演示中的主要反射路径,但作者称还发现其他逃逸方式。单独扩展黑名单仍难以证明 Python 对象图被真正隔离;发布验证应检查所有区域和实际镜像摘要,并配合进程、文件系统和系统调用限制。
防护措施与验证方法
- 不在完整 Python 对象模型上依赖敏感词黑名单;优先使用能力受限解释器、独立低权限进程和系统调用级隔离。
- 所有动态属性访问、解码、描述符和反射最终经过同一运行时策略点;对拼接、编码和间接调用做语义回归。
- 在启动用户代码之前清除不必要模块和对象引用,避免把 Worker 自身实现暴露在同一解释器对象图中。
- 每个租户、环境和任务使用独立短期身份;Worker 内不得放置控制面 TLS 私钥、全局租户目录或可复用本地管理凭据。
- 把模型响应、异常、生成文件和连接器视为网络出口,按数据分类限制可返回内容。
- 对容器复用设置可验证上限,任务结束后销毁内存、临时文件和连接器状态;监测真实 Worker 年龄而不是只依赖配置值。
- 从所有发布环验证补丁,记录沙箱镜像摘要、模型版本和策略摘要。
关键结果与实际影响
研究证明,模型护栏和源代码字符串检查没有切断 Python 的运行时对象能力;“无直接网络”和“短生命周期”也不能抵消响应回显、连接器、共享资产和多租户身份的风险。实际影响应按资产逐项判断:读取源代码与拓扑属于信息泄露,读取生产私钥和共享管理身份可能带来更严重后果,但公开材料没有证明 140 余个租户都被进一步访问。
局限与待验证问题
公开材料没有提供可独立运行的 Copilot Studio 测试环境,100% 成功率仅适用于作者的模型、提示词、区域和镜像。MSRC 与作者对容器生存期及影响有明显分歧,修复部署范围也可能随时间变化。需要厂商公告、跨区域镜像核验和独立团队复测,才能确定最终受影响版本、其他反射路径和租户隔离效果。