eval() 和 exec() 直接解析用户输入极危险,因可执行任意代码;应优先用 ast.literal_eval() 解析字面量,或在受限作用域中白名单函数,复杂场景用 simpleeval 等专用库。

为什么 eval() 和 exec() 是危险的
直接用 eval() 或 exec() 解析用户输入的字符串,等于把 Python 解释器的控制权交出去。哪怕只传入 "__import__('os').system('rm -rf /')" 这种字符串,也能触发任意系统命令。真实场景中,攻击者常通过构造恶意属性链(如 ().__class__.__mro__[1].__subclasses__())绕过简单关键词过滤。
常见错误现象包括:服务端被远程执行命令、敏感配置泄露、内存耗尽(如递归构造超长表达式)、甚至反序列化漏洞联动。
- 即使加了
if "import" not in s:这类白名单检查,也无法阻止利用内置对象反射调用 -
eval("1+1")看似安全,但若输入来自表单、URL 参数或日志字段,就完全不可信 - Python 3.12+ 对
ast.literal_eval()的限制更严格,但老版本仍可能被构造特殊浮点字面量绕过(如eval("1e1000")导致内存爆炸)
用 ast.literal_eval() 安全解析基础数据结构
ast.literal_eval() 只允许解析 Python 字面量:数字、字符串、元组、列表、字典、布尔值和 None。它不执行函数调用、不访问属性、不触发任何副作用,是处理配置、JSON-like 字符串的首选。
使用场景:读取用户提交的 JSON 格式参数、解析配置文件片段、校验前端传来的数组或字典结构。
- 支持嵌套结构:
ast.literal_eval("[{'a': 1}, (2, 3), 'hello']")返回对应 Python 对象 - 拒绝非法输入:
ast.literal_eval("__import__('sys')")抛出ValueError - 注意类型限制:无法解析
set([1,2])或bytes(b'x')—— 它们不是字面量语法 - 空格和换行不影响解析,但必须是合法 Python 字面量格式,不能是纯 JSON(如键名不加引号会报错)
需要动态执行逻辑时,用受限作用域 + 白名单函数
如果业务确实需要运行简单表达式(比如规则引擎里的 "price > 100 and status == 'active'"),不能硬上 eval(),而应显式定义可调用函数集,并禁用全局/局部命名空间中的危险对象。
实操建议:
- 用空字典初始化
globals和locals,再手动注入白名单函数:safe_dict = {"len": len, "max": max, "min": min, "abs": abs} - 禁止访问内置模块:确保
__builtins__不在作用域中,或显式设为{} - 设置最大递归深度和超时(需配合
signal.alarm或子进程隔离,单纯ast检查无法防死循环) - 对输入做 AST 静态检查:用
ast.parse()遍历节点,拒绝Call、Attribute、Name(除白名单变量外)等节点类型
更复杂需求请交给专用库或沙箱
一旦涉及变量绑定、作用域管理、异步支持或性能要求,手写安全 eval 就容易漏掉边界情况。这时候该让专业工具接手。
推荐方案:
-
simpleeval:轻量、可扩展、默认禁用危险操作,支持自定义函数和命名空间:SimpleEval(names={"x": 42}).eval("x * 2") -
restrictedpython:编译期重写 AST,生成真正受限字节码,适合高安全等级场景(如 SaaS 多租户规则引擎) - 避免用
numexpr或pandas.eval()处理非数值表达式——它们专为数组计算优化,不提供通用代码安全防护 - 绝对不要尝试用正则“过滤危险词”后放行
eval()——正则无法覆盖 Python 语法的所有变体,这是已知的反模式
真正难的不是写一个能跑通的解析器,而是穷举所有绕过路径。生产环境里,宁可多一层进程隔离(如用 subprocess.run() 启动独立解释器),也不要妥协作用域控制。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











