php反序列化
概念
序列化
将变量转变为可以保存和传输的字符串类型的过程
相关的函数
1 |
|
反序列化
就是将反序列化的字符串变成原来的变量
实现的函数是
1 |
|
反序列化类型
类型 | 结构 |
---|---|
Object | O:length:class name:attribute number:{arr1;value1;arr2;value2;} |
Array | a:number:{key1;value1;key2;value2;} |
String | s:length:value; |
Integer | i:value; |
Boolean | b:value; //(0=False,1=True) |
Double | d:value; |
Null | N; |
pointer reference | R:number |
1 |
|
但是可以发现一个地方并不是按照我们需求
在反序列化private $interger = 10
的过程中,将参数名改成了”exampleinterger”
反序列化规则
var和public:var 和 public 声明的字段都是公共字段,因此它们的字段名的序列化格式是相同的。
公共字段的字段名按照声明时的字段名进行序列化,但序列化后的字段名中不包括声明时的变量前缀符号
$。
protected:声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可
见。保护字段的字段名在序列化时,字段名前面会加上\0*\0的前缀。这里的\0表示 ASCII 码为0的字
符,属于不可见字符,因此该字段的长度会比可见字符长度大3。
private:声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可
见。私有字段的字段名在序列化时,字段名前面会加上\0\0前缀。这里 表示的是声明该私有字段的类的类名,而不是被序列化的对象的类名。因为
声明该私有字段的类不一定是被序列化的对象的类,而有可能是它的祖先类。
反序列化漏洞
反序列化漏洞:也叫对象注入,就是当程序在进行反序列化时,会自动调用一些函数,但是如果传入函
数的参数可以被用户控制的话,用户可以输入一些恶意代码到函数中,从而导致反序列化漏洞。
可以理解为程序在执行unserialize()函数是,自动执行了某些魔术方法(magic method),而魔术方法
的参数被用户所控制(通过控制属性来控制参数),这就会产生安全问题。
利用条件
- unserialize() 函数的参数是可以控制的
- 存在可以利用的魔术方法
属性赋值
通过利用条件可以发现,如果想利用反序列化漏洞的话,必须向unserialize() 函数传入伪造的反序列化数据(定义合适的属性值)
可以参考下面一段存在漏洞的代码
1 |
|
直接写序列化数据显示是不合适的,因为序列化数据不符合人类直观,很容易出错。实际上,都是利用serialize()函数来生成序列化数据的。
把题目代码复制到本地;
注释掉与属性无关的内容(方法和没用的代码);
对属性赋值;
输出url编码后的序列化数据
echo(urlencode(serialize(1 new DEMO1())));
将反序列化的发给服务器进行解析,造成反序列化漏洞
但是这个过程中需要对序列化字符串进行url 编码
因为是:反序列化的字符串具有不可见的字符如果不进行加密,数据可能被截断
现在应该想的是如何对属性赋值
直接在属性中赋值
优点是方便
缺点是只能对属性赋值字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<?php
class DEMO
{
public $func = 'evil';
public $arg = 'phpinfo();';
# public function safe(){
# echo $this->arg;
# }
# public function evil(){
# eval($this->arg);
# }
# public function run(){
# $this->{$this->func}();
# }
# function __construct(){
# $this->func = 'evil';
# }
}
# $obj=unserialize($_GET['a']);
# $obj->run();
echo (urlencode(serialize(new DEMO())));外部赋值
优点是可以赋值任何值
缺点是只能对public 属性进行赋值1
2
3
4
5
6
7
8
9
10
11<?php
class DEMO
{
public $func;
public $arg;
}
$o = new DEMO();
$o->func = 'evil';
$o->arg = 'phpinfo();';
echo (urlencode(serialize($o)));小技巧:对于php7.1+的版本,反序列化对属性类型不敏感。尽管题目的类下的属性可能不是public,但是我们可以本地改成public,然后生成public的序列化字符串。由于7.1+版本的容错机制,尽管属性类型错误,php也认识,也可以反序列化成功。基于此,可以绕过诸如\0 字符的过滤。
构造方法赋值(万能方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14<?php
class DEMO
{
public $func;
public $arg;
function __construct()
{
$this->func = 'evil';
$this->arg = 'phpinfo();';
}
}
$o = new DEMO();
echo (urlencode(serialize($o)));魔术方法
上面的例子是有一个$obj->run() 函数来执行注入的命令,这是为了演示漏洞利用,实际上但是大多数服务器代码是没有这个运行函数。这时,需要可以自动执行的魔术方法。
魔术方法是一种特殊的方法,在某些情况下会自动调用。魔术方法的命名是以两个下划线开头的,常见的魔术方法有:1
2
3
4
5
6
7
8
9
10
11
12
13__construct() //对象创建(new)时会自动调用。
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get($key) //用于从不可访问的属性读取数据,$key就是不存在的属性
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把对象当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发
__autoload() //在代码中当调用不存在的类时会自动调用该方法