eval()本身不是漏洞,但处理不可信输入等于主动打开后门;它默认共享调用者全部命名空间,可执行任意表达式,如__import__('os').system('rm -rf /'),cve-2025-2945即因pgadmin未过滤用户输入导致远程代码执行。

eval() 本身不是漏洞,但用它处理不可信输入等于主动打开后门。 它能执行任意 Python 表达式,一旦参数来自用户、文件或网络,攻击者就能注入 __import__('os').system('rm -rf /') 这类代码,直接接管系统。CVE-2025-2945 就是典型——pgAdmin 因一处未过滤的 eval 调用,导致远程代码执行。
eval() 为什么一用就出事?
根本原因在于它的执行模型:默认共享调用者的全部命名空间。你写 eval("x + 1"),它不仅能访问变量 x,还能调用 open、exec、getattr,甚至通过 __import__ 加载任意模块。
- 传入
globals={}并不能阻断危险操作——__builtins__仍默认存在,eval("__import__('os').getcwd()")照样成功 - 试图用
ast.parse()+ 白名单校验节点类型,极易遗漏边缘 case(比如getattr绕过属性白名单) - 运行时限制(如超时、内存限制)只能缓解 DoS,无法阻止代码注入
ast.literal_eval() 能解决什么问题?
ast.literal_eval() 是唯一被 Python 官方明确推荐的安全替代项,但它只认字面量:int、float、str、list、dict、tuple、None、True、False。遇到任何函数调用、属性访问、运算符,立刻抛 ValueError。
- 安全:输入
"[1, 2, {'a': __import__('os')}]"→ 直接报错,不执行 - 局限:无法处理
"2 * x + 1"这类含变量的表达式 - 适用场景:解析配置、API 返回的 JSON-like 字符串、用户提交的结构化数据
需要计算动态表达式时怎么办?
如果业务确实要支持类似计算器或规则引擎的表达式求值,必须放弃 eval(),转用专用库或自建解析器:
-
simpleeval:开箱即用,默认禁用所有危险操作,支持自定义函数和变量白名单,可设执行超时 -
numexpr:专为 numpy 数组设计,底层 C 实现,性能高且沙箱干净,但仅支持数学运算 - 手写解析器:用
pyparsing或lark定义严格文法,只允许BinOp、Num、Name,且Name.id必须在预设变量列表中(如["x", "y", "pi"])
真要硬上 eval() 的底线是什么?
极少数场景(如内部运维脚本、完全可信环境下的调试工具)若必须用 eval(),以下三点缺一不可:
- 输入必须经过正则或词法分析预检,例如只允许数字、运算符、括号、预设变量名,拒绝任何点号、括号调用、下划线开头标识符
-
globals必须显式传入,且__builtins__设为{}或None;locals只传入明确需要的变量字典,禁止用locals() - 执行前用
compile(expression, "", "eval")预编译并捕获SyntaxError,运行时再用exec替代eval会更危险,绝对不要混用
真正难的不是写出“看起来安全”的 eval 调用,而是持续保证输入来源的可信性——一次日志注入、一个未校验的 API 参数、一段被污染的配置文件,都足以让所有防护形同虚设。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











