反序列化学习

Posted by Tattoo on 2019-11-16
Estimated Reading Time 4 Minutes
Words 1.2k In Total
Viewed Times

序列化和反序列化

当程序创建对象时,对象会一直存在;当程序终止,对象则被销毁。如果能够保存程序运行时的对象信息,下次程序启动时,对该对象进行重建,并且与程序上次运行时所拥有的信息相同。而序列化就能完成这种操作。

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程,便于保存在内存、文件、数据库中。在序列化期间,对象将其当前状态写入到临时或持久性存储区。存入以后,可以通过反序列化恢复为程序对象。序列化是对应于类中的,所以大部分面向对象编程语言都存在反序列化漏洞。

简单地说,序列化就是将对象用字符串表示,反序列化就是将序列化字符串转换为对象。

PHP反序列化漏洞

PHP序列化和反序列化

在php中,通过

string serialize(mixed $value) 函数实现序列化

mixed unserialize(string $str) 函数实现反序列化

漏洞成因

程序对输入的数据处理不当导致的。当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL注入,目录遍历等不可控后果。

在反序列化的过程中自动触发了某些魔术方法。

漏洞触发条件

unserialize函数变量可控,php文件中存在可利用的类,类中有魔术方法

魔术方法

php将所有以 __ (两个下划线) 开头的类方法保留为魔术方法,不需要显式调用也能执行,为编程提供了很多便利,也非常重要。

常用的魔术方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__construct()
类的构造函数,当一个对象被创建时调用,做初始化工作

__destruct()
类的析构函数,当一个对象被销毁时调用

__toString()
当一个对象被当作一个字符串时调用,无参数

__sleep()
调用serialize()函数之前(若存在则)触发的方法,无参数,返回数组

__wakeup()
调用unserialize()函数之前(若存在则)触发的方法,用于预先准备对象需要的资源,如数据库连接

__construct()的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
//序列化对象
class Person
{
public $name = "Tom";
public $age = 18;
public $id;
public function __construct($content)
{
$this->id = $content;
}
}
//new一个对象
$person = new Person('Who am I?');

//输出序列化对象
echo serialize($person);
?>

输出如下

从左往右,序列化数据格式:

O代表Object对象

6代表对象名长度

“Person”代表对象名称

3代表该对象中变量个数

花括号中以分号为一组,相邻两组为一个变量属性,分别为数据类型、变量名长度、变量名(变量值)

__sleep()的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
//序列化对象
class Person
{
public $name = "Tom";
public $age = 18;
public $id;
public function __construct($content)
{
$this->id = $content;
}
public function __sleep()
{
$this->id = 'I am admin!'; //在调用serialize函数之前触发输出
return array('id');
}
}
//new一个对象
$person = new Person('Who am I?');

//把这个对象进行序列化
echo serialize($person);
?>

输出如下

可以看到用了__sleep()魔术方法之后,对象原本的变量没有输出

漏洞演示

serialize.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
<?php
//serialize.php
class Book
{
public $bookname = '《LINUX COMMAND》';
public $ISBN = '7123-8452-594';
public function __toString()
{
return file_get_contents($this->filename);
}
}
class User
{
public $name = 'Tattoo';
public $age = 20;

public function __toString()
{
return 'username: '.$this->name.'<br> age: '.$this->age;
}
}
$book = new Book();
echo serialize($book);
?>

输出如下

这是一个php的序列化文件,可以看到对象book已经被保存到变量$book中,并成功输出对象信息

仔细审计代码,发现存在读文件函数,修改代码进行测试反序列化

unserialize.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
<?php
//unserialize.php
class Book
{
public $bookname = '《LINUX COMMAND》';
public $ISBN = '7123-8452-594';
public function __toString()
{
return file_get_contents($this->filename);
}
}
class User
{
public $name = 'Tattoo';
public $age = 20;

public function __toString()
{
return 'username: '.$this->name.'<br> age: '.$this->age;
}
}
$obj = unserialize($_GET['selz']);
echo $obj;
?>

构造selz参数的值,读取文件

1
?selz=O:4:"Book":1:{s:8:"filename";s:8:"flag.txt";}

输出如下

成功读取文件内容

修复方法

  1. 严格控制unserialize函数的参数,一切输入都是不安全的
  2. 对变量内容进行检查和过滤,降低风险
  3. 更换更安全的函数

If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !