文章首发于:https://mp.weixin.qq.com/s/8678EM15rZSeFBHGDfPvPQ
CVE-2020-2883 将
extract
方法存在危险操作的MvelExtractor
和ReflectionExtractor
两个类加入到了黑名单中,因此我们只需要继续找一个extract
方法存在危险操作的类即可绕过补丁,这里找到的是 Weblogic 12.2.1.4.0 Coherence 组件特有的类com.tangosol.util.extractor.UniversalExtractor
,因此只能打 Weblogic 12.2.1.4.x。
漏洞复现
影响范围
Oracle WebLogic Server 12.2.1.4.0
漏洞报告
入口同 CVE-2020-2883,利用 java.util.PriorityQueue
反序列化时会间接调用 com.tangosol.util.ValueExtractor
接口任意实现类的 extract
方法。 在 CVE-2020-2883 中我们可以使用
com.tangosol.coherence.rest.util.extractor.MvelExtractor
的 extract
方法执行mvel表达式或者使用 com.tangosol.util.extractor.ReflectionExtractor
的 extract
方法反射调用任意方法。但是补丁把他们都加入到了黑名单中,因此我们需要再找一个 extract
方法有危险操作且实现了 com.tangosol.util.ValueExtractor
接口的类。 我这里找到的是 com.tangosol.util.extractor.UniversalExtractor
1 | public class UniversalExtractor<T, E> extends AbstractExtractor<T, E> implements ValueExtractor<T, E>, ExternalizableLite, PortableObject { |
看其 extract
方法,虽然if条件中有一个 invoke
操作,但是 this.m_cacheTarget
使用了 transient
修饰导致无法被序列化。跟进else条件中的
extractComplex
方法,该方法中也有一个 invoke
操作,虽然我们可以控制参数 oTarget
和 aoParam
,但是 method
对象的获取过程也有一个if条件。ClassHelper.findMethod
方法通过类、方法名以及方法的参数类型数组来反射返回该类中的特定方法。因此只要我们进入else条件中,即可调用任意类的任意方法。 进入else条件的前提是
this.isPropertyExtractor()
返回false,也就是 this.m_fMethod
为true,但是该成员变量依然使用 transient
修饰,无法序列化,因此我们只能寄希望于if条件中。
1 | public boolean isPropertyExtractor() { |
看下if条件
1 | protected E extractComplex(T oTarget) throws InvocationTargetException, IllegalAccessException { |
跟进 sCName
的赋值过程 this.getCanonicalName()
,因为this对象的原因,Lambdas.getValueExtractorCanonicalName
无论如何都会返回null。 接着看下
CanonicalNames.computeValueExtractorCanonicalName
,如果 aoParam
不为 null
且数组长度大于0就会返回 null
,因此我们可调用的方法必须是无参的。接着如果方法名 sName
不以 ()
结尾,则直接返回方法名。否则会判断方法名是否以 VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES
数组中的前缀开头,是的话就会截取掉并返回。 回到
extractComplex
方法中,在if条件里会对上面返回的方法名做首字母大写处理,然后拼接 BEAN_ACCESSOR_PREFIXES
数组中的前缀判断 clzTarget
类中是否含有拼接后的方法,此时会发现我们现在无论如何只能调用任意类中 get
和 is
开头的方法,并且是无参的,条件有些苛刻。 不过仔细观察发现
UniversalExtractor#extract
方法可以调用两次,我们可以利用第一次调用改变 UniversalExtractor
对象的关键成员变量值,在第二次调用时完成利用。 接下来我想了三个思路:
- 直接去找所有类中
get
和is
开头并且可利用的无参方法 - 想办法调用
init
方法对this.m_fMethod
进行赋值,从而令fProperty
的值为false并进入else条件中。
-
extractComplex
方法中对this.m_cacheTarget
进行了赋值,因此我们第二次调用UniversalExtractor#extract
方法时可以进入到if条件中执行targetPrev.getMethod().invoke()
。
由于我们只能调用
get
和 is
开头的方法,因此思路2不行。由于我们在利用 targetPrev.getMethod().invoke()
调用任意方法时,传入的参数和 extractComplex
方法中 findMethod
的参数是同一个,导致如果我们调用非 get
和 is
开头的方法时,findMethod
会返回 null
,从而在 method.invoke
时会报错,这样的话就无法第二次调用 UniversalExtractor#extract
方法,因此思路3也不行。 最终只能用思路1去全局搜 get
和 is
开头且存在危险操作的无参方法。虽然只能调用无参方法,但是我们可以通过序列化控制对象的成员变量来获取一些可控点,当然该方法所在的类必须是可序列化的,另外该方法的危险操作涉及到的对象依然需要可序列化,否则无法利用。 这个寻找过程和fastjson有些相似,最终找到了 com.sun.rowset.JdbcRowSetImpl
这个jdk内置类,看下其 getDatabaseMetaData()
方法。 跟进
this.connect()
,可以发现只要 this.getDataSourceName()
可控,我们就能进行JNDI注入。getDataSourceName()
调用的是父类 javax.sql.rowset.BaseRowSet
的,其 dataSource
属性是可序列化的,因此我们可以控制 this.getDataSourceName()
的返回值。
1 | private String dataSource; |
最终poc
1 | import java.io.*; |
由于Weblogic在JEP290机制下做的是全局反序列化过滤,而JNDI在jdk高版本依赖于本地Class的反序列化链,因此在Weblogic中JNDI注入只能打 JDK6u211、7u201、8u191
之前的版本。
漏洞修复
p31537019_122140_Generic.zip:https://updates.oracle.com/Orion/Services/download/p31537019_122140_Generic.zip?aru=23654622&patch_file=p31537019_122140_Generic.zip 可以看到最新补丁的黑名单中不仅过滤了 com.tangosol.util.extractor.UniversalExtractor
,还过滤了 com.tangosol.util.extractor
包下的其他几个类,另外也把com.tangosol.coherence.rest.util.extractor
、com.tangosol.internal.util.invoke.lambda
等一些包加入了黑名单中。