eval() 默认不安全,因其直接执行任意python表达式字符串,可绕过内置限制调用危险函数或模块,即使删除__builtins__仍可能通过对象链复活os等模块。

eval() 为什么默认不安全
因为 eval() 会直接执行任意 Python 表达式字符串,只要字符串里能构造出合法语法,它就照单全收。比如 "__import__('os').system('rm -rf /')" 这种在交互式环境里一跑就完蛋。
常见错误现象:本地测试时只传数字或简单算式,上线后用户输入 "2 + 3" 没问题,但有人提交 "(lambda: __import__('subprocess').run(['id']))()",权限没限制的话,服务就可能被当跳板。
- 它不区分“计算”和“调用”,
eval("open('/etc/passwd').read())合法且危险 - 哪怕删掉
__builtins__,仍可能通过对象链绕过,比如已有变量a = [],那eval("a.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].system(...)")就可能复活危险模块 - Python 3.12+ 对某些内置属性做了更严管控,但不等于修复了根本问题
哪些场景下真得用 eval(),又该怎么压风险
极少数情况绕不开:比如科学计算器后端、DSL 解析器原型、内部运维工具(输入可控、运行环境隔离)。
实操建议不是“加固 eval()”,而是把攻击面缩到最小:
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 用
ast.literal_eval()替代——它只认基本字面量:int、float、str、list、dict、tuple、None、True、False,遇到函数调用或属性访问直接抛ValueError - 如果非得支持简单运算(如
"2 * x + 1"),先用ast.parse()+ 自定义ast.NodeVisitor白名单校验:只允许ast.BinOp、ast.Num、ast.Name,且ast.Name.id必须在预设变量白名单里(如["x", "y"]) - 绝对不要传入含用户数据的
globals或locals,尤其别写eval(expr, globals(), locals())—— 这等于把当前作用域全开放
比 eval() 更靠谱的替代方案有哪些
多数时候你真正要的是“安全求值”,不是“执行 Python 代码”。选对工具比硬防 eval() 更省事。
- 数学表达式:用
simpleeval库,它默认禁用所有危险操作,支持自定义函数和变量,且可设超时;numexpr适合 numpy 数组批量计算,底层用 C 实现,快且沙箱干净 - 配置/规则解析:YAML/JSON +
pydantic验证,比手写表达式更易维护;规则引擎如jsonpath-ng或trafaret处理嵌套结构查询 - 动态逻辑:用
operator模块组合基础运算符,或封装成策略类,靠多态分发,而不是拼字符串再 eval
调试 eval() 相关报错时容易忽略的点
报错信息常误导人。比如 eval("1 + ") 报 SyntaxError 是语法问题;但 eval("__import__('sys').exit()") 报 SystemExit,看起来像程序退出,其实已执行成功。
-
NameError不代表安全——可能是你删了__builtins__导致len找不到,但getattr还在,照样能挖 - 用
timeout包裹eval()(如signal.alarm)在 Windows 上无效,得改用multiprocessing.Process+join(timeout)杀子进程 - 日志里记录被 eval 的原始字符串时,务必先做脱敏,否则日志文件本身可能被注入恶意 payload
真正难的不是让 eval() 跑起来,是证明它在所有输入组合下都跑不出沙箱。绝大多数业务需求,都有更窄、更稳、更易审计的解法。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










