PHP 依然是 Web 后端的主力语言之一,围绕它的漏洞讨论也从未停止。与其追逐"最新 CVE 编号",不如先弄清楚漏洞究竟出在哪一层——因为大部分被贴上"PHP 漏洞"标签的问题,其实和 PHP 语言本身没什么关系。
先分清三类"PHP 漏洞"
第一类是 PHP 解释器或运行时的缺陷,比如历史上的 PHP-FPM 下溢(CVE-2019-11043)、Windows 下 CGI 模式的参数注入(CVE-2024-4577)。这类漏洞影响面取决于部署方式,升级版本通常就能解决。
第二类是框架和 Composer 依赖里的问题,比如某个流行的序列化库、模板引擎或管理后台组件爆出反序列化或任意文件读取。这类漏洞的特点是传播广、修复滞后。
第三类是业务代码自身的问题,也是实际渗透中最常见的入口。SQL 拼接、文件包含、权限校验缺失,几乎每次代码审计都能遇到。
写这篇文章不是给你一份 CVE 清单,而是梳理这三类问题的典型形态和可落地的防御动作。具体编号和影响版本,请以 PHP 官方发布公告和权威漏洞库为准。
反序列化依然是重灾区
PHP 的 unserialize() 一旦作用在用户可控数据上,就等于把对象构造流程交给了攻击者。魔术方法 __destruct、__wakeup、__toString 会被自动触发,配合项目里现成的类,很容易拼出一条利用链。
更隐蔽的是 phar:// 流包装器:只要代码里存在对 phar:// 路径的文件操作函数(file_exists、file_get_contents、getimagesize 等),即使没有显式调用 unserialize(),也可能触发 PHAR 元数据的反序列化。这个特性常被用来绕过只检查上传文件内容、不检查路径的防护。
防御上,能不用就不用。缓存、队列、配置传递等场景,优先换成 json_encode / json_decode。如果确实必须用 PHP 序列化格式,至少限制可反序列化的类:
$data = unserialize($input, ['allowed_classes' => ['App\Dto\Order']]);
另外,对上传目录做严格的路径校验,不要让用户输入直接进入文件操作函数的第一个参数。
松散比较和类型转换的坑
PHP 的 == 在比较字符串和数字时会做类型转换,"0e12345" == "0e67890" 会判定为相等,这类问题在哈希比较、验证码校验、支付金额判断中反复出现。
比较敏感值一律用 hash_equals():
if (!hash_equals($expectedToken, $userToken)) {
throw new \RuntimeException('invalid token');
}
金额、数量这类数值,入库前就做类型声明和校验,不要指望运行时的隐式转换。PHP 8 收紧了部分比较规则,但历史代码里遗留的松散比较仍然值得逐处排查。
文件包含与路径穿越
include、require、readfile 这类函数一旦接收用户输入,就可能变成任意文件读取甚至代码执行。常见的错误写法是把语言、模板名直接拼进路径。
正确做法是白名单映射,而不是过滤 ../:
$map = ['zh' => 'zh-CN.php', 'en' => 'en-US.php'];
$lang = $map[$_GET['lang'] ?? 'zh'] ?? 'zh-CN.php';
include __DIR__ . '/lang/' . $lang;
白名单的好处是攻击者无论构造什么输入,最终都只能落到你预设的文件上。
命令执行与参数注入
system、exec、shell_exec、proc_open 是另一个高频风险点。除了明显的命令拼接,还要注意参数注入:即使你用了 escapeshellarg(),如果参数被传给了本身支持执行命令的程序(比如某些图像处理、压缩工具),依然可能被利用。
能不用 shell 就不用。图像处理用 GD 或 Imagick 的 API,压缩用 ZipArchive,实在要调用外部程序,把参数固定成常量或严格白名单,不要让用户输入参与命令行的任何部分。
依赖和运行时版本管理
业务代码写得再干净,一个过期的 Composer 依赖就能把门打开。建议把这几件事变成流水线的一部分:
- 用
composer audit检查已知漏洞,纳入 CI; - 锁定
composer.lock并定期更新,不要长期停在旧版本; - 关注 PHP 官方支持周期,停止维护的版本不要继续跑在生产环境;
- 生产环境关闭
display_errors,把expose_php设为 Off,减少信息泄露。
一份可落地的自查清单
- 全局搜索
unserialize(、phar://、include $、eval(、system(等危险调用,逐个确认输入来源; - 所有 SQL 走预处理语句,禁止字符串拼接;
- 上传文件校验 MIME、扩展名、大小,存储目录禁止执行权限;
- Session 使用
session_regenerate_id(true)防固定攻击,Cookie 开启HttpOnly、Secure、SameSite; - 密码哈希使用
password_hash(),不要自己写加密逻辑; - 错误日志集中收集,但不要记录明文密码和完整身份证号。
安全问题很少靠一次大修解决,更多是靠这些不起眼的习惯累积起来。与其等下一个漏洞公告,不如现在就把上面几条过一遍。