eval 本质上不可安全使用,因其设计放弃输入边界;替代方案包括结构化解析、专用表达式引擎、沙箱模板及白名单函数调用;仅当输入完全可信且满足多重防护条件时才可临时容忍。

根本上,eval 不应被“安全使用”——它不是可加固的工具,而是设计上就放弃输入边界的执行机制。所谓“安全执行上下文”,无法修复 eval 的本质缺陷:只要输入含不可信成分,就等于把 PHP 解析器的控制权交出去。
为什么限制命名空间或清空 globals 仍不安全
很多人尝试用 eval($code, ['__builtins__' => null], $safe_vars)(PHP)或 Python 中传入精简的 globals/locals 来“隔离环境”,但这只是幻觉:
- PHP 中即使禁用
__builtins__,仍可通过${"system"}("id")、call_user_func("exec", "id")或反射类绕过 - Python 中删掉
__builtins__后,攻击者可用().__class__.__mro__[1].__subclasses__()挖掘危险类,再调用os.system - 变量名本身可被动态构造(如
${"sy"."stem"}),白名单校验极易失效
真正可行的替代路径
业务需要的从来不是“执行任意字符串”,而是完成某类确定任务。应直接替换 eval,而非给它套壳:
-
解析配置或表达式:用
json_decode($input, true)(结构化数据)或symfony/expression-language(支持白名单函数的 AST 解析器) -
数学计算:用
filter_var($expr, FILTER_SANITIZE_NUMBER_FLOAT)清洗后转floatval(),或引入mpdf/expression等专用表达式引擎 - 模板渲染:改用 Twig、Blade 等沙箱化模板引擎,它们默认禁用 PHP 代码执行,只暴露安全的过滤器和函数
-
动态函数调用:用白名单数组映射,例如
$allowed = ['date' => 'date', 'strlen' => 'strlen']; $func = $allowed[$user_func] ?? null; $func ? $func($arg) : die();
若继承遗留系统、短期内无法移除 eval
必须满足全部以下条件,才可视为风险可控(否则请立即排期重构):
- 输入来源为硬编码字符串或 CI 构建时注入的只读环境变量,零用户输入、零网络请求、零文件读取
- 执行前强制加
return前缀,如eval('return ' . $expr . ';'),避免多语句注入 - 在 php.ini 中全局禁用高危函数:
disable_functions = exec,system,shell_exec,passthru,proc_open,popen - 启用
open_basedir限制文件访问范围,并配合php-opcache日志记录所有 eval 调用栈用于审计
不复杂但容易忽略:安全不在“怎么围住 eval”,而在“为什么非用它不可”。砍掉 eval,往往比加固它更省事、更可靠。











