导语:Phar反序列化漏洞是一种较新的攻击向量,用于针对面向对象的PHP应用程序执行代码重用攻击,该攻击方式在Black Hat 2018会议上由安全研究员Sam Thomas公开披露。
概述
Phar反序列化漏洞是一种较新的攻击向量,用于针对面向对象的PHP应用程序执行代码重用攻击,该攻击方式在Black Hat 2018会议上由安全研究员Sam Thomas公开披露。类似于对编译二进制文件的ROP(Return-oriented Programming)攻击,这种类型的漏洞利用PHP对象注入(POI),这是面向对象的PHP代码上下文中的一种面向属性的编程(POP)。
由于其新颖性,这种攻击媒介在过去的几个月中越来越受到安全届的关注,导致在许多广泛部署的平台中,接连发现远程代码执行漏洞,例如:
· Wordpress < 5.0.1 (CVE-2018-20148)
· Drupal 8.6.x, 8.5.x, 7.x (CVE-2019-6339)
· Prestashop 1.6.x, 1.7.x (CVE-2018-19126)
· TCPDF < 6.2.19 (CVE-2018-17057)
· PhpBB 3.2.3 (CVE-2018-19274)
在本系列文章中,我们的主要目的是讲解Phar反序列化漏洞的内部工作原理,并进行实战操作,尝试利用PhpBB 3.2.3版本平台中的远程代码执行漏洞。
关于Phar文件、DESERIALIATION和PHP包装器
为了更好地理解这个攻击向量是如何工作的,我们首先需要了解Phar文件是什么、反序列化攻击的原理、PHP包装器是什么,以及上述三个概念是如何关联的。
什么是Phar文件?
Phar(PHP Archive)文件是一种使用单一文件格式分发PHP应用程序和库的方法(类似于JAR文件在Java生态系统中的工作方式。这些Phar文件也可以直接包含在我们自己的PHP代码中。在结构上,它们只是用于压缩,具有可选gzip压缩或基于ZIP压缩文件的tar文件),PHP文档中描述的具体细节如下:
1. 存根(Stub):当Phar作为独立应用程序运行时,它是一个PHP代码序列,充当引导程序。其中,至少必须包含以下代码:
<?php __HALT_COMPILER();
2. 描述存档中包含的源文件的清单:该文件属于可选,保存序列化的元数据,这一序列化的块是漏洞利用链中的关键一环,后续我们将详细说明。
3. 源文件:实际的Phar功能。
4. 一种可选签名,用于完整性检查。
了解反序列化漏洞
序列化是指以二进制格式存储对象属性的过程,以允许其传递或存储在磁盘上,因此可以在以后对其进行反序列化和使用。
在PHP中,序列化过程仅会保存对象的属性、类名,但不保存其方法(因此其缩写词是POP)。从安全的角度来看,这被证明是一种明智的设计方案,除了有一个使反序列化过程变得危险的“魔术方法”。
这些函数特定于每个PHP类,具有Double-underscore的前缀名称,并在某些运行时事件上隐式调用。默认情况下,大多数时间什么都不做,开发人员的工作就是定义它们的行为。在我们的实际案例中,有以下两项值得特别关注,因为它们是只能被Phar反序列化触发的因素:
1. __wakeup():在对象的反序列化过程中调用实现;
2. __destruct():在代码中不再适用对象并被垃圾收集器销毁时隐式调用。
接下来,我们具体来看看如何使用此向量,在虚拟的示例中利用易受攻击的代码片段:
# file: dummy_class.php <?php /* Let's suppose some serialized data is written on the disk with loose file permissions and gets read at a later time */ class Data { # Some default data public $data = array("theme"=>"light", "font"=>12); public $wake_func = "print_r"; public $wake_args = "The data has been read!\n"; # magic method that is called on deserialization public function __wakeup() { call_user_func($this->wake_func, $this->wake_args); } } # acting as main the conditional below gets executed only when file is called directly if (basename($argv[0]) == basename(__FILE__)) { # Serialize the object and dump it to the disk; also free memory $data_obj = new Data(); $fpath = "/tmp/777_file"; file_put_contents($fpath, serialize($data_obj)); echo "The data has been written.\n"; unset($data_obj); # Wait for 60 seconds, then retrieve it echo "(sleeping for 60 seconds…)\n"; sleep(60); $new_obj = unserialize(file_get_contents($fpath)); }
我们注意到,在反序列化时,__wake方法动态调用对象的$wake_func和$wake_args属性指向的print_r函数。经过简单的运行后,产生以下输出:
$ php dummy_class.php The data has been written. (sleeping for 60 seconds…) The data has been read!
但是,如果在60秒的时间内,我们设法是用自己的序列化数据替换原序列化数据,从而控制反序列化中所要求的的函数呢?下面的代码描述了如何完成此操作:
# file: exploit.php <?php require('dummy_class.php'); # Using the existing class definition, we create a crafted object and overwrite the # existing serialized data with our own $bad_obj = new Data(); $bad_obj->wake_func = "passthru"; $bad_obj->wake_args = "id"; $fpath = "/tmp/777_file"; file_put_contents($fpath, serialize($bad_obj));
尽管dummy_class.php的源代码没有改变,但是当dummy_class.php正在等待的过程中,如果在60秒的时间内运行上面的代码片段,我们就能够得到一个完美的代码执行。该行为是由序列化对象的动态函数调用产生的,通过对象的属性更改为passthru("id")。
$ php dummy_class.php The data has been written. (sleeping for 60 seconds…) uid=33(www-data) gid=33(www-data) groups=33(www-data),1001(nagios),1002(nagcmd)
在PHP对象注入(POI/反序列化)攻击的上下文中,这些易受攻击的代码序列中包含Gadget或POP链的名称。
PHP包装器:将它们包装在一起
根据PHP文档上的描述,流(Stream)是与文件、网络、数据压缩以及共享一组通用功能和用途的其他操作相关的方式。PHP包装器负责执行处理各种协议的复杂任务,并提供带有协议数据的流接口。这些流通常由文件系统函数使用,例如:fopen()、copy()和filesize()。
使用类似URL的语法方案访问流wrapper://source。PHP提供的最常见流接口是:
· file:// – 访问本地文件系统
· http:// – 访问HTTP(s) URL
· ftp:// – 访问FTP(s) URL
· php:// – 访问多种I/O流
我们需要重点关注的一种流类型是(*drum roll*) phar://包装器。其典型的格式类似于phar://full/or/relative/path,并且包含两个值得关注的属性:
1. 在声明流时,不会检查其文件扩展名,从而使phar文件成为名副其实的多类型候选者。
2. 如果使用phar流作为参数调用文件系统函数,那么Phar的序列化元数据会自动通过设计实现反序列化。
下面是触发Phar反序列化的文件系统函数列表:
copy file_exists file_get_contents file_put_contents file fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype fopen is_dir is_executable is_file is_link is_readable is_writable lstat mkdir parse_ini_file readfile rename rmdir stat touch unlink
如何进行Phar反序列化攻击
至此,我们已经拥有了漏洞利用所需的所有组成部分。利用Phar反序列化漏洞所需的条件通常包括:
1. 应用程序源代码(包括第三方库)中存在小工具/弹出链,允许POI利用;大多数情况下,这都是通过检查源代码来发现的。
2. 能够包含本地或远程恶意Phar文件(最常见的一种是,通过文件上传和依赖于多类型(Polyglot))。
3. 在用户控制的Phar包装器上调用文件系统函数的入口点,也可以通过检查源代码发现。
举例来说,我们考虑一个未经过充分过滤的输入字段,用于通过URL设置个人资料图片。攻击者将输入值设置为先前上传的Phar/polyglot,而不是http://地址,例如:phar://../uploads/phar_polyglot.jpg;在服务器端,后端在提供的包装器上执行文件系统调用,例如通过调用file_exists(phar://../uploads/phar_polyglot.jpg)来验证磁盘上是否存在该文件。此时,上传的Phar的元数据被反序列化,并利用Gadget/POP链完成漏洞利用链。
在本博客文章的第二部分,我们将通过在PhpBB 3.2.3(CVE-2018-19274)中利用远程代码执行漏洞,来具体理解这些概念是如何应用的。