【漏洞分析】反序列化漏洞

0x01 背景

2022年1月18日,ORACLE官方发布了2022年第一季度的补丁,其中涉及到多个关于Weblogic的漏洞。由于Weblogic的补丁很贵,一般人下载不到,所以一直在等待相关的细节信息。

从CVE官网找到的CVE-2022-21350漏洞是属于Weblogic的未授权访问和拒绝服务漏洞,CVSS评分也只有6.5,和这里的反序列化似乎不是一个漏洞。从漏洞的简要描述来看这个漏洞似乎也不是CVE-2022-21306。

本篇文章主要是针对该漏洞的详细分析和研究,以探讨安全技术为目的,对漏洞分析不感兴趣的小伙伴可直接跳到文末的结论。

0x02 利用链分析

发现反序列化漏洞的过程我们就不追究了,我们直接站在巨人的肩膀上来分析这个漏洞,顺便说明一般反序列化漏洞利用链的分析方式。

要实现反序列化利用链,首先需要知道整个利用链的栈调用过程。这里就不用文字来描述了,直接用最后poc编写完成之后的栈调用截图来表示,如图2.1所示。

HomeHandle反序列化漏洞栈调用过程

从栈调用过程可以看出整个反序列化利用链的入口(Source)点是BadAttributeValueExpException类,出口(Sink)点是HomeHandleImpl类。我们按照倒叙的方式来分析整个利用链,倒叙的方式可能看起来比较别扭,但是有利于我们编写反序列化利用链的利用代码。

找到HomeHandleImpl类的getEJBHome方法,如图2.2所示。图中箭头位置是一个典型的JNDI注入函数lookup,并且lookup函数的两个点ctx和this.jndiName都是可控的。如果某条反序列化的利用链最终能调用这个函数,则可能造成JNDI注入。关于这个位置进行JNDI注入利用的方式在后面的章节进行分析。

HomeHandleImpl类的getEJBHome方法

【一>所有资源获取<一】
1、网络安全学习路线
2、电子书籍(白帽子)
3、安全大厂内部视频
4、100份src文档
5、常见安全面试题
6、ctf大赛经典题目解析
7、全套工具包
8、应急响应笔记

到这里我们要实现反序列化利用链编写的第一步,编写Sink点利用方式。这里可控的参数有this.serverURL和this.jndiName两个点。控制这两个点都可以实现反序列化漏洞的poc(但是实现exp的利用过程需要通过this.jndiName,这是后话)

模拟Sink点调用逻辑

DNSLOG请求日志

到这里我们就可以明确一个信息,如果某个反序列化的调用链可以调用HomeHandleImpl类的getEJBHome方法,那么就可以实现反序列化,整个反序列化利又向前迈出一步。我们继续看哪里调用了getEJBHome方法,从栈调用的图可以看出BusinessHandleImpl类的getBusinessObject方法调用了getEJBHome方法。并且这里的this.homeHandle字段类型是HomeHandleImpl类。

getBusinessObject方法调用了getEJBHome方法

到这里就可以把反序列化利用链再向前一步,编写利用链代码。调用之后同样可以看到DNSLOG截图。

通过调用BusinessHandleImpl的getBusinessObject函数出发Sink

第二步调用之后的DNSLOG截图

