eval() 本质不安全,白名单过滤无效;应禁用eval,改用json_decode、filter_var或symfony/expression-language等安全替代方案,必要时用AST解析严格校验表达式结构。

eval() 本身无法安全,白名单过滤不是补救措施而是掩耳盗铃
PHP 的 eval() 执行任意字符串为 PHP 代码,只要输入可控,就等于把服务器控制权交给调用方。所谓“严格白名单过滤”,在 eval() 场景下本质是幻觉——正则或字符串检查拦不住绕过(比如用 chr(97).chr(112).chr(112) 拼出 app,再拼接函数名),更挡不住动态变量、花括号语法、${...} 表达式注入等真实攻击路径。
常见错误现象:
– 写了个“只允许数字和加减乘除”的正则,结果用户传入 "${@system('id')}" 成功执行命令
– 用 in_array() 检查函数名白名单,但没禁用 call_user_func 或 array_map 间接调用
– 过滤掉 system 却忘了 exec、shell_exec、`ls`、proc_open
- 真正安全的替代方案:不用
eval()—— 改用json_decode()解析结构化数据,或用filter_var()+ 类型校验处理表达式输入 - 如果必须动态计算数学表达式,用现成库如
symfony/expression-language,它不走eval(),而是解析 AST 执行 - 所有“过滤后 eval”逻辑,都应视为高危代码,上线前必须进安全审计清单
真要硬上 eval(),必须剥离全部 PHP 上下文
如果你继承了遗留系统、无法替换又必须保留 eval 行为(极少数场景,如内部 DSL 调试工具),唯一能压低风险的做法,是让 eval() 只能接触一个完全隔离、无副作用的变量空间,且禁止任何语言结构扩展。
使用场景:
– 仅限本地开发环境下的配置模板渲染(非 Web 请求入口)
– 输入来源绝对可信(如硬编码字符串、CI 环境变量,且不含用户输入)
立即学习“PHP免费学习笔记(深入)”;
- 永远不要传入
$GLOBALS、$_SERVER、$_POST等超全局变量到eval()作用域 - 显式传入空作用域:
eval('return ' . $expr . ';');比eval($expr);少一层失控风险(至少强制 return) - 用
ini_set('disable_functions', 'exec,system,passthru,shell_exec,proc_open,popen')配合open_basedir限制,但这只是辅助,不能替代逻辑层防护 - PHP 8.1+ 可配合
pcov或自定义 opcache hook 记录eval()调用栈,用于事后追溯
白名单过滤 eval 输入?别信正则,信 AST 解析
想靠 preg_match('/^[0-9+\-*/().\s]+$/', $expr) 拦住危险表达式?它连 1+2;phpinfo() 都拦不住(分号后语句仍会执行)。真正能约束表达式的,只有解析其语法结构。
参数差异:
– 正则匹配:表面字符合规,实际可嵌套任意 PHP 结构
– AST 解析(如 nikic/php-parser):能确认是否仅为 Expr_BinaryOp_Plus 等安全节点,拒绝 Expr_FuncCall、Expr_Variable、Scalar_String 中含动态内容
- 用
PhpParser\ParserFactory::create(ParserFactory::PREFER_PHP7)解析输入,遍历Stmt_Expression下的Expr节点 - 只允许
Expr_BinaryOp_*、Scalar_LNumber、Scalar_DNumber、Scalar_String(且值不含${、{、;) - 禁止任何
Expr_Array、Expr_Closure、Expr_Ternary—— 它们可能引入变量引用或副作用 - 性能影响明显:一次 AST 解析比正则慢 10–50 倍,别在高频请求路径用
替代 eval 的三个务实选择(按优先级排序)
别纠结“怎么安全地 eval”,直接换掉。大多数声称需要 eval() 的业务,其实只需要其中一种能力:计算、映射、或模板填充。
- 纯数学表达式 → 用
symfony/expression-language:(price * quantity) - discount这种写法它原生支持,底层不碰eval() - 字段映射/条件判断 → 提前定义规则数组:
['status' => ['map' => ['1' => 'active', '0' => 'inactive']]],运行时查表,不解释字符串 - 模板类需求 → 用
league/plates或twig/twig,它们编译为 PHP 文件但绝不eval()用户模板,沙箱机制更可控
最容易被忽略的一点:很多团队把“用户可配公式”功能塞进 eval(),其实数据库里存的是字段名和操作符,运行时拼成数组结构再交给 expression-language 执行——这一步抽象,就是安全边界的起点。











