php反序列化

1.参考文章

php反序列化完整总结 - 先知社区 (aliyun.com)

PHP-反序列化(超细的) | spaceman’blog (gitee.io)

2.反序列化

php中,序列化函数serialize(),反序列化函数unserialize()。

序列化:将对象序列化为一串字符串;

反序列化相反。

并非只对类可以序列化,数组也可以的。

序列化演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class classAAA
{
public $a="yuleiyun";
protected $b="yunque";
private $c="haha";
public function test()
{
echo "test,ok";
}
}
$test=new classAAA();
$test1=serialize($test);
echo $test1;

?>

结果为:

O:8:”classAAA”:3:{s:1:”a”;s:8:”yuleiyun”;s:4:”*b”;s:6:”yunque”;s:11:”classAAAc”;s:4:”haha”;}

对以上结构分析得:

O:对象名的长度:”对象名”:对象属性个数:{s:属性名的长度:”属性名”;s:属性值的长度:”属性值”;}

a是public类型的变量,s表示字符串,8表示变量名的长度,a是变量名。

b是protected类型的变量,它的变量名长度为4,protected属性的表示方式是在变量名前加上%00*%00,这个长度就是3了,%00很熟悉了,ASCII码即为0。

c是private类型的变量,c的变量名前添加了%00类名%00。所以,private属性的表示方式是在变量名前加上%00类名%00。

而且可见,序列化字符串中只保存变量,不保存函数方法。

反序列化演示

在php下:

echo 输出的是变量和字符串;

var_dump 输出的是变量类型,变量长度和变量值;打印数组及其结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class classAAA
{
public $a="yuleiyun";
protected $b="yunque";
private $c="haha";
public function test()
{
echo "test,ok";
}
}
$test=new classAAA();
$test1=serialize($test);
$test2=unserialize($test1);
var_dump($test2);

?>

结果为:

object(classAAA)#2 (3) {
[“a”]=>
string(8) “yuleiyun”
[“b”:protected]=>
string(6) “yunque”
[“c”:”classAAA”:private]=>
string(4) “haha”
}

除此之外。var_dump与 echo具体输出的话,只能指向公共变量a

如var_dump($test2->a);

否则出现:

Fatal error: Uncaught Error: Cannot access protected property

3.漏洞产生原理

与魔术方法有关,魔术方法接收了序列化后未经过滤的字符串,不当执行造成的。比如魔术方法有一些检查,你可以构造一些数据绕过这些检查,那这样未过滤的输入,危害性很大咯。

魔术方法

魔术方法命名是以符号开头的,比如 construct, destruct, toString, sleep, wakeup等等。这些函数在某些情况下会自动调用。

  • __construct():具有构造函数的类会在每次创建新对象时先调用此方法。
  • __destruct():析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
  • __toString()方法用于一个类被当成字符串时应怎样回应。例如echo $obj;应该显示些什么。 此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
  • __sleep()方法在一个对象被序列化之前调用;可以指定要序列化的对象属性
  • __wakeup():unserialize( )会检查是否存在一个_wakeup( )方法。如果存在,则会先调用_wakeup方法,预先准备对象需要的资源。
  • get(),set() 当调用或设置一个类及其父类方法中未定义的属性时
  • __invoke() 调用函数的方式调用一个对象时的回应方法
  • call 和 callStatic前者是调用类不存在的方法时执行,而后者是调用类不存在的静态方式方法时执行。
  • isset():在不可访问的属性上调用isset()或empty()触发
  • unset():在不可访问的属性上使用unset()时触发

注意: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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
class Test1
{
public $a="123";


public function __construct(){
echo "执行了construct";
}
public function __destruct(){
echo "执行了destruct";
}
public function __sleep(){
echo "执行了sleep";
return array();

}
public function __toString(){
return "执行了toString";
}
public function __wakeup(){
echo "执行了wakeup";
}

}

echo "new一个对象\n";
$test =new Test1();
echo "\n";

echo "\n直接输出类,会调用To_string\n";
echo $test;
echo "\n";

echo "\n序列化,先执行sleep(若有)哈,再序列化\n";
$test1=serialize($test);
echo "\n";

echo "\n反序列化,先执行wakeup(若有),有则先调用wakeup\n";
$test2=unserialize($test1);
echo "\n";
?>

结果为:

new一个对象
执行了construct

直接输出类,会调用To_string
执行了toString

序列化,先执行sleep(若有)哈,再序列化
执行了sleep

反序列化,先执行wakeup(若有),有则先调用wakeup
执行了wakeup
执行了destruct执行了destruct

两次destruct,销毁了反序列化对象一次,new的对象一次。

注意:我这里如果不在sleep指定序列化的变量,是输出不出来的(wakeup,还有反序列的结果)

public function __sleep(){
echo “执行了sleep”;
return array();
}

反序列化与构造函数

虽然反序列化返回的也是一个对象。但构造函数不会在反序列化期间被调用,而是在对象创建时(使用new关键字)才会被调用。反序列化过程只会还原对象的状态,而不会再次创建对象。

4.实例

wakeup绕过

见本博客的CVE部分的CVE-2016-7124

绕过方法

  1. 未定义 __wakeup() 方法: 如果类的定义中没有定义 __wakeup() 方法,无论序列化字符串中的内容如何,都不会触发 __wakeup() 方法的执行。
  2. 类的名称不匹配: 序列化字符串中指定的类名与实际的类名不匹配,或者序列化字符串中的类不存在时,也不会触发 __wakeup() 方法。
  3. 修改类定义: 如果在序列化和反序列化之间修改了类的定义,包括类名、属性或方法的修改,可能会导致 __wakeup() 方法不被调用。因为序列化和反序列化过程依赖于类的定义和结构,如果类的定义发生了改变,可能无法正确还原对象的结构,进而无法触发 __wakeup() 方法。

字符逃逸

漏洞特点

1.相当于构造闭合。(已知php在反序列化时,底层代码以;作为字段分隔,以}作为结尾,且根据长度判断内容。通过构造一定范围内的….;}提前闭合,挤出后面的内容。)

2.长度不对应会报错。

漏洞产生

代码中存在针对序列化后的字符串进行了过滤操作。

phar反序列化

session反序列化

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2023-2025 是羽泪云诶
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信