必须校验$_get或$_post路径参数,禁用allow_url_include,用正则白名单、realpath()归一化、stream_resolve_include_path()和open_basedir多层防护。

文件包含前必须校验 $_GET 或 $_POST 传入的路径参数
PHP 的 include、require 一旦直接拼接用户输入,就等于把服务器文件系统交给攻击者翻查。真实漏洞场景里,常见的是用 page=about.php 这类参数动态加载内容,但没过滤 ..、/、%00 等字符,导致读取 /etc/passwd 或执行恶意代码。
实操建议:
- 绝不直接使用
include $_GET['page']这类写法 - 路径参数只允许小写字母、数字、下划线、短横线,用正则严格白名单匹配:
preg_match('/^[a-z0-9_\-]+\.php$/', $page) - 若需支持多级目录(如
admin/user/list),先str_replace掉所有..和开头/结尾斜杠,再realpath()解析后检查是否落在预设根目录内 - 禁用
allow_url_include=1(PHP 配置项),避免远程包含
basename() 不足以防御路径遍历
很多人以为用 basename($_GET['file']) 就能防住 ../../etc/passwd —— 实际上它只截取最后一段,对 foo.php%00../../etc/passwd 这类空字节截断完全无效(尤其在老版本 PHP 中),且不处理 URL 编码绕过。
实操建议:
-
basename()只适合清理已知安全的本地路径,不能用于用户输入 - 真正校验前,先用
urldecode()多次解码,再做字符串清理 - 用
pathinfo($file, PATHINFO_EXTENSION)单独提取扩展名,确保是php或白名单内的类型,禁止空扩展或双扩展(如shell.jpg.php) - Windows 下还要额外过滤
\、:$、CON等设备名
用 stream_resolve_include_path() 替代硬编码路径拼接
手动拼接 ./templates/' . $_GET['tpl'] . '.php' 很容易漏掉边界情况;而 stream_resolve_include_path() 会按 include_path 配置搜索,天然规避相对路径穿越,还能配合 set_include_path() 锁死可包含范围。
实操建议:
- 提前设置可信目录:
set_include_path('/var/www/app/includes'); - 用
$resolved = stream_resolve_include_path($filename);获取绝对路径,再判断是否以预设根目录开头 - 如果返回
false,说明文件不在白名单路径中,直接拒绝包含 - 注意:该函数不解析
..,所以仍需配合realpath()做最终路径归一化
开启 open_basedir 是最后防线,但别依赖它兜底
open_basedir 能限制 PHP 所有文件操作(包括 include)只能在指定目录内,看似一劳永逸。但它不是安全机制而是资源隔离手段,配置错误反而会导致正常功能失效,且无法阻止同一目录下的恶意文件包含(比如攻击者上传了 shell.php)。
实操建议:
- 线上环境必须启用,例如:
open_basedir = "/var/www/site/:/tmp/"(注意末尾冒号和路径分隔) - 不要写成
open_basedir = "/var/www/site"(缺斜杠会导致 /var/www/site2 也被允许) - CLI 模式下该配置默认不生效,需单独设置
- 它不能替代代码层校验——一旦你允许包含用户可控的
.php,攻击者就能在你授权的目录里搞事情
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











