如何在不使用eval/exec的安全前提下,为用户提交的Python代码植入自定义sys.settrace追踪逻辑?
如何在不使用eval/exec的安全前提下,为用户提交的Python代码植入自定义sys.settrace追踪逻辑?
我完全理解你的顾虑——直接用eval/exec执行用户提交的代码确实有很大安全隐患,尤其是面对不可信的代码时。不过别担心,我们有几个安全可靠的方案,既能实现sys.settrace的追踪功能,又能避开这些风险。
方案一:临时文件+模块导入法(最直观安全)
核心思路是把用户代码写入临时文件,通过Python的模块导入机制来执行代码,同时在导入前启用你的追踪函数。这种方式完全不需要直接eval/exec字符串,而且能完整捕获代码执行的全流程。
代码示例
import sys import tempfile import importlib.util import os # 先定义你的追踪函数,这里以打印行号和函数名为例 def trace_calls(frame, event, arg): if event == 'line': print(f"正在执行第 {frame.f_lineno} 行,函数:{frame.f_code.co_name}") # 如果你要追踪变量,可以通过frame.f_locals获取当前局部变量 # 比如 print("当前变量:", frame.f_locals) return trace_calls def safe_track_user_code(user_submitted_code): # 创建临时.py文件,写入用户代码 with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp_file: tmp_file.write(user_submitted_code) tmp_file_path = tmp_file.name try: # 启用追踪 sys.settrace(trace_calls) # 导入临时模块,触发代码执行 spec = importlib.util.spec_from_file_location("user_code_module", tmp_file_path) user_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(user_module) finally: # 清理工作:关闭追踪,删除临时文件 sys.settrace(None) os.unlink(tmp_file_path) # 测试用的用户提交代码 user_code = """ def adder(x,y): return x + y result = adder(3,5) print(f"计算结果:{result}") """ safe_track_user_code(user_code)
方案优势
- 完全避开直接执行字符串的风险,符合Python的安全执行规范
- 追踪函数可以完整捕获用户代码执行的每一行、函数调用、变量变化等事件
- 可以配合沙箱机制(比如限制可导入的模块、禁用危险内置函数)进一步提升安全性
方案二:AST语法树修改法(精准控制追踪点)
如果你需要更细粒度的追踪控制(比如只追踪变量赋值、特定函数调用),可以用Python的ast模块解析用户代码的语法树,在指定位置注入追踪逻辑,再编译执行修改后的语法树。这种方式比字符串拼接安全得多,因为能确保语法正确性,避免注入攻击。
代码示例(简化版)
import ast import sys # 自定义AST转换器,在赋值语句前插入追踪逻辑 class TraceInjector(ast.NodeTransformer): def visit_Assign(self, node): # 生成追踪代码的AST节点,这里以打印赋值目标为例 trace_stmt = ast.parse(f"print('追踪赋值:{ast.dump(node.targets[0])}')").body[0] # 返回修改后的节点:先执行追踪,再执行原赋值 return ast.Module(body=[trace_stmt, node], type_ignores=[]) def ast_based_track(user_submitted_code): # 解析用户代码为AST code_tree = ast.parse(user_submitted_code) # 注入追踪逻辑 injector = TraceInjector() modified_tree = injector.visit(code_tree) ast.fix_missing_locations(modified_tree) # 创建隔离的命名空间,限制用户代码的权限 safe_globals = { "__builtins__": {k: v for k, v in __builtins__.__dict__.items() if k not in ["open", "os", "sys"]} # 这里可以进一步过滤掉危险的内置函数 } safe_locals = {} try: # 启用追踪 sys.settrace(trace_calls) # 编译并执行修改后的AST compiled_code = compile(modified_tree, "<user_code>", "exec") exec(compiled_code, safe_globals, safe_locals) finally: sys.settrace(None)
方案优势
- 可以精准控制追踪的位置和逻辑,比如只追踪赋值、函数进入/退出等事件
- 通过AST修改注入逻辑,比字符串拼接更安全,避免语法错误和注入攻击
- 配合隔离命名空间,可以严格限制用户代码的权限
关键安全提醒
无论用哪种方案,都必须做沙箱隔离:
- 使用
RestrictedPython等库创建严格的执行环境,过滤掉危险语法和内置函数 - 限制用户代码的资源使用(比如CPU、内存),防止恶意代码耗尽资源
- 永远不要在生产环境中直接执行未经过滤的用户代码,必须先做语法检查和安全扫描
备注:内容来源于stack exchange,提问作者Anuj Kumar
相关产品推荐
相关产品推荐

