审计之PHP反序列化漏洞详解(附实例)
Posted 猎户攻防实验室
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了审计之PHP反序列化漏洞详解(附实例)相关的知识,希望对你有一定的参考价值。
OWASP Top10 中反序列化已单独分类, php反序列化漏洞是一种常见的漏洞,本篇以源码原理角度分析,希望能深入浅出的让各位即使没有安全背景的读者明白什么是PHP反序列化漏洞,以及如何利用。
如有错误不足之处,请不吝指教。
==== 上卷 —— 科普 ====
1. 前置知识
• PHP类与对象
• PHP魔术方法
• PHP反序列化方法
1.1 PHP类与对象
先从最简单的开始。
在PHP中,定义一个类,和定义一个类的方法。
一个简单的例子:
1.2 PHP魔术方法
在PHP官方网站中的定义:
__construct() , __destruct() , __call() , __callStatic() , __get() , __set() , __isset() , __unset() , __sleep() , __wakeup() , __toString() ,__invoke() , __set_state() , __clone() 和 __debugInfo() 等方法在 PHP 中被称为"魔术方法"(Magic methods)。在命名自己的类方法时不能使用这些方法名,除非是想使用其魔术功能。
咱们来简单讲解几个魔术方法
1. __construst()
2. __destruct()
3. __toString()
__construst() 方法在每次创建新对象时会被自动调用
__destruct() 方法在使用 exit() 终止脚本运行时也会被自动调用
__toString() 方法在一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。
代码:
输出结果:
1.3 PHP对象序列化
在PHP网站中的定义:
所有php里面的值都可以使用函数serialize() 来返回一个包含字节流的字符串来表示。unserialize() 函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
简单的理解序列化:就是把一个类的实例变成一个字符串。
简单的理解反序列化:把一个特殊的字符串转换成一个实例。
代码:
输出结果:
高亮部分即为序列化后的对象。
反序列化的操作:
代码执行效果:
想要深入了解序列化后的字符的具体意义,请参考这个链接:
http://php.net/manual/zh/function.serialize.php
2. 小试牛刀
通过上面的前置知识,相信大家已经对PHP序列化相关的内容有一个初步的认识。下面来开始分析一个简单的例子。
这是一个WTFLog 类,调用 loginfo 方法会记录一条记录到access.log文件中,WTF的地方是,当这个类完成它的使命的时候(exit),会删除掉它所记录的文件。
重点来了...
这里有一个“正常”的业务,pets.php :
该代码的业务目标是从用户处收集序列化后的数据,并进行一些操作。
该代码段的特点是:直接使用客户端可以控制的输入点($_GET['pet_serialized']),在不进行验证的情况下,直接实例化了这段代码!!
看我们如何利用这个危险的输入点:
还记得刚才的WTFLog吧!
先新创建一个新的poc.php,把 log.php 内的WTFLog类引入进来。
index.php 是一个很有用的文件,会输出一句话。
运行 poc.php ,得到我们想要的序列化后的WTFLog 字符串。
反序列化字符串:O:6:"WTFLog":1:{s:8:"filename";s:9:"index.php";}
这里利用这个字符串,去调用pets.php
本来要删除掉access.log这个文件,却删除了我们的重要文件index.php。
验证一下:
到了这一步,漏洞已经利用完成。
回顾一下整个漏洞利用的过程:
1. 需要有一个漏洞触发点 pets.php 内的 $pet = unserialize($_GET['pet_serialized']);
2. 需要有一个相关联的,有魔术方法(会被自动调用)的类。log.php (WTFLog 内的__destruct函数)
3. 漏洞的效果取决于__destruct 这个魔术函数内的操作,这里的是可以操控的可以删除的log 的 filename。
4. 构建poc.php ,利用程序,先序列化后,从可控输入$_GET['pet_serialized']输入进去。
5. 完成。
大家从上面的过程中对PHP反序列化的方式及危害有了一个比较直观的理解了。那如何利用PHP反序列化这个漏洞来做更多的事情呢,这里需要看更多的魔术方法的自动触发的情景,根据不同的使用情景(代码实现方式),来定制我们的Poc,如果可控代码中,存在call_user_func() 这个函数,可以实现通过PHP反序列化漏洞来进行任意代码执行。
==== 下卷 —— 实战 ====
结合上一部分PHP反序列化的介绍,炒一下冷饭,给大家分析一下2017年10月份出的 Typecho 反序列化漏洞。
本文的目的是把反序列化漏洞讲清楚,奶妈级讲解方式,让你看明白。
1. 漏洞回顾
示例漏洞利用的过程:
1. 需要有一个漏洞触发点 :pets.php 内的 $pet = unserialize($_GET['pet_serialized']);
2. 需要有一个相关联的,有魔术方法(会被自动调用)的类。log.php (WTFLog 内的__destruct函数)。
3. 漏洞的效果取决于__destruct 这个魔术函数内的操作,这里的是可以操控的可以删除的log 的 filename。
4. 构建poc.php ,利用程序,先序列化后,从可控输入$_GET['pet_serialized']输入进去。
5. 完成。
2. 代码回溯
在本文编写时,Typecho早已发布了很多新版本,让我们从git logs 中,找到存在漏洞的版本。
根据漏洞发生时间:10月左右,commit message : 漏洞 来确定具体的commit是哪一个
可以看到e277141 是已经修复后的commit,那么242fc1a就是我们需要回溯到的版本。
通过git 方式下载仓库以后。切换到目标版本。
3. 漏洞分析
Typecho 反序列化漏洞存在于install.php 文件中,让我们按照之前的思路来分析一下。
1.%2 寻找可控的反序列化输入点。
在install.php中,通过搜索 unserialize 关键词,发现两个相关点。
看下代码:
install.php
232行是漏洞关键输入点。那么Typecho_Cookie::get()是啥?
看下代码:
Cookie.php 的 Typecho_Cookie 类
A: “获取指定的COOKIE值”
那么,232 行的意思是:反序列化了用base64解码后的key为__typecho_config 的 cookie 内容。
cookie 可控,还有一个关键点是如何进入这段代码。
一起来看一下
执行到232行漏洞触发点的前提:
1. 设置finish参数
2. 设置refer,来源于漏洞站就ok
第一步已经搞定。下面来分析第二步。
3.2 需要有一个相关联的,有魔术方法(会被自动调用)的类
我们重新来看下232行开始的代码。
序列化后的内容赋值给了$config
234行把$config['adapter'] , $config['prefix']作为参数传入了Typecho_Db类。
复习一下之前说过的magic method。
1. __construst()
2. __destruct()
3. __toString()
__construst() 方法在每次创建新对象时会被自动调用
__destruct() 方法在使用 exit() 终止脚本运行时也会被自动调用
__toString() 方法在一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。
查看Typecho_Db类查看下源代码。
Db.php
114 行,$adapterName 作为参数被传进来以后,在120行进行了字符串拼接操作,字符串拼接操作会触发实例化类的__toString()方法, 那我们的目标就可以放在__toString()这个magic method 的方向上了。
搜索__toString(),
当我们找到一个符合条件的类的时候,就要确定这个类的magic method 中执行了什么东西。在Typecho_Feed这个类的__toString()中,没有跟上个文章中举例的一样的方法,这里就要另辟蹊径。
3.3 寻找能利用的利用点
在290行,我们可控的$item['auther']调用了一个screenName 这个属性。
在PHP中,如果该实例化的对象访问了一个‘不可访问’的属性的时候,就会调用__get()方法。
这时候我们需要再次寻找 包含 __get() 的类。
这里的__get()调用了get()
继续看get()
298行到310行都是一些赋值操作。
追踪一下_applyFilter()
终于见到了我们想要的可以执行操作的函数call_user_func,
call_user_func — 把第一个参数作为回调函数调用
通过这个函数,可以执行函数。
回溯一下参数传递。从后到前。
[Request.php] call_user_func() 的参数 $filter 可控,来自
$value 参数可控,来自_applyFilter() 的参数 $value。
_appleyFilter() 参数来自get()方法的$key。
_get($key) 直接调用get($key)
看下Feed.php。
[Feed.php] $item['author']->screenName 调用了 _get()方法。
$item['auther']->screenName 的$item 来自 $this->_item
3.4 构建POC
通过第三点的分析,来构建Poc。
再顺过来整理一遍。
install.php - > Db.php -> Feed.php -> Request.php
1. install.php 漏洞触发点,存在unserialize()函数,序列化后的结果传给了Typecho_DB()
2. Db.php 中 120行
$adapterName = 'Typecho_Db_Adapter_' . $adapterName;触发了__toString()方法。
3. 找到存在__toString()并有可以利用的Feed.php,在其中调用了
$item['author']->screenName,调用__get()方法。
4. 找到__get()方法的Request.php ,调用了get()->调用了_appleyFilter()->call_user_func()
5. 结束、
Poc:test.php
先访问:
设置cookie __typecho_config 为test的输出值
设置refer & url
成功写入shell
4. 结语
PHP反序列化漏洞详细教程及实例(下)的分析到这里,就告一段落了。
如果还有疑问,欢迎来讨论。
同时,非常感谢下面的小伙伴的分析。在编写这篇文章的过程中,起到了参考和启发的作用。
Refer:
http://p0sec.net/index.php/archives/114/
https://paper.seebug.org/424/
https://paper.tuisec.win/detail/c1ecf917be22318.jsp
以上是关于审计之PHP反序列化漏洞详解(附实例)的主要内容,如果未能解决你的问题,请参考以下文章
16-PHP代码审计——Typecho1.0.14反序列化漏洞