ysoseri.us

技术内容 / 本科的一些学习笔记和wp / 反序列化漏洞基础

反序列化漏洞基础

本周学习php反序列化漏洞

0×01 概述

反序列化漏洞可以认为是面向对象语言的一种特有漏洞,常见于Java,php,python 中

php反序列化漏洞又称php对象注入漏洞

反序列化漏洞基于序列化和反序列化的操作,在反序列化时存在用户可控参数,而反序列化会自动调用一些魔术方法,如果魔术方法内存在一些敏感操作例如eval()函数,而且参数是通过反序列化产生的,那么用户就可以通过构造参数来执行敏感操作,让反序列化产生非预期的对象

0×02原理

1.序列化与反序列化

序列化是指把对象转换为字节序列的过程,在序列化期间,对象将其当前状态写入到临时或持久性存储区,从而实现跨平台通讯和持久化存储;反序列化则指把字节序列恢复为对象的过程

最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

例如淘宝每年都会有定时抢购的活动,很多用户会提前登录等待,长时间不进行操作,一致保存在内存中,而到达指定时刻,几十万用户并发访问,就可能会有几十万个session,内存可能吃不消。这时就需要进行对象的活化、钝化,让其在闲置的时候离开内存,将信息保存至硬盘,等要用的时候,就重新加载进内存。

在php中

● serialize()将一个对象转换成一个字符串

● unserialize()将字符串还原为一个对象

在java中

序列化用到了ObjectOutputStream类中的writeObject()

反序列化用到了ObjectInputStream类中的readObject()

java暂时仅作了解,本周主要学习php反序列化漏洞

2.魔术方法

PHP把所有以__(两个下划线)开头的类方法当成魔术方法

(所以在定义自己的类方法时,不要以 __为前缀)

__construct() 当创建对象时触发,一般用于初始化对象,对变量赋初值 
__destruct() 当删除一个对象或对象操作终止的时候(对象被销毁前,即从内存中清除前)触发
__sleep() 使用serialize()时自动触发  
__wakeup() 使用unserialize()时自动触发 
其预期用途是重新建立在序列化期间可能已丢失的任何数据库连接,并执行其他重新初始化任务
__toString() 当一个类被当成字符串使用(当打印一个对象时)时触发
 __invoke() 当尝试以调用函数的方式调用一个对象时触发
 __call() _call( $method, $arg_array ) 当调用一个未定义的方法时调用此方法
这里的未定义的方法包括没有权限访问的方法;如果方法不存在就去父类中找这个方法;
如果父类中也不存在就去调用本类的__call()方法,如果本类中不存在__call()方法就去找父类中的__call()方法
__callStatic() 类似于 __call() 魔术方法,当处理静态方法调用
 __get() 用于从不可访问的属性读取数据
 __set() 用于将数据写入不可访问的属性 
 __get、__set两个方法是为在类和他们的父类中没有声明的属性而设计的
__get( $property ) 当调用一个未定义的属性时,此方法会被触发,传递的参数是被访问的属性名
__set( $property, $value ) 给一个未定义的属性赋值时,此方法会被触发,传递的参数是被设置的属性名和值
这里的没有声明包括当使用对象调用时,访问控制为proteced,private的属性(即没有权限访问的属性)

__isset( $property ) 当在一个未定义的属性上调用isset()函数时调用此方法
__unset( $property ) 当在一个未定义的属性上调用unset()函数时调用此方法
与__get方法和__set方法相同,这里的没有声明包括当使用对象调用时,
访问控制为proteced,private的属性(即没有权限访问的属性) 

以上只举了部分常见例子

3.漏洞原理分析

造成漏洞主要有两个问题:应用代码问题、底层PHP漏洞。

(一)应用代码反序列化漏洞产生条件

1. 反序列化函数,传入参数可控
2. __destruct()方法可利用,如利用__destruct()方法中的show_source(),从而暴露源码;
3. 以上两点没有对传参进行过滤,否则无法构成目的Payload。

(二)底层PHP漏洞

简单来说,如果对象属性检查异常。那么Purrase_nested_data()将会返回0,且不调用WAKEUP()方法,但是在这之前对象和它的属性已经被创建,紧接着对象将被破坏,从而执行DESTRUCT()函数,于是导致了漏洞。

(这句话是复制来的,我不理解,也没找到一个合适的阐述来解释什么叫对象属性检查异常)

4.漏洞挖掘

反序列化漏洞的发现一般需审计源码,寻找可利用的pop链

直接在魔术方法中就有可以利用的漏洞在而实际中并不现实,更多的则是需要通过寻找相同的函数名将类的属性和敏感函数的属性联系起来

构造POP CHAIN(POP链)

通过用户可控的反序列化操作,其中可触发的魔术方法为出发点,在魔术方法中的函数在其他类中存在同名函数,或通过传递,关联等可以调用的其他执行敏感操作的函数,然后传递参数执行敏感操作,即

用户可控反序列化→魔术方法→魔术方法中调用的其他函数→同名函数或通过传递可调用的函数→敏感操作

5.实例解析

一些好像能看懂一点的实例

白泽sec安全团队https://my.oschina.net/bylibrary/blog/4969895

