不能用 eval 执行用户输入,因其会直接执行任意代码、无沙箱限制;应优先用 ast.literal_eval 处理安全字面量,必要时用 simpleeval 或自定义 AST 遍历限制运算符和节点。

为什么不能用 eval 执行用户输入
eval 会直接在当前作用域执行任意 Python 代码,哪怕只是传入 "__import__('os').system('rm -rf /')" 这样的字符串,也可能触发系统调用、文件操作或网络请求。它不区分“表达式”和“语句”,也不限制内置函数访问,本质上不是沙箱——只是把字符串当代码扔进解释器里跑一遍。
真实场景中,用户可能故意输入 "2 + (lambda: exec('print(1)'))()" 绕过简单关键词过滤;或者利用 getattr、__builtins__ 等反射机制逃逸限制。靠字符串黑名单/白名单基本防不住。
用 ast.literal_eval 处理安全子集
ast.literal_eval 只允许基础字面量:数字、字符串、元组、列表、字典、布尔值、None。它不会执行函数调用、属性访问、运算符重载,也不会触发任何副作用。
- 适合场景:解析用户提交的配置项、JSON-like 参数(如
"[1, 2, {'x': 3.14}]") - 不支持:变量名、算术运算(
"2 + 3")、比较("5 > 3")、函数调用("len([1,2])") - 错误类型:输入非法时抛出
ValueError或SyntaxError,不是Exception的宽泛捕获
示例:
立即学习“Python免费学习笔记(深入)”;
import ast
try:
result = ast.literal_eval("[1, 2, {'a': True}]")
except (ValueError, SyntaxError) as e:
# 安全失败,无副作用
print("Invalid literal:", e)
需要计算表达式?用 simpleeval 或自定义 AST 遍历
如果必须支持 "2 * x + 1" 这类带变量和运算的表达式,ast.literal_eval 不够用,但又不想自己从头写解析器,推荐轻量库 simpleeval:
- 默认禁用所有函数和属性访问,只开放基础运算符和常量
- 可安全注入变量:
se.eval("a + b", names={"a": 10, "b": 20}) - 能限制最大执行步数、嵌套深度、字符串长度,防止穷举或栈溢出
- 不支持
lambda、import、exec、下标以外的属性访问(如obj.method)
不用第三方库时,可基于 ast.Expression + 白名单节点遍历实现最小计算器,重点拦截 ast.Call、ast.Attribute、ast.Subscript(除非明确允许数组索引)等危险节点。
绕过沙箱的常见手法与应对
即使用了 simpleeval 或自定义 AST,也要警惕用户构造边缘输入:
-
"1 if True else __import__('sys').exit()"→ 检查是否允许条件表达式,或禁用ast.IfExp -
"'a' * 10**6"→ 限制字符串生成长度,或在遍历时统计总字符估算量 -
"[i for i in range(10**6)]"→ 禁用列表推导式(ast.ListComp),或限制循环模拟次数 - 利用大整数幂运算耗尽 CPU:
9**9**5→ 在访客节点时检查数字大小和运算符组合
真正安全的表达式求值没有银弹。越接近通用计算能力,防御成本越高。多数业务场景其实只需要 ast.literal_eval 或固定几个运算符的白名单——先想清楚“用户到底需要算什么”,再决定沙箱复杂度。










