本文目录导读:

- 基础限制:
exec+ 受限命名空间(不安全) - 中级保护:
eval+ AST限制(适用于简单表达式) - 严格限制:
RestrictedPython(推荐用于简单场景) - 进程级隔离:
subprocess+ 资源限制(推荐) - 容器级隔离:Docker(最安全)
- 专业方案:PyPy的沙箱(已停止维护,但理念值得参考)
- 安全建议
- 实际工程选择
在Python中安全运行沙箱代码是一个复杂的课题,因为Python本身的设计目标是灵活和动态,而不是安全隔离,没有任何方案能做到100%安全,但可以根据需求选择不同级别的防护措施。
以下是几种常见的Python沙箱方案,按安全等级从低到高排列:
基础限制:exec + 受限命名空间(不安全)
def exec_in_sandbox(code, allowed_builtins=None):
# 创建一个受限的全局命名空间
restricted_globals = {
"__builtins__": allowed_builtins or {},
"__name__": "__sandbox__"
}
try:
exec(code, restricted_globals)
return restricted_globals
except Exception as e:
return {"error": str(e)}
问题:exec本身不是安全边界,很容易通过各种方式逃逸:
# 可以轻松绕过 code = """ ().__class__.__bases__[0].__subclasses__() """
中级保护:eval + AST限制(适用于简单表达式)
import ast
def safe_eval(expr, allowed_names={}):
# 只允许安全的AST节点
SAFE_NODES = {
ast.Expression, ast.Str, ast.Num, ast.Name, ast.Attribute,
ast.Load, ast.BinOp, ast.UnaryOp, ast.Add, ast.Sub,
ast.Mult, ast.Div, ast.Mod, ast.Pow, ast.Constant
}
try:
tree = ast.parse(expr, mode='eval')
for node in ast.walk(tree):
if type(node) in SAFE_NODES:
continue
raise ValueError(f"Unsafe expression: {type(node)}")
return eval(compile(tree, '<string>', 'eval'),
{"__builtins__": {}}, allowed_names)
except Exception as e:
raise ValueError(f"Evaluation error: {e}")
适用范围:只能用于非常简单的数学表达式或属性访问。
严格限制:RestrictedPython(推荐用于简单场景)
from RestrictedPython import compile_restricted, safe_globals
from RestrictedPython.Guards import guarded_iter_unpack_sequence
def restricted_exec(code, local_vars=None):
try:
compiled_code = compile_restricted(code, '<string>', 'exec')
restricted_globals = {
'__builtins__': {
'True': True,
'False': False,
'None': None,
'int': int,
'float': float,
'str': str,
'bool': bool,
'list': list,
'dict': dict,
'tuple': tuple,
'len': len,
'range': range,
'min': min,
'max': max,
'abs': abs,
'print': print, # 可选的输出函数
},
'_getattr_': safe_getattr,
'_write_': guarded_write,
'_print_': guarded_print,
}
restricted_locals = local_vars or {}
exec(compiled_code, restricted_globals, restricted_locals)
return restricted_locals
except Exception as e:
return {"error": str(e)}
限制:不能使用import、文件操作、网络访问等。
进程级隔离:subprocess + 资源限制(推荐)
import subprocess
import resource
import os
import signal
def run_sandboxed_code(code, timeout=5, memory_limit=100*1024*1024):
# 将代码写入临时文件或通过管道传递
script = f"""
import sys
import resource
# 设置资源限制
def set_limits():
# CPU时间限制
resource.setrlimit(resource.RLIMIT_CPU, (1, 1))
# 内存限制
resource.setrlimit(resource.RLIMIT_AS, ({memory_limit}, {memory_limit}))
# 文件大小限制
resource.setrlimit(resource.RLIMIT_FSIZE, (1024, 1024))
# 进程数限制
resource.setrlimit(resource.RLIMIT_NPROC, (10, 10))
set_limits()
# 执行用户代码
try:
exec({repr(code)})
except Exception as e:
print(f"Error: {{e}}", file=sys.stderr)
sys.exit(1)
"""
try:
# 在新进程中运行
result = subprocess.run(
['python3', '-c', script],
capture_output=True,
timeout=timeout,
text=True
)
return {
"stdout": result.stdout,
"stderr": result.stderr,
"returncode": result.returncode
}
except subprocess.TimeoutExpired:
return {"error": "Timeout"}
except Exception as e:
return {"error": str(e)}
优点:
- 进程级别的隔离
- 可以通过资源限制防止DoS攻击
- 用户代码无法影响主程序
容器级隔离:Docker(最安全)
import docker
import tempfile
import os
def run_in_docker(code, image="python:3.9-slim", timeout=30):
client = docker.from_env()
# 创建临时文件存放代码
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
f.write(code)
temp_path = f.name
try:
# 运行容器
container = client.containers.run(
image=image,
command=f"python /code/script.py",
volumes={temp_path: {'bind': '/code/script.py', 'mode': 'ro'}},
mem_limit="100m",
cpu_quota=50000, # 限制CPU使用
network_mode="none", # 禁用网络
read_only=True, # 只读文件系统
remove=True,
detach=True
)
# 等待完成
result = container.wait(timeout=timeout)
logs = container.logs(stdout=True, stderr=True, tail=50)
return {
"exit_code": result['StatusCode'],
"output": logs.decode('utf-8')
}
except docker.errors.ContainerError as e:
return {"error": str(e)}
finally:
os.unlink(temp_path)
专业方案:PyPy的沙箱(已停止维护,但理念值得参考)
PyPy曾有一个沙箱实现,使用独立的子解释器运行代码:
# 概念代码(需要PyPy沙箱支持)
from pypy_sandbox import Sandbox
sandbox = Sandbox()
sandbox.run("print('Hello from sandbox')")
安全建议
- 明确需求:确定需要开放哪些功能(算术运算、字符串处理、文件访问等)
- 最小权限原则:只开放必需的功能
- 多层防御:组合使用多种限制方法
- 输入验证:预处理用户输入,过滤危险字符
- 超时机制:设置代码执行时间限制
- 资源限制:内存、CPU、文件大小等
实际工程选择
- 简单表达式求值:
ast.literal_eval或RestrictedPython - 运行用户代码:
subprocess+ 资源限制(推荐) - 需要网络/文件访问:Docker容器(最安全)
- 大规模生产环境:云厂商提供的Serverless服务(AWS Lambda, Cloud Functions等)
记住:没有任何纯Python的沙箱方案是绝对安全的,对于高安全性需求,始终使用操作系统级别的隔离(容器、虚拟机)或专业的沙箱服务。