前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何自动化挖掘php反序列化链 - phpunserializechain诞生记

如何自动化挖掘php反序列化链 - phpunserializechain诞生记

作者头像
LoRexxar
发布2023-02-21 21:22:18
4500
发布2023-02-21 21:22:18
举报
文章被收录于专栏:LoRexxar's BlogLoRexxar's Blog

反序列化漏洞是PHP漏洞中重要的一个印象面,而反序列化漏洞的危害则需要反序列化链来界定,如何挖掘一条反序列化链,往往成为了漏洞挖掘中最浪费时间的部分。

而和挖掘漏洞一样,建立在流敏感分析基础上的自动化白盒漏洞扫描技术,依赖数据流中大量的语法节点数据,通过合理的分析手段,我们就可以回溯分析挖掘漏洞,而挖掘php反序列化链也一样,只要有合理的分析思路,那么我们就可以通过分析数据流来获得我们想要的结果。

今天我们就来一起聊聊,如何把人工审计转化成自动化挖掘方案吧~

如何挖掘一个PHP反序列化链

反序列化漏洞的原理这里就不再赘述了,而PoP链的核心,就是魔术方法。而php的魔术方法中涉及到反序列化的大致有以下几种:

代码语言:javascript
复制
__destruct: 析构函数,会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。一般来说,也是Pop链的入口。
__toString: 类对象遇到字符串操作时触发。
__wakeup:   类实例反序列化时触发。
__call:    当调用了类对象中不存在或者不可访问的方法时触发。
__callStatic: 当调用了类对象中不可访问的静态方法时触发。
__get:     当获取了类对象中不可访问的属性时触发。    
__set:     当试图向类对象中不可访问的属性赋值时触发。
__invoke:   当对象调用为函数时触发

通俗来讲,我们可以把__destruct当作挖掘反序列化链的入口,因为__wakeup一般内容为反序列化的限制。

__destruct开始,我们探讨,在不同的情况下我们分别会如何寻找调用链?

__call__callstatic

当代码出现

代码语言:javascript
复制
function __destruct(){
    $this->a();
}

我们可以直接跟下去看a方法的内容,如果当前类不存在a方法,则会优先查找父类的a方法。如果父类也不存在a方法(或是不可访问),那么就会触发当前类的__call魔术方法。

代码语言:javascript
复制
$this->a() ==> 当前类a方法 ==> 父类a方法 ==> 当前类__call方法 ==> 父类__call方法

值得注意的是,如果触发__call方法,那么a会作为__call的方法的第一个参数,而参数列表会作为__call的方法第二个参数。

而当代码出现

代码语言:javascript
复制
function __destruct(){
    $this->a->b();
}

此时,我们便不用纠结于$this->a是代表什么类了,我们可以调用任意类的b方法。换言之,我们也可以调用任意没有b方法的类对象的__call方法。

代码语言:javascript
复制
$this->a->b() ==> 任意类的b方法 ==> 任意类的__call方法

__callstatic和call大同小异,唯一的区别就是当调用静态方法时触发,例如:

代码语言:javascript
复制
function __destruct(){
    $this::a();
}

但可惜的是__callstatic一般来说都不会有太有价值的代码。

__get__set

当代码出现

代码语言:javascript
复制
function __destruct(){
    echo $this->a;
}

这时echo会优先访问当前类的a变量,然后寻找父类的a变量,如果不存在该类变量或者不可访问时,则会调用对应的__get方法

image-20210204163257467
image-20210204163257467
代码语言:javascript
复制
$this->a ==> 当前类a变量 ==> 父类a变量 ==> 当前类__get方法 ==> 父类__get方法

同样,如果调用$this->a->b,我们就有可能触发任意类的__get方法。

而当代码出现

代码语言:javascript
复制
function __destruct(){
    $this->a = 1;
}

如果当前类不存在a变量时,则会触发__set方法

image-20210204165309768
image-20210204165309768

__toString

__toSring是一个很特别的魔术方法,当类对象遇到字符串操作时触发。

他一般常见于这种代码中

代码语言:javascript
复制
function __destruct(){
    echo $this->a;
}

当我们控制$this->a时,我们就可以触发任意类的__toSring方法。

image-20210204170419508
image-20210204170419508
代码语言:javascript
复制
echo $this->a ==> 任意类的__toSring

其他方法

其他方法主要包括__wakeup__invoke,这两种方法比较特殊,在反序列化链中出现的概率比较小。

由于__wakeup是在反序列化时执行,所以一般来说,开发者会倾向于在wakeup函数中加入过滤部分,以减少反序列化漏洞的危害。

