PHP项目文件包含漏洞:从原理到代码审计实战指南
目录导读
- 文件包含漏洞基础概念 – 理解Local File Inclusion与Remote File Inclusion的本质差异
- 漏洞触发核心条件 – 动态包含、路径拼接、权限控制三大危险因子
- 代码审计实战步骤 – 从函数定位到参数追踪的完整方法论
- 高危函数与代码模式 –
include、require、file_get_contents等函数审计要点 - 绕过与防御机制 – 绕过WAF的常见手法与安全编码规范
- 问答环节 – 5个高频审计问题深度解答
文件包含漏洞基础概念
文件包含漏洞是PHP项目中最常见的Web安全漏洞之一,攻击者通过控制include()、require()、include_once()、require_once()等函数的参数,实现非预期文件的加载执行。

关键分类:
- LFI (Local File Inclusion): 包含服务器本地文件,如
/etc/passwd。 - RFI (Remote File Inclusion): 包含远程服务器上的恶意文件,通常利用
allow_url_include=On配置。
实战案例:
某CMS系统使用$_GET['page']参数加载模板文件,若输入../../etc/passwd即可读取系统密码文件,这种利用路径回溯符()的手法,在代码审计中需要重点关注。
问题1:如何区分LFI和RFI的审计重点?
答:LFI重点审计路径过滤不足导致的目录穿越,RFI重点检查allow_url_include是否开启且用户输入未做协议限制(如未禁止php://input等伪协议)。
漏洞触发核心条件
通过分析大量CVE与实战案例,文件包含漏洞的触发需要同时满足三个条件:
- 动态包含 – 函数参数直接或间接来自用户输入(
$_GET、$_POST、$_COOKIE等)。 - 路径拼接 – 未对输入进行规范化处理(如
realpath()、basename()过滤)。 - 权限缺失 – 未限制可包含文件的目录范围(常见于模板系统)。
危险代码示例:
$template = $_GET['tpl'] . '.php';
include('templates/' . $template);
看似添加了.php后缀限制,但攻击者可通过%00截断(PHP < 5.3.4)或路径编码绕过。
问题2:URL编码在文件包含漏洞中如何被利用?
答:攻击者常将关键字符如进行双重URL编码(%2e%2e%2f)绕过简单过滤,审计时需关注对输入的解码处理次数是否匹配。
代码审计实战步骤
步骤1:定位高危函数
使用工具(如RIPS、PHPStan)或手动搜索PHP内置文件操作函数:
- 包含类:
include、require、include_once、require_once - 文件读取类:
file_get_contents、fopen、readfile - 数据处理类:
fgets、file_put_contents(配合文件包含的写入)
步骤2:追踪参数来源
建立函数调用链:
$_GET['file'] → 经str_replace过滤 → 传递给include()
审计陷阱:过滤函数可能被绕过(如仅替换一次,但攻击者使用)。
步骤3:验证路径可控性
检查路径中是否存在:
- 绝对路径拼接(
/var/www/$file) - 相对路径拼接(
./cache/$file) - 数据库或配置文件中的路径变量
思维清单:
- 参数是否经过
htmlspecialchars而非路径过滤? - 是否使用
realpath()但忽略返回值? - 是否允许伪协议(
php://filter、data://)?
步骤4:测试边界条件
构造Payload进行黑盒验证:
?file=php://filter/read=convert.base64-encode/resource=index.php
?file=../../../etc/passwd%00
?file=/etc/passwd (绝对路径)
问题3:为什么审计时推荐从file_get_contents函数入手?
答:该函数虽然不直接导致RCE,但常配合文件写入函数形成“写文件到可包含目录 → 包含恶意脚本”的链式攻击。file_get_contents对伪协议的过滤经常被忽视。
高危函数与代码模式解析
| 函数名称 | 常见被用模式 | 典型绕过方式 |
|---|---|---|
include() |
include("lang/".$_GET['lang'].".php") |
路径回溯、双写、编码绕过 |
require() |
require_once $path |
伪协议php://input传递PHP代码 |
file_get_contents() |
$data = file_get_contents($_POST['url']) |
结合file_put_contents写入shell |
实战代码审计案例(某电商系统):
function loadModule($module){
$file = 'modules/' . $module . '.php';
if(file_exists($file)){
include $file;
}
}
$userModule = $_GET['mod'];
loadModule($userModule);
漏洞点: 未验证$module是否包含,且file_exists误报可能导致路径遍历。
问题4:如何审计框架中的路由分发机制?
答:关注框架的ControllerLoader或ViewLoader类,例如ThinkPHP的模板加载函数fetch()中若未设置白名单,攻击者可通过指定任意模板文件。
绕过与防御机制
常见绕过技术:
- 路径编码混淆:单次替换时使用(双写绕过)
- 伪协议利用:
php://filter配合Base64读取源码,data://直接写入代码 - 环境变量注入:在IIS或Apache中利用
..%255c..%255c(UTF-8编码双写入) - 服务器特性:Nginx的
alias配置不当导致路径穿越
安全编码规范:
- 限制可包含目录:使用
basename()后拼接固定前缀'static/' . basename($file) - 白名单校验:
$allowed = ['home', 'about']; in_array($page, $allowed) - 禁用危险配置:
ini_set('allow_url_include', 0)和open_basedir限制目录 - 输入规范化:使用
realpath()解析后检查是否在允许路径内
问题5:realpath()函数在审计中的正确姿势?
答:需注意realpath()在文件不存在时返回false,导致某些逻辑绕过,正确做法是先file_exists()检查文件,再用realpath()验证路径是否在安全目录范围内。
常见问题深度解答(FAQ)
Q1: PHP7以上版本是否还存在文件包含漏洞?
A: 虽然%00截断已被修复,但路径遍历()和伪协议利用仍然普遍,某知名内容管理系统在2023年仍爆出LFI漏洞。
Q2: 使用file_get_contents读取远程文件是否算RFI?
A: 严格来说这属于数据获取而非代码执行,但配合eval()或include可将读取内容解释执行形成RFI。
Q3: 审计时如何快速定位用户输入点?
A: 使用静态分析工具的污点分析(Taint Analysis),或手动搜索$_变量后跟踪函数调用链,工具如Seay源码审计系统可自动标记。
Q4: 文件包含漏洞一定会导致RCE吗?
A: 不一定,LFI通常只能读取敏感文件(如/proc/self/environ),但结合日志注入、Session文件包含、上传图片木马等可实现RCE。
Q5: 小团队如何高效进行文件包含漏洞审计?
A: 建立危险函数清单,编写PHPUnit测试用例覆盖所有包含路径,重点审计模板加载、语言文件切换、动态插件加载这三个业务场景。
总结建议: 文件包含漏洞的审计难点在于业务逻辑的多样性,建议开发者遵循“默认禁止”原则,对可包含文件实施白名单管理,使用最新版PHP并关闭危险配置(allow_url_include=Off),可消除80%以上的高危害漏洞,对于遗留代码,优先排查include与require语句中直接拼接$_GET、$_POST的代码段。