CTF题https://www.freebuf.com/news/172507.html

来自安全客的一篇比较全面的讲解https://www.anquanke.com/post/id/86452

0×03 漏洞复现练习

demo

我们先将i am emotional damaged序列化并直接输出

图片 1

得到如下序列化的字符串

图片 2

O代表结构类型为:类,5表示类名($oasis)长度,接着是类名、属性(成员)个数
大括号内分别是:属性名类型、长度、名称;值类型、长度、值

修改代码,定义两个新的对象$s_c和$uns_c,分别对应序列化的对象$c和其反序列化形式,打印对象

图片 3

得到如下结果

图片 4

以上就是一个正常的序列化和反序列化的过程

下面的漏洞就基于这个demo进行复现

编号CVE-2016-7124漏洞

wakeup()魔术方法绕过

版本要求PHP5 < 5.6.25 PHP7 < 7.0.10,这里我用的经典5.4.45版本

这个漏洞的核心在于如果存在__wakeup方法,调用 unserilize() 方法前则先调用__wakeup方法,但是如果****序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行

我有理由怀疑网上大部分的文章也是相互之间抄来抄去,甚至有个作者的代码里连个alert都没有就敢放上带弹窗的页面,真笑死了,随便拿串代码就开讲

图片 5

从网上好几篇文章里看到的一份经典demo,笑死,根本跑不出来

图片 6

为了便于观察结果,我自己写了一份demo

接收s传来的参数对其反序列化后输出name属性的值,加了换行符和utf-8转换(主要是避免汉字输出成乱码)

让输出结果看起来更整洁一些

利用前面test.php得到序列化字符串 O:5:"oasis":1:{s:1:"a";s:31:"研究一个demo给我整emo了";}

用这个字符串作参数访问s得到以下结果

图片 7

可以看到__wakeup()函数率先被调用,随后才返回$a

那么我们把对象属性的个数改成2 O:5:"oasis":2:{s:1:"a";s:31:"研究一个demo给我整emo了";}

图片 8

可以看到$a调用出错没有返回,同时成功绕过__wakeup()

Session反序列化漏洞

可以通俗理解成由于使用不同序列化方式导致的漏洞,例如如果一个是使用php_serialize,而另一个使用php读取Session。因为两者的格式不一样,攻击者可以伪造格式,从而控制数据。

Session序列化机制

当session_start()被调用或者php.ini中session.auto_start为1时,PHP内部调用会话管理器,访问用户session被序列化以后,存储到指定目录(默认为/tmp)。

PHP处理器的三种序列化方式:
| 处理器 | 对应的存储格式 |
| ————————— |:——————————-|
| php_binary | 键名的长度对应的ASCII字符+键名+经过serialize() 函数反序列处理的值 |
| php | 键名+竖线+经过serialize()函数反序列处理的值 |
|php_serialize |serialize()函数反序列处理数组方式|

图片 9

配置文件php.ini中与session存储配置相关的配置项

session.save_path=""   --设置session的存储路径,默认在/tmp
session.auto_start   --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler   --定义用来序列化/反序列化的处理器名字。默认使用php

写了一个demo如下

图片 10

在这个demo中,PHP默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码

ini_set('session.serialize_handler', '需要设置的引擎');。

在 php_serialize 引擎下,session文件中存储的数据为: a:1:{s:4:"name";s:6:"spoock";}

php 引擎下文件内容为: name|s:6:"spoock";

php_binary 引擎下文件内容为: names:6:"spoock";

另外还发现了一个有意思的问题

<?php
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['oasis'] = $_GET['oasis'];
?>

执行上面这个代码,会报错 Undefined array key "oasis" in D:\phpstudy_pro\WWW\interesting.php on line 4

其中的array key就是代码中$_GET[ ]中包含的内容

不断修改session和GET的内容获取不同的报错后访问上面的session.php

图片 11

我们发现session把所有的访问记录都保存了下来

而一旦访问了我写的另一个demo即session1.php后

图片 12

触发了session_start()函数,session就会被重置(或销毁旧的session同时产生一个新的session)

再次访问session.php时,session中的访问记录就会全部消失

上述现象充分体现了存储位置temp的性质,真是名副其实

session1.php的完整代码如下

图片 13

可以发现session.php与session1.php使用了不同的序列化方法

以此为切入点,我们的目标就是实例一个**$a=<?php phpinfo();?>** 并将其写入shell.php里面

思路是访问session.php让/tmp存储可以用php反序列化的poc

在interesting.php页面随便传递一个值 ?emo=123

图片 14

因为session1.php采用php的序列化来读取session的值,所以我们可以控制123的值来构造语句

在interesting.php页面访问emo=O:3:"emo":1:{s:1:"a";s:17:"<?php phpinfo();?>";}

随后访问session.php,会发现写入了如下数据

图片 15

遗憾的是我并没有成功地将<?php phpinfo();?>写进去,尝试了很长时间,百思不得其解

并且我也没找到session_savepath在什么地方,更别说生成的session临时文件了

后面的过程在下面这篇博客中讲得比较详细且没有误人子弟的成分

to be continued……

https://blog.csdn.net/nzjdsds/article/details/82703639?utm_source=app