如何安全加载Python游戏模组并限制import、exec、eval等危险操作
游戏模组安全执行方案改进问题
问题背景
我开发了一款带模组框架(供模组开发者使用的API)的游戏,目录结构如下:
Game ├─ Info │ └─ about.txt ├─ Packages │ └─ mod1 │ ├─ main.txt │ └─ data │ └─ credits.txt └─ run.py
模组的main.txt内容示例:
import requests # 不允许 userpassword = "peussweurdo" open("Game/Info/About.txt", "r") # 允许 open("Game/Info/About.txt", "w") # 不允许 open("data/credits.txt") # 允许 open("C:/Users/<user>/passwords") # 不允许
核心需求
- 确保模组无法使用
import、exec和eval - 允许模组合法访问游戏相关资源
当前尝试的代码
import os mod_path = "Packages/mod1/main.txt" mod_data = open(mod_path, "r").read() def safe_open(path, mode="r"): if path.startswith("Game") and mode != "r": raise OSError("Accessing game files is read-only") else: return open("%s/%s" % (mod_path, path), mode) methods = { "open": safe_open } newly_defined = {} exec(mod_data, methods, newly_defined)
存在的问题
当前方案会自动将所有内置函数加入methods字典,导致模组仍能调用import、exec和eval,不符合安全需求。
改进方案
要解决这个问题,需要手动构建干净的全局命名空间,只保留允许模组使用的内容,完全隔离内置的危险函数。具体实现如下:
1. 构建严格的全局执行环境
不依赖默认的内置函数集合,手动定义模组能访问的变量和函数,明确排除__import__、exec、eval等危险项:
import os from pathlib import Path # 获取模组所在根目录与游戏根目录 mod_file_path = "Packages/mod1/main.txt" mod_root = Path(mod_file_path).parent.resolve() game_root = Path(__file__).resolve().parent mod_data = open(mod_file_path, "r").read() def safe_open(path, mode="r"): target_path = Path(path).resolve() # 规则1:访问Game目录文件仅允许只读 if game_root in target_path.parents or target_path == game_root: if mode != "r": raise OSError("Game目录文件仅支持只读访问") if not target_path.exists(): raise FileNotFoundError(f"文件不存在:{path}") return open(target_path, mode) # 规则2:仅允许访问模组自身目录下的文件 if mod_root in target_path.parents or target_path == mod_root: return open(target_path, mode) # 规则3:禁止访问其他外部路径 raise OSError(f"禁止访问路径:{path}") # 构建仅包含允许内容的全局命名空间 safe_globals = { # 按需添加允许的基础内置类型 "str": str, "int": int, "float": float, "bool": bool, "list": list, "dict": dict, "tuple": tuple, # 替换为安全的open函数 "open": safe_open, # 直接禁用import功能 "__import__": lambda *args: raise ImportError("禁止导入任何模块") } # 使用严格环境执行模组代码 exec(mod_data, safe_globals, {})
2. 额外语法层面的安全校验
可以配合ast模块提前扫描模组代码,直接拦截包含import、exec、eval的代码:
import ast def check_safe_code(code): tree = ast.parse(code) for node in ast.walk(tree): # 拦截import语句 if isinstance(node, (ast.Import, ast.ImportFrom)): raise SyntaxError("禁止使用import语句") # 拦截exec/eval调用 if isinstance(node, ast.Expr) and isinstance(node.value, ast.Call): if isinstance(node.value.func, ast.Name) and node.value.func.id in ["exec", "eval"]: raise SyntaxError("禁止使用exec/eval函数") return tree # 先校验代码安全性,再执行 check_safe_code(mod_data) exec(mod_data, safe_globals, {})
通过执行环境隔离+语法前置校验的双重机制,既能彻底阻止模组使用危险功能,也能保证合法的资源访问需求。
内容的提问来源于stack exchange,提问作者Dimsey
相关产品推荐
相关产品推荐