到这里我们就可以明确一个信息,如果某个反序列化的调用链可以调用BusinessHandleImpl类的getBusinessObject方法,那么就可以实现反序列化。从栈调用过程中可以看出AttributeWrapperUtils类的unwrapEJBObjects方法调用了BusinessHandleImpl类的getBusinessObject方法,这里的调用过程只与第二个参数unwrappedObject相关。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rVTQW65S-1645444484061)(https://upload-images.jianshu.io/upload_images/26472780-4fb04124c54dc927.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

unwrapEJBObjects方法又被unwrapObject方法调用。

image.png

所以我们只要能调用AttributeWrapperUtils类的unwrapObject方法,并且第二个参数可控,那么就可以实现整个反序列化调用链。继续把反序列化调用链向上走一步,编写反序列化链代码。

通过调用AttributeWrapperUtils类的unwrapObject方法触发Sink

继续向上寻找哪里调用了AttributeWrapperUtils类的unwrapObject方法,通过栈调用图可以看出FileSessionData类的getAttributeInternal方法调用了AttributeWrapperUtils类的unwrapObject方法。这里有一个小坑是FileSessionData本身是没有相关函数的调用的,实际上是通过FileSessionData类的父类SessionData类的getAttributeInternal方法来进行调用的。那么我们为什么不能直接用SessionData类呢?因为SessionData类是一个abstract类,不能直接实例化。

在上一步的分析中我们要求unwrapObject方法的第二个参数必须可控,在这一步中unwrapObject的第二个参数来源于this.attributes字段,属于反序列化可控字段。

getAttributeInternal方法调用unwrapObject方法

getAttributeInternal方法又被getAttribute方法调用。

 getAttribute方法调用getAttributeInternal方法

getAttribute方法又被isDebuggingSession方法调用。

isDebuggingSession方法调用getAttribute方法

isDebuggingSession方法又被toString方法调用。

toString方法调用isDebuggingSession方法

到这里我们就可以通过调用SessData类的toString方法来调用attributeWrapperUtils类的unwrapObject方法。继续向上编写反序列化利用链。

这里有一个点要注意就是attributeWrapperUtils类的unwrapObject方法的第二个参数来源于this.attributes.get(name),这里的name来源于方法参数,最开始的赋值只能是wl_debug_session。

image.png

有经验的小伙伴看到toString函数就会想到CC链,在CC5中就是通过BadAttributeValueExpException类的readObject方法来调用任意类的toString方法的。我们直接使用CC5的后面部分的代码拿过来使用就可以组成本次反序列化利用链的最终组成部分。

通过BadAttributeValueExpException类来调用toString方法

上面的过程就已经完整的复现了这个利用链,但是在本地实际测试的过程中却发现怎么都不成功,不成功主要是因为条件判断会包异常。

异常原因是因为本身registry里面的变量都是null,再去调用null的任何方法都会报空指针异常。

registry.isProductionMode()异常

遇到这个问题的第一感觉是通过反射来修改registry字段的值,但是实际测试过程中却发现这个registry的赋值过程异常复杂,而且里面还有很多final修饰的字段,通过反射修改不成功。后面发现这个registry是属于Weblogic程序启动时候的初始化数据,包含的是Weblogic服务器信息,本地调试当然没有registry信息,但是远程的真实Weblogic服务器就都有这个信息。所以,直接开一个Weblogic的远程调试。

远程调试下的registry是有值的

可能有的小伙伴就觉得奇怪了,我们分析的每一步都是基于调用栈来复现的,实际情况下我们肯定不可能先有某个漏洞的调用栈,然后再来分析。这当然不可能,挖掘一个漏洞的调用过程是一个极难的事情,这可比分析漏洞难多了。

0x03 POC编写

其实上面关于反序列化利用链的分析过程中就已经包含了POC代码了,但是为了方便小伙伴们查看。我们还是把整个代码整合一下,完整的利用代码如下所示:

import weblogic.ejb.container.internal.BusinessHandleImpl;

执行上面的代码会生成一个序列化的文件cve_2022_21350.ser。然后再通过t3协议把这个序列化的文件发送给目标主机就可以了。

通过t3协议发送序列化数据

查看DNSLOG解析记录

0x04 EXP编写

上面我们的POC编写都还是在关于DNSLOG的请求层面,俗话说的好“不能弹计算器的POC都不是好EXP”。上面的也仅是POC层面,还没有EXP层面的利用,我们的目标是弹出计算器。

达到EXP的效果,就需要跟踪Weblogic里面关于JNDI注入的代码。常规的JNDi注入代码大约如下所示。

JNDI注入的一般利用方式

一般情况下我们需要能控制lookup函数中的值,当我们写成ldap协议的地址,就会按照ldap协议请求加载远程数据。但是这里我们可控的是jndiName的字段类型是Name,并不是一般的String。

Name类型的字段jndiName

跟踪关于lookup函数的定义示。

lookup函数定义

里面最关键的是getURLOrDefaultInitCtx函数,该函数规定了对Name类型的数据进行处理,然后传递给下一步的NamingManager类。

通过getURLOrDefaultInitCtx函数处理Name对象数据

我们想要进行的是Ldap注入,Ldap的地址来源于scheme变量,只要我们控制scheme变量为我们恶意的Ldap地址,就可以进行常规的基于LDAP协议的JNDI注入。这里最关键的就是让name.get(0) == "ldap://xxxx.xxx.xx.xxx:1389/xxx"

Name是一个接口基于此接口的实现类如下

javax.naming.CompoundName

javax.naming.CompositeName

com.sun.jndi.dns.DnsName

com.sun.jndi.toolkit.dir.HierarchicalName

com.sun.jndi.ldap.LdapName

javax.naming.ldap.LdapName

依次对这几个类创建对象,查看对象调用get(0)之后是否能获取到ldap://xxxx.xxx/xxx这样的数据。

1) 第一个找到的是DnsName类

