审计之PHP反序列化漏洞详解(附实例)

上卷 —— 科普

PHP反序列化漏洞是一种常见的漏洞,在本篇文章中,希望能深入简出的让各位即使没有安全背景的读者明白什么是PHP反序列化漏洞,以及如何利用。

如有错误不足之处,请不吝指教。

1.你需要的前置知识

a.PHP类与对象

b.PHP魔术方法

c.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 文件中,让我们按照之前的思路来分析一下。

3.1寻找可控的反序列化输入点。

在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_DbAdapter' . $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

本站所有文章均属原创,转载请注明出处

更多技术文章,敬请关注猎户安全实验室微信公众号:

发表评论

电子邮件地址不会被公开。 必填项已用*标注