__invoke触发条件是当尝试以调用函数的方式调用一个对象时。但可惜的是,如果你试图调用一个方法时,会优先执行__call逻辑,而不是invoke。

image-20210204172009929
image-20210204172009929

换言之,只有经过二次赋值的代码才有可能触发这个函数

image-20210204172252298
image-20210204172252298

在实际环境中,很难见到这样的代码出现了。

在了解了挖掘反序列化链的基础知识后,我们就把前面的思路整理整理,一起来看看怎么写一个自动化挖掘php反序列化链的小工具吧。

完成一个自动化挖掘php反序列化链的小工具

不知道为什么写到这里感觉有点儿像 :>

img
img

到这里为止,你需要我之前的文章构造一个 CodeDB 来探索全新的白盒静态扫描方案的一些前置知识。

现在我们手里有了一张通过格式化AST数据流生成的CodeDB表,我们的目标是完成一个能自动化挖掘php反序列化的工具。

现在我们至少拥有了这样一个思路。首先,我们需要寻找所有的destruct函数

image-20210204182124042
image-20210204182124042

首先,我们加入对call方法的分析

代码语言:javascript
复制
$this->a() ==> 当前类a方法 ==> 父类a方法 ==> 当前类__call方法 ==> 父类__call方法

那么流程图就变成了

image-20210204183416313
image-20210204183416313

紧接着我们要加入__get__set两部分的处理。

代码语言:javascript
复制
$this->a ==> 当前类a变量 ==> 父类a变量 ==> 当前类__get方法 ==> 父类__get方法
image-20210205105145831
image-20210205105145831

在这个基础上,我们再加上__toString部分,如果出现字符串操作,那么进到tostring函数的追溯。

代码语言:javascript
复制
echo $this->a ==> 任意类的__toSring
image-20210205105639061
image-20210205105639061

到目前为止,整个工具的大体架构就确定下来了。为了更好的确认每种会触发魔术方法的方式。我们直接将所有的语法结构分类。

比如MethodCall(方法调用)就有可能触发__call,而Assignment(赋值操作)的左值可能触发__set,右值可能触发__set。建立在这个基础上,我们圈定了每种分类可能触发的魔术方法顺序以及范围,落成代码就成了已有的工具框架。

最后一个需要确定的问题是,如何界定是否存在危害?

这里我用了,可控+参数数量一致+敏感函数3个限制来圈定范围

代码语言:javascript
复制
def check_danger_sink(self, node):
    """
    检查当前节点是否调用了危险函数并可控
    :param node:
    :return:
    """
    self.danger_function = {'call_user_func': [0],
                            'call_user_func_array': [0, 1],
                            'eval': [0],
                            'system': [0],
                            'file_put_contents': [0, 1],
                            'create_function': [0, 1],
                            }

    self.indirect_danger_function = {
                            'array_map': [0],
                            'call_user_func_array': [0],
                            }

    if node.node_type == 'FunctionCall' and node.source_node in self.danger_function:
        sink_node = eval(node.sink_node)

        if len(sink_node) >= len(self.danger_function[node.source_node]):
            # 必须有更多参数
            for i in self.danger_function[node.source_node]:
                if self.check_param_controllable(sink_node[i], node):
                    continue

                return False
            return True

只有出现对应敏感函数,且存在相应的必选参数,且相应的必选参数可控才会被认定为有危害。

到这里为止,我们已经完成了一个看上去还不错的工具雏形,接下来一起看看效果吧。

Joomla 3.9.2反序列化利用链

这里我们拿Joomla 3.9.2做范例,目前版本的工具可以扫描到几个利用链。其中主要的一条利用链如下:

image-20210205124653395
image-20210205124653395

可以跟进去看看代码

image-20210205124734611
image-20210205124734611

有兴趣的朋友可以深入去看下这里的利用链,这是一个可以构造下去的利用链。

写在最后

在研究基于.QL的白盒扫描方案过程中,我遇到了很多很难解决的困难,所以就生出了写一个小工具探索一下试试看的想法。于是phpunserializechain就诞生了。在写插件的过程中也切实体会到了许多有趣的问题,也完善了一个更完整的CodeDB生成方案。践行了不少想法,具体代码可以看

https://github.com/LoRexxar/Kunlun-M/tree/master/core/plugins/phpunserializechain

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021/02/05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 如何挖掘一个PHP反序列化链
    • __call与__callstatic
      • __get与__set
        • __toString
          • 其他方法
          • 完成一个自动化挖掘php反序列化链的小工具
          • Joomla 3.9.2反序列化利用链
          • 写在最后
          相关产品与服务
          文件存储
          文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
          http://www.vxiaotou.com