serialize()

php支持将一个数组或者类的属性序列化为一个字符串,使用的函数就是 serialize() 函数,如果想要复原可以使用 unserialize() 函数来将一个序列化的字符串还原成原来的类或者数组的状态。

举个例子(来自与BuXuan):

1
2
3
4
5
6
7
8
9
10
<?php
class test
{
public $name="ghtwf01";
public $age="18";
}
$a=new test();
$a=serialize($a);
print_r($a);
?>

这个test类的变量被序列化并输出之后,会得到这样的一个字符串,这里面只能序列化这个对象里面的各种参数,无法序列化方法:

20191112144234-9c15d5ca-0517-1.png (899×409)

这里需要注意的是,不同类型的变量在使用序列化的时候,是不一样的,PHP的类中的属性类型有三种:public / private / protected

public (公共的) : 在本类内部、外部类、子类都可以访问。
protected (受保护的) : 只有本类或子类或父类中可以访问。
private (私人的) : 只有本类内部可以使用。

public 类型属性序列化的时候是没有任何变化的。

private 类型会变成 %00类名属性名%00%00 会占用一个字符的长度,有类名是为了标记这是一个只有在这个类里面使用的私有属性,所以 private类序列化之后的长度往往会变成:属性长度+类名长度+2 .

protected 类型这会变成 %00*%00属性名 ,所以这个长度就会变成 属性名+3 .

unserialize()

php的 unserialize(str) 函数可以把一段符合序列化的字符串还原成一个类对象,里面只包含类的各种属性。 如果当前代码中有含有和反序列化的类名字相同的类,那么可能会激活 __wakeup() !

无论反序列化的类名字和当前代码的类名字是否相同,都不需要在意里面的属性名字是否需要吻合。如果类名字相同,部分属性名字相同,那么相同部分的属性的值可以共用。

序列化漏洞

序列化和反序列化不仅仅只是上面的两次转换那么简单,因为在PHP的class类中,有一些魔术方法,这些方法会在class中的各种操作中自动调用,我们就可以利用这些魔术方法在序列化和反序列化的过程中的自动调用,配合着自己精心准备的序列化代码,就可以实现一些神奇的操作。

  1. __construct() 当new一个新的对象时,会自动执行。
  2. __destruct() 当销毁一个对象时自动执行,程序结束时,所有存活的对象都会被销毁,若有 __destruct() 则会自动执行。
  3. __sleep() 进行序列化的时候,会自动进行。
  4. __wakeup() 进行反序列化的时候,会自动进行。
  5. __toString() 当反序列化的数据被输出(被当成字符串)的时候
  6. __call() 当我们调用类中无法访问的方法(包括没有权限访问和不存在两中可能。)就会自动执行。
  7. __get() 我们的类中有些属性都是私有的或者被保护的类型,如果在类外面调用这些属性的时候,如果__get() 存在,那么就会自动执行,如果不存在,就会报错。__get() 中和 __call() 中是一样的,最后只需要明确一个返回值(return)给调用时处理就可以了,但是 __get($name) 会自动传入一个 $name 这个数据,为所非法调用属性的名字。
  8. __invoke() 如果把一个实例对象当作函数调用,那么就会激活这个方法。

需要注意的是,当我们使用 unserialize() 反序列化之后,所产生的也是一个新的对象,这个对象被销毁(或程序结束的时候),同样会触发 __destruct() 函数。

我们就可以利用网页代码里面包含的各种魔术方法里面的代码,利用他们的自动执行加上我们序列化的时候精心设计的各种属性的值,然后拿到各种信息。

__wakeup()

这个函数会在数据反序列化的时候,自动执行(前提是网页里对应的类里面含有__wakeup() 方法)

__wakeup()函数的目的一般是为了给反序列化出来的对象进行初始化赋值,但是我们是想用我们自己的属性而不是网页代码给你的初始化值,因此就需要绕过这个函数。而绕过的方法就是在序列化生成的数据中修改里面属性的数量,使其大于实际类中属性的数量,然后系统就会绕过wakeup() 函数的执行,像这样:

1
2
3
4
5
6
7
8
class xctf
{
public $flag = '111';
public function __wakeup()
{
$flag="111";
}
}

实际生成的序列化数据:O:4:"xctf":1:{s:4:"flag";s:7:"hacked!";}

如果我们输入这段序列化数据,那么我们输入的 $flag 的值就会在反序列化的时候被 __wakeup() 函数给 修改覆盖为 “111”,就入侵失败了。

现在我们修改序列化数据的数值,使得其大于类中的属性数量。

O:4:"xctf":2:{s:4:"flag";s:7:"hacked!";}

这样在传入数据的时候,因为数据中的显示属性是2,那么因为实际上的类中的属性数量是1,冲突了,就会跳过 __wakeup() 的执行,成功传入想要的属性的值。

特殊的PHP6反序列化

一般来说我们 注意有 S 这个东西,可以绕过一些

注入对象构造方法

需要注意的是,当我们在传入对象被 privateprotected 保护的属性的时候,是有特殊的格式的,而在生成序列化字符串的时候因为 %00 不会被显示出来,所以就需要我们在输出的字符串里面加上这些东西。

private 类型会变成 %00类名%00属性名

protected 类型这会变成 %00*%00属性名

这些东西一定要记得加上啊!

__toString()

就和前面的描述的是一样的,__toString() 函数是再这个类被当成字符串使用的时候,定义一下该做一些什么东西。比如下面的代码,当我们输出一个类$a的时候,它就会调用 _toString() 函数返回需要的字符串。

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

class Echo_String
{
public $string;
public function __construct($in)
{
$this->string=$in;
}
public function __toString()
{
return $this->string;
}
}

$a=new Echo_string('Xorex');
echo $a;

?>

最后会输出字符串 Xorex ;

__call($function_name,$function_variable)

对于这个自动方法,在我们引用不可被访问的类中的方法的时候就会执行。

比如我定义的类中方法只有 sing(),jump(),rap()这三个方法,但是你硬要使用方法 basketball(‘run’,’jump’,’shoot’),那这时如果存在 __call() 函数,那么就会自动执行了。

__call 会自动输入两个参数,一个是你非法调用的函数名称 basketball 最为第一个参数,然后回将你调用非法函数时传入的数据 run jump shoot 合并成一个数组作为第二个变量。