本文目录导读:

PHP 文件包含漏洞(如本地文件包含 LFI 和远程文件包含 RFI)是 Web 安全中非常危险的漏洞,攻击者可利用它读取敏感文件、执行恶意代码,甚至完全控制服务器。
要彻底避免这类漏洞,需要从编码规范、配置加固、架构思想三个层面进行防御,以下是全面的防护方案:
代码层防御(最核心)
这是最直接、最有效的防线,核心原则是永远不要信任用户输入。
A. 严格的输入验证(白名单机制)
永远不要直接拼接用户输入到文件路径中,而是使用白名单进行映射。
-
错误做法:
$page = $_GET['page']; include($page . '.php'); // 攻击者传入 ../../etc/passwd
-
正确做法(映射表):
$allowed_pages = [ 'home' => 'home.php', 'about' => 'about.php', 'contact' => 'contact.php' ]; $page = $_GET['page'] ?? 'home'; // 检查键是否存在,如果存在,只允许取对应的值 if (isset($allowed_pages[$page])) { include(__DIR__ . '/pages/' . $allowed_pages[$page]); } else { // 返回默认页或404错误 include(__DIR__ . '/pages/error.php'); }
B. 绝对路径与路径规范化(Path Canonicalization)
如果需要动态指定路径,禁止使用相对路径(),必须转换为绝对路径,并在包含前校验真实路径。
$base_dir = '/var/www/html/secure/pages/';
$path = $base_dir . basename($_GET['file']); // basename() 去掉路径中的目录部分
// 关键:使用 realpath() 解析并检查最终的绝对路径是否在允许的目录内
$real_path = realpath($path);
if ($real_path !== false && strpos($real_path, $base_dir) === 0) {
include($real_path);
} else {
die('Invalid file path.');
}
C. 禁用动态包含(PHP 8.0+ 建议)
如果包含的文件名是固定的,不要使用变量,如果非要使用,检查 allow_url_include(见下方配置)。
配置加固(php.ini)
修改 PHP 配置文件,降低被利用的风险(LFI 和 RFI 都适用)。
-
禁止远程文件包含(RFI):这是最重要的一条。
; 默认是 Off,确保是 Off allow_url_include = Off
-
禁止开启远程文件打开(针对某些扩展):
; 不建议开启,防止通过 URL 读取文件 allow_url_fopen = Off
-
关闭危险函数(如果业务不需要):在
disable_functions中禁用可能导致文件读写的函数,但注意这会连带影响正常业务。disable_functions = exec, system, shell_exec, passthru, popen, proc_open
-
敏感文件防护:禁止 PHP 解析某些敏感后缀(如
.txt,.log),防止包含日志文件时执行代码。; 在 Nginx/Apache 配置中,对 uploads 目录禁止执行 PHP ; Nginx: location ~ \.php$ { deny all; }
架构与业务层面
A. 目录权限与隔离
- 目录权限:存放模板或包含文件的目录,其权限设为只读(
755),确保 Web 服务器无法写入。 - 上传目录隔离:用户上传文件的目录严禁存放 PHP 文件,且严禁通过
include直接包含上传目录的文件。
B. 日志文件包含防护
攻击者常通过把恶意代码写入日志(如 User-Agent),然后通过 LFI 包含日志文件执行代码。
- 防护方法:
- 日志目录放在 Web 根目录之外。
- 如果必须在根目录内,给日志文件设置只读权限(
chmod 640),并禁止 Web 服务器对该目录执行 PHP。
C. 使用框架自带的模板引擎
如果业务复杂,建议使用成熟的 PHP 框架(如 Laravel, Symfony)或模板引擎(Twig, Smarty)。
- 框架通常自带
view()或render()方法,内部已经封装了严格的文件路径校验和输入过滤,不要手动拼接include。
高级进阶:危险函数替代方案
在某些极端情况下(如业务必须解析用户指定的文件),请使用以下替代方案:
- 避免使用
include/require:它们会将文件当作 PHP 代码执行,如果只是读取内容,使用file_get_contents()或fopen(),并配合htmlspecialchars()输出,避免 XSS。 - 使用
readfile()输出文件内容,但同样要先做白名单过滤。
终极防御:WAF 与监控
- Web 应用防火墙(WAF):部署 WAF 规则,拦截带有 ,
%00(Null字节,已废弃),php://filter,data://等特征参数的请求。 - IDS/IPS:监控服务器日志,发现异常的路径穿越尝试立即告警。
安全检查清单
| 检查项 | 状态 | 操作 |
|---|---|---|
allow_url_include |
Off |
强制关闭 RFI |
| 包含路径来源 | 不使用 $_GET/$_POST 直接拼接 |
改用白名单映射 |
| 路径校验 | 使用 realpath() 和 strpos 验证 |
阻止目录穿越 |
| 危险函数 | 检查 disable_functions |
禁用 exec,system 等 |
| 上传目录 | 禁止执行 PHP | 隔离上传与执行 |
| 日志文件 | 绝对拒绝包含 | 防止日志投毒 |
最重要的一句话:任何用户可控的输入,都不应该直接或间接地决定 include 或 require 的文件路径。 如果可以,尽量使用路由+控制器+模板映射的方式取代动态文件包含。