php反序列化漏洞

Posted by kingkk on 2018-07-20

序列化与反序列化

目的和意义

序列化:把对象转换为字节序列的过程。
反序列化:把字节序列恢复为对象的过程。

其目的是为了将一个对象通过可保存的字节方式存储起来
这样就可以将学列化字节存储到数据库或者文本当中,当需要的时候再通过反序列化获取

类的序列化

在反序列化对象之后,需要当前作用域中存在该类的定义,否则php无法将类对应到指定类

1
2
$ser = 'O:6:"kingkk":1:{s:4:"test";s:3:"123";}';
print_r(unserialize($ser));

运行结果:

1
2
3
4
5
__PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => kingkk
[test] => 123
)

可以看到,当定义域中不存在该类时会反序列化出一个__PHP_Incomplete_Class的类,也就是一个不完整的类

其次,在反序列化的过程中,只保存原有类的变量值,并不会保存其函数
具体的函数功能是根据定义域中定义好的函数功能来执行

1
2
3
4
5
6
7
8
9
class cls{
var $value = 'kingkk';
function action(){
echo 'action…';
}
}
$a = new cls();
print_r($a);
print_r(serialize($a));

运行结果:

1
2
3
4
5
cls Object
(
[value] => kingkk
)
O:3:"cls":1:{s:5:"value";s:6:"kingkk";}

可以看到字节码中仅保留了变量的相关信息,并没有保存函数的定义
具体的函数要根据定义域中声明的函数功能来执行

最后,当一个类中的变量是另一个类时,会将另一个类也序列化到当前类中

1
2
3
4
5
6
7
8
9
10
11
class cls1{
function __construct(){
$this->cls = new cls2();
}
}
class cls2{
var $value = 'cls2';
}
$a = new cls1();
print_r($a);
print_r(serialize($a));

运行结果:

1
2
3
4
5
6
7
8
9
cls1 Object
(
[cls] => cls2 Object
(
[value] => cls2
)

)
O:4:"cls1":1:{s:3:"cls";O:4:"cls2":1:{s:5:"value";s:4:"cls2";}}

魔术方法(Magic function)

魔术方法是一种类中的特殊方法,在通常通过语法糖的形式自动调用
所以当使用了一些语法糖时,会触发这些函数,从而无意间执行了该函数

搬运来的一些魔术方法

1
2
3
4
5
6
7
8
9
10
11
12
__construct() //对象创建时触发
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发

漏洞利用

直接调用

恰巧类中危险函数在后面存在被调用,就可以控制当前类中的变量,导致漏洞触发

1
2
3
4
5
6
7
8
class cls{
var $value = 'echo 123;';
function action(){
eval($this->value);
}
}
$a = unserialize('O:3:"cls":1:{s:5:"value";s:10:"phpinfo();";}');
$a->action();

运行结果:

1
2
3
4
5
6
7
phpinfo()
PHP Version => 5.5.12

System => Windows NT DESKTOP-S9PSGAM 6.2 build 9200 (Windows 8 Business Edition) AMD64
Build Date => Apr 30 2014 11:15:47
Compiler => MSVC11 (Visual C++ 2012)
……

当危险函数在魔术方法中时

当不存在方法的直接调用时,可以查看一些魔术方法中是否存在危险的函数
因为魔术方法大多在无意间被触发

不同魔术方法的触发条件不一样,这里我拿最简单的__wakeup来介绍

1
2
3
4
5
6
7
class cls{
var $value = 'echo 123;';
function __wakeup(){
eval($this->value);
}
}
$a = unserialize('O:3:"cls":1:{s:5:"value";s:10:"phpinfo();";}');

运行结果:

1
2
3
4
5
6
7
phpinfo()
PHP Version => 5.5.12

System => Windows NT DESKTOP-S9PSGAM 6.2 build 9200 (Windows 8 Business Edition) AMD64
Build Date => Apr 30 2014 11:15:47
Compiler => MSVC11 (Visual C++ 2012)
……

虽然没有显式的调用函数,但是在反序列化过程中,就无意间触发了魔术方法,从而导致漏洞的产生

当危险函数在别的类中被调用

当一个类中的成员变量是另一个类,且调用了另一个类中的危险函数
此时就可以序列化前者类,从而触发触发漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class cls1{
var $ser;
function __construct(){
$ser = new ser2();
}

function __wakeup(){
$this->ser->evil();
}
}

class cls2{
var $value = "echo 123;";
function evil(){
eval($this->value);
}

}
$cls = $GET['cls'];
$instance = unserialize($cls);

此时我们可以构造这样的类,来获取其反序列化字节

1
2
3
4
5
6
7
8
9
10
11
12
class cls1{
var $ser;
function __construct(){
$this->ser = new cls2();
}
}

class cls2{
var $value = 'phpinfo();';
}

print_r(serialize(new cls1()))

1
O:4:"cls1":1:{s:3:"ser";O:4:"cls2":1:{s:5:"value";s:10:"phpinfo();";}}

传入$GET['cls']中既可以触发漏洞

其他编程语言中的反序列化

其他编程语言也存在对应的序列化和反序列化功能
触发的原理类似

Python:

1
2
3
pickle.dump(obj, file, protocol=None, *, fix_imports=True) # 序列化

pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict') #反序列化

Java:
java不是很了解,引用一下别人的

在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。
通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化

Reference Link

https://chybeta.github.io/2017/06/17/%E6%B5%85%E8%B0%88php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
https://www.hackersb.cn/shenji/239.html