介绍
序列化就是:
将php变量比如php数组或者对象转换为字节流,字节流可以储存在数据库里面
相关函数:
serialize() //将对象转换为字符串
unserialize() // 将字符串还原为一个对象
addslashes() // 对写入数据库的数据进行处理,
stripslashes() //对数据进行处理
相关方法:
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发,调用不到方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发
序列化与反序列化
例子
1 |
|
1 | 输出 |
0:4:”test”:4
代表 O为对象 站四个字符为test 有4个属性
{} 里面的为 属性昵称 和 属性值
格式:{类型:变量长度:”变量名”;类型:变量值长度:”变量值”} //六个 : 为一组 前三个代表属性名 后三个代表属性值
注:
私有属性会在 text两边加上空子节 所以长度为10
保护属性同样也会在*两边加空字节<0x00><0x00>0x00>0x00>
进行反序列化
1 | $data1 = unserialize($data); |
object(test)#2 (4) {
["flag":"test":private]=>
string(3) "123"
["flag1"]=>
string(3) "123"
["a"]=>
string(3) "aaa"
["c":protected]=>
string(3) "ccc"
}
魔术方法
__sleep()
在进行序列化的时候
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误
通俗的讲就是 对象的哪些属性应该被序列化
sleep方法必须返回一个数组,包含需要串行化的属性. PHP会抛弃其它属性的值. 如果没有sleep方法,PHP将保存所有属性.
__wakeup()
unserialize() 会检查是否存在一个 wakeup() 方法。如果存在,则会先调用 wakeup 方法,预先准备对象需要的资源。
wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
通俗讲就是还会对wakeup里面的属性进行反序列化
这两个方法都不接受参数,
实例
1 |
|
__toString()
__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
就是当使用 echo 对象名 因为echo只能用来输出字符串 当用在输出对象时,就会在类里面寻找__toString() 输出这个方法的返回值
实例
1 |
|
输出:
hello xiaoyang[Finished in 0.1s]
反序列化漏洞
绕过__wakeup()
1 |
|
destruct()方法会读取文件
思路大概就是构造序列化对象然后base64编码传入,经过unserialize将file设为flag.php,但是wakeup会在unserialize之前执行,所以要绕过这一点。
当序列化属性要大于真实属性的个数时, 会跳过__wakeup()
先构造序列化
构造序列化对象:O:5:”SoFun”:1:{S:7:”\00*\00file”;s:8:”flag.php”;}
再修改属性值
构造序列化对象:O:5:”SoFun”:2:{S:7:”\00*\00file”;s:8:”flag.php”;}
注:
因为file是protect属性,所以需要加上\00*\00。再base64编码。
session反序列化漏洞
POP链构造
类的构成
类成员包括由属性和方法构成,类属性存在于数据段,类方法存在于代码段,对于一个类来说,类的方法不占用类的空间,占空间的只有类的属性
概念
在二进制利用时,ROP 链构造中是寻找当前系统环境中或者内存环境里已经存在的、具有固定地址且带有返回操作的指令集,而 POP 链的构造则是寻找程序当前环境中已经定义了或者能够动态加载的对象中的属性(函数方法),将一些可能的调用组合在一起形成一个完整的、具有目的性的操作。
二进制中通常是由于内存溢出控制了指令执行流程,而反序列化过程就是控制代码执行流程的方法之一,前提:进行反序列化的数据能够被用户输入所控制。
POP链利用
一般的序列化攻击都在PHP魔术方法中出现可利用的漏洞,因为自动调用触发漏洞,但如果关键代码没在魔术方法中,而是在一个类的普通方法中。这时候就可以通过构造POP链寻找相同的函数名将类的属性和敏感函数的属性联系起来
挖掘思路
能控制反序列化的点
反序列化类有魔术方法
魔术方法里有敏感操作(常规思路)
魔术方法里无敏感操作,但是通过属性(对象)调用了一些函数,恰巧在其他的类中有同名的函数(pop链)
例子
1 |
|
首先看到 flag的输出在 GetFlag 类的普通方法get_flag里面
要想让这个方法执行 就要构造POP链
步骤:
在 string1类 中发现同名方法 get_flag() 在string类的tostring中 要想让tostring执行就想要把类string 当做字符串 因为调用的是参数str1 的方法 所以要将str1 赋值为 类getFlag 的对象
public function __construct(){$this->str1 = new GetFlag(); //将$str1赋值为 类Getflag 的对象 }
在 fnc类 发现invoke方法 当把对象当做函数时会调用此方法 $this->mod2 = “字符串拼接”.$this->mod1;
将$mod1赋值为 string1类 的对象
public function construct(){$this->mod1 = new string1(); }
funct类 中的call 方法 中 调用类mod1方法 且参数为$test2,即无法调用test2方法时自动调用 call方法
需要将mod1 赋值为 func类 的对象
public function __construct(){$this->mod1 = new func(); }
在Call中的test1方法中存在$this->mod1->test2();,需要把$mod1赋值为funct的对象,让call自动调用。
public function construct(){$this->mod1 = new funct(); }
5.查找test1方法的调用点,在start_gg中发现$this->mod1->test1();,把$mod1赋值为start_gg类的对象,等待destruct()自动调用。
public function construct(){
$this->mod1 = new Call();
}
最终payload:
1 |
|
例子2
1 |
|
在 lemo类中调用得的是 normal类 但是在 evil类 中也有在normal类中的相同方法名 action
这时可以构造poc链来调用 evil类 中的 action方法
payload:
1 |
|
一些绕过
1、当成员属性数目大于实际数目时可绕过wakeup方法
2、CTF中成员属性数目前面多一个+可以绕过正则
实战
1
D0g3平台一道很简单的反序列化的题
网址:http://120.79.33.253:9001/
1 |
|
原理:
1 |
|
payload:
http://120.79.33.253:9001/?str=s:7:%22D0g3!!!%22
参考
https://xz.aliyun.com/t/3674#toc-0
http://php.net/manual/zh/language.oop5.magic.php
http://php.net/manual/zh/language.oop5.serialization.php
https://blog.csdn.net/qq_23937195/article/details/73027557
http://www.cnblogs.com/iamstudy/articles/php_object_injection_pop_chain.html
https://www.cnblogs.com/iamstudy/articles/php_unserialize_pop_2.html
https://www.anquanke.com/post/id/162300