PHP 8.2 的 realpath() 对路径遍历更严格,遇非法路径返回 false,但不能替代白名单;必须显式校验返回值、优先用 basename() 拼接预设目录,或采用白名单映射,禁止用户输入直接参与路径拼接。

PHP 8.2 的 realpath() 对路径遍历更严格,但不能替代白名单
PHP 8.2 中 realpath() 在遇到非法路径(如含 ../ 且超出根目录)时会直接返回 false,而 PHP 7.4 可能仍返回一个“看似合理”的路径(尤其在符号链接或挂载点边界处)。但这不意味着你只要加一行 realpath($user_input) 就安全了。
常见错误是:先拼路径再 realpath(),比如 $path = __DIR__ . '/uploads/' . $_GET['file']; realpath($path)——攻击者传 file=../../etc/passwd,拼出来的是 /var/www/uploads/../../etc/passwd,realpath() 虽返回 false,但你没检查返回值就继续用了。
- 必须显式判断
realpath()返回值是否为字符串,不是false或null - 校验前先用
basename()提取文件名,再拼到预设目录下,比依赖realpath()更可靠 - 若业务真需支持子目录(如
?dir=reports),应使用白名单映射:$dirs = ['reports' => 'reports/', 'logs' => 'logs/'];,而非放行任意路径片段
PHP 8.2 默认禁用 allow_url_include,但伪协议攻击依然有效
PHP 7.4 和 8.2 都默认关闭 allow_url_include,所以 include('http://...') 不会执行。但攻击者转而用 php://filter 或 data:// 读源码,这类伪协议不受该配置影响。
典型漏洞写法:include($_GET['page'] . '.php'); → 攻击者传 page=php://filter/convert.base64-encode/resource=index,PHP 8.2 一样能成功返回 base64 编码的源码。
立即学习“PHP免费学习笔记(深入)”;
- 所有动态
include/require必须绑定绝对路径,且路径来源只能是硬编码或白名单数组 - 禁止任何用户输入参与文件路径拼接,哪怕加了
.php后缀或basename() - 上线前用
grep -r "include.*\$_" .扫描全项目,重点盯$_GET、$_POST、$_REQUEST
PHP 8.2 的 open_basedir 行为更激进,路径校验失败会直接报错而非静默降级
在 PHP 7.4 中,若 open_basedir 限制了 /var/www,而你尝试 file_get_contents('/tmp/cache.json'),它可能只发 warning 并返回 false;PHP 8.2 则大概率抛出 RuntimeException 或 Warning: file_get_contents(): open_basedir restriction in effect,中断执行。
这看似更安全,实则掩盖了真正问题:你本就不该让代码跑到 /tmp 去读——上传临时文件、缓存、日志等路径应在部署时明确约定,并写死在配置里。
- 把所有外部路径(上传目录、缓存目录、日志目录)定义为常量,例如
define('UPLOAD_DIR', __DIR__ . '/public/uploads'); - 在
php.ini中设置open_basedir = /var/www:/tmp(仅加必要路径),避免一刀切锁死所有临时操作 - 不要在运行时动态修改
open_basedir,ini_set('open_basedir', ...)在 PHP 8.2 中多数情况已失效
符号链接 + file_exists() 在 PHP 8.2 下行为更危险
file_exists() 在 PHP 7.4 中对符号链接目标不可达时,可能返回 true(只检查链接本身存在);PHP 8.2 默认检查链接指向的真实路径是否存在且可访问,若目标被 open_basedir 拦截或权限不足,就返回 false——这会让旧逻辑误判“文件不存在”,跳过后续校验,反而绕过防护。
比如你写了:if (file_exists($user_path) && is_readable($user_path)) { include($user_path); },攻击者构造一个指向 /etc/passwd 的软链,PHP 7.4 过了 file_exists,但 is_readable 失败;PHP 8.2 直接卡在 file_exists 返回 false,整个 if 块跳过——你以为安全了,其实只是没走到漏洞触发点。
- 永远不要用
file_exists()做安全校验,它不是访问控制开关 - 路径合法性必须由白名单或
basename()+ 预设目录双重保障 - 若必须处理符号链接,显式调用
is_link()+readlink()检查目标,再做路径规范化
realpath() 就万事大吉”,却忘了它只解决路径归一化,不解决权限、白名单、执行上下文这三个根本问题。



















