<!-- title: file-inclusion -->
文件包含漏洞详解:原理、利用与防御
一、漏洞原理
文件包含漏洞是指 Web 应用在引用文件时,允许用户控制"包含哪个文件",导致攻击者可以加载非预期文件甚至恶意代码的漏洞。它常见于 PHP 等支持动态包含语法的语言,按加载目标的来源分为两类:
- LFI(Local File Inclusion,本地文件包含):能加载服务器本地的任意文件。配合上传功能或日志文件,可升级为代码执行,是最常见的形态
- RFI(Remote File Inclusion,远程文件包含):能加载远程 URL 的文件(需 PHP 配置
allow_url_include=On,现代默认关闭),攻击者可直接让服务器执行自己服务器上的代码,危害最烈
漏洞根源依旧是"用户输入被当代码路径执行"这一信任问题:开发本意是让 page=index 映射到 pages/index.php,但拼接后没有校验,攻击者传入 page=../../../../etc/passwd 就能用 ../ 目录跳转序列读到系统任意文件——这也是文件包含与目录遍历常常成对出现的原因。
PHP 的四个包含语句 include、include_once、require、require_once 在处理不存在或不可读文件时行为略有差异,但对漏洞本身没有本质影响;真正决定危害的是"路径可控"和"解析方式"。
二、示例代码
以下是一个经典的文件包含漏洞页面(虚构站点 example.com):
<?php
// index.php —— 通过 GET 参数决定加载哪个页面模块
$page = $_GET['page'];
include($page . ".php"); // 致命点:路径完全由用户控制
?>正常访问 index.php?page=about 加载 about.php;攻击者访问 index.php?page=../../../../etc/passwd%00(旧版 PHP 的截断技巧)即可读出系统账户文件。修复后的安全写法是白名单映射:
<?php
// 安全版:用户只能选择白名单里的"逻辑名",路径由服务端决定
$allowed = [
'home' => 'pages/home.php',
'about' => 'pages/about.php',
'news' => 'pages/news.php',
];
$page = $_GET['page'] ?? 'home';
if (!isset($allowed[$page])) {
http_response_code(404);
exit('页面不存在');
}
include($allowed[$page]); // 路径不含任何用户输入
?>三、三种典型 Payload(教学演示)
1. 目录跳转读系统文件
?page=../../../../etc/passwd
?page=....//....//....//etc/passwd (过滤 ../ 时的双写绕过思路)利用 ../ 逐层跳出当前目录直达系统根。Windows 服务器对应 ..\..\windows\win.ini。这类读取是 LFI 漏洞验证的第一步,也是应急响应中判断"是否发生过探测"的日志特征。
2. 读取并执行会话文件(LFI 升级为代码执行)
若攻击者能把一段 PHP 代码注入到服务器上任何"会被包含的文件"中——例如写入了 $_GET 数据的会话文件、Web 日志、或上传的"图片马"——再通过 LFI 包含它:
?page=../../../../tmp/sess_xxxxxxxxxxxxxxxx被包含文件中的 PHP 代码将被执行。这条链路解释了为什么 LFI 从"读文件"升级为"执行命令"只差一个可写位置,防御必须整体考虑。
3. 伪协议读取源码(PHP filter)
PHP 独有的伪协议可以在不执行的情况下读出文件经过 Base64 编码后的内容:
?page=php://filter/convert.base64-encode/resource=index将 index.php 本身 Base64 编码后输出,攻击者解码即得源码。这条 Payload 的教学意义:它是无损读源码的标准手法,也是判断目标是否为 PHP 的指纹之一。
四、防御建议
- 白名单映射是根治:永远不要把用户输入拼进 include/require 路径;用"逻辑名 → 真实路径"的映射数组,路径字符串中不含任何用户输入
- 关闭远程包含:确认
php.ini中allow_url_include=Off、allow_url_fopen按需最小化,RFI 即从根源消失 - 固定前缀并校验真实路径:无法完全白名单时,强制拼上固定目录前缀,再用
realpath()校验解析后的路径必须落在该目录内,杜绝../跳转 - 关闭危险伪协议:通过
php://过滤器限制或使用 open_basedir 划定 PHP 可访问目录,封堵php://filter、data://等协议滥用 - 最小化可写位置:会话、日志、上传目录与 Web 包含路径隔离,避免"LFI + 可写文件 = 代码执行"的组合拳成立
- 升级语言栈:现代框架(路由 + 模板引擎)天然不提供"参数决定包含文件"的写法,老式 PHP 站点改造是最彻底的修复
五、总结
文件包含漏洞的攻防围绕一个核心展开:"包含谁"这个决定权必须留在服务端。白名单映射写法只需五行代码就能根治,却仍有大量遗留系统在裸奔。对学习者而言,LFI/RFI 是理解"路径即权限"的最佳案例——它能与文件上传、会话机制、日志系统组合成完整的攻击链,也是防御方案必须做纵深设计的原因。