由于英文点在DnsName类中是parse函数的分隔符,所以不能使用点。但是我们可以使用ip转整数的方式对点进行转换。所以127.0.0.1可以转化为2130706433,这样就可以获取到完整的ldap路径。

使用DnsName获取Name对象调用get(0)

2) 第二个找到的是CompoundName类

CompoundName可以直接使用构造函数,获取到的就是我们想要的数据。

image.png

3) 第三个找到的是HierarchicalName类

HierarchicalName类是继承自CompoundName类的,所以使用方法也类似。
image.png

图4.7 使用HierarchicalName类获取Name对象调用get(0)

从上面的三种方式中任选一种来生成Name对象,ldap地址填写我们远程vps服务器地址。Sink部分代码如图4.8所示,这里采用最简单直接的CompoundName类生成name对象。

image.png

图4.8 通过CompoundName类来构造恶意ldap地址

其他部分的代码和POC里面的代码完全一样。利用过程需要首先在V1PS上开启注册恶意的ldap服务,利用marshalsec就可以实现了。如图4.9和图4.10所示。

image.png

图4.9 V1PS上的Ldap接受请求,加载恶意类

image.png

0x05 实际测试

本来以为上面的过程之后就完了,但是本着对漏洞分析完整性的研究,我在打了补丁的环境里面测试漏洞的实际危害。下面的所有环境均值Weblogic12.2.1.4.0,其他环境可能略有不同。

环境一,没有打补丁的环境

见0x04章节,可以成功复现漏洞。

环境二,打了2021.10的Weblogic补丁

按理说这个是2022年的漏洞,漏洞更新也是在2022.1的补丁中进行更新的。但是实际上在2021.10补丁的环境中,仍然不能复现成功这个漏洞。

打了补丁的环境是通过FilteringObjectInputStream类来对t3协议的流量进行反序列化,跟踪此类中的resolveClass函数,如图5.1所示。

image.png

图5.1 FilteringObjectInputStream类的resolveClass函数

这个函数就是用来反序列化类,获取类对象的。里面增加了安全措施,第一个就是通过checkLegacyBlacklistIfNeeded函数来校验待反序列化的类是否在Weblogic黑名单中(大名鼎鼎的Weblogic黑名单机制),最开始就说过的这是一条新的反序列化利用链,当然不在现在的黑名单列表中,所以这条的判断可以通过。第二个会通过validateReturnType函数校验生成类的类型,如图5.2所示。如果反序列化的类不是this.expectedType或者this. expectedTypes中的某一个类的子类,则抛出异常。

image.png

图5.2 通过validateReturnType函数校验类型

动态调试发现this. expectedType一定是null,但是this. expectedTypes是有值的,this. expectedTypes值的定义来源于InboundMsgAbbrev类,如图5.3所示。

image.png

图5.3 通过readObjectValidated来对expectedTypes赋值

赋值的依据是ABBREV_CLASSES,这是一个常量,定义如图5.4所示。也就是我们反序列化的类必须继承自ABBREV_CLASSES常量中的某个类,这就相当于是一种白名单机制,比传统的黑名单机制要狠太多了。

image.png

图5.4 对ABBREV_CLASSES常量进行定义

如果我们还是使用0x04中的exp来攻击打了2021.10补丁的环境,就会失败,并且看到异常信息。

环境三,打了2021.01补丁的环境

可以成功复现漏洞利用,exp与0x04相同。看了一下代码,发现2021.01的环境中InboundMsgAbbrev类的定义如图5.5所示。

image.png

图5.5 2021.01补丁环境中没有定义ABBREV_CLASSES常量

也就是在2021.01中并没有白名单的类型限制,所以我们仍然可以利用成功,如图5.6所示。

image.png

图5.6 在2021.01补丁环境中调用成功exp

所以,Weblogic从2021.0x的某个版本开始引入了ABBREV_CLASSES机制,该机制会限制反序列化时候的类的名称。目前不知道有什么办法可以绕过这个机制,希望知道的小伙伴能告知。

0x06 结论

目前来看这确实是一条新的反序列化利用链,但是这条利用链要成功利用还是有诸多限制,主要需要满足的条件如下:

1) 目标必须出网

2) 目标JDK版本必须小于JDK1.8.0_191

3) 目标不能采用ABBREV_CLASSES白名单机制(这大约是2021年的某次更新)

虽然利用条件有限,但是还是不能阻止这确实是一个好洞,伸手党可直接扫描下方二维码关注公众号回复“weblogic”获取漏洞研究相关工具。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值