CVE-2026-53710 是一个严重级别的沙箱逃逸漏洞,存在于IBM的MCP Context Forge项目中的python_sandbox_server子项目。它允许攻击者在默认配置下实现RCE,CVSS 评分为10.0。
一、漏洞档案
| 项 | 值 |
|---|---|
| CVE | CVE-2026-53710 |
| 通告 | GHSA-xm98-3vcf-fph7 |
| CVSS 3.1 | 10.0 AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H |
| CWE | CWE-94 代码注入、CWE-693 保护机制失效 |
| 受影响 | IBM mcp-context-forge 的 python_sandbox_server < 1.0.2 |
| 修复版本 | 1.0.2 |
| 修复 commit | 63a2900e6301b9c8a483a38d3737a1beb3a7ce89 |
| CVE 发布 | 2026-09-15 |
| KEV | 未收录 |
| EPSS | 0.00827(55.7 百分位) |
CVE记录:https://www.cve.org/CVERecord?id=CVE-2026-53710
受影响组件:mcp-contextforge-gateway项目下的python_sandbox_server子项目。
受影响版本:>= v0.1.0且 < v1.0.2 的所有版本。
修复版本:v1.0.2及更高版本已修复该漏洞。
漏洞的核心原因是python_sandbox_server的RestrictedPython沙箱配置不当,使得攻击者可以绕过安全限制。
但本质上来说,RestrictedPython并不能算作一个沙箱,因为实际上它是一个源到源编译器,通过将用户输入的代码改写成一个被预设好能通过检查的写法,尤其是一些存在危险的写法。
二、复现记录
借助AI搭建了一个简单的脚手架文件。
首先尝试最经典的沙箱逃逸payload:

正常来说,对于用户在RestrictedPython中提交的代码而言,首先由validate_code()调用 compile_restricted_exec 对用户的输入作静态检查,compile_restricted_exec 在解析和编译阶段如果发现违规,则记录到CompileResult.errors字段中。validate_code() 根据CompileResult.errors非空,把错误通过包装成JSON里的validation_errors字段返回。从错误信息也能看出来:它禁止出现_开头的属性访问。所以静态检查会把它们直接标记成非法属性名。这也就是这个payload没有通过的原因。
换句话说,RestrictedPython只要检测到了代码中类似于__class__的写法,就直接标注为危险。但是用来标注危险代码的validate_code()函数是利用字符串查找来审阅代码中的危险内容的。
所以基于这个信息,如果让源码中选择不出现完整的包含__的字面量,反而通过某种方法拼接出,并且能够真实访问到,就能实现绕过这个检查。
例如:
首先对于compile_restricted_exec来说,它没有识别到完整的__class__字面量,所以检查通过。getattr()方法把__cl和ass__拼接到了一起,从而实现了绕过。并且因为getattr()是原始内建,属于python自带的函数,不会经过改写。
我在写上面这段的时候,查了很多资料,注意到了一个和getattr()相近的_getattr_函数,二者属于同一类型,并且调用形态也一样,那么为什么getattr()和_getattr_,前者不会经过管控,而后者会呢?
getattr()是内置函数,不受编译器的管控。而_getattr_ 不是 Python 的内置函数,是 RestrictedPython 自己的约定,属于是在编译期植入的守卫钩子。
了解了这个差异之后回过头看上游源码。
点击展开
1 |
|
在safe_builtins字典中直接暴露了原始内建getattr、setattr和hasattr,没有用RestrictedPython的守卫钩子_getattr_来替换或限制它们。因此就产生了利用__class__构建漏洞的可能。
因此在新版本的源码中,可以看到做了以下改动。
1 | _SAFE_DUNDER_ATTRS = frozenset({...}) # 白名单,只允许安全的 dunder |
引入 _guarded_getattr / _guarded_setattr 守卫,并强制注入。
并且在create_safe_globals()也做了修改
1 | globals_dict = { |
RestrictedPython 编译期会把所有 obj.attr 重写为 _getattr_(obj, 'attr')。修补前,_getattr_ 可能被 RestrictedPython 默认实现或自定义的原始 getattr 覆盖;修补后,自定义守卫被显式注入,且白名单只放行安全的 dunder(如 __str__、__len__ 等),__class__、__subclasses__、__globals__ 等一律拦截。
safe_builtins 中移除了原始 getattr、setattr、hasattr。
1 | def create_safe_globals(self) -> dict[str, Any]: |
validate_code中针对静态检查做了修改
1 | tree = ast.parse(code) |
静态字符串匹配可被 "__cl" + "ass__" 轻易绕过;AST 分析能准确捕获源码中静态写出的 obj.__class__,减少误报和漏报。运行时拼接的字符串虽然能通过静态检查,但会在执行时被 _guarded_getattr 拦截。