Oracle WebLogic Deserialization (CVE-2020-2883) (2)
라온화이트햇 핵심연구팀 김동민
소개
Oracle WebLogic Deserialization (CVE-2020-2883)
지난 게시글(CVE-2020-2555)에 이어 해당 취약점 패치 이후 재발견된 취약점이며, ZDI 버그 리포트를 번역한 내용입니다. 또한, 이전 게시글의 확장 버전으로 많은 내용이 생략되었기 때문에 아래 링크를 통해 먼저 확인해 주시면 감사하겠습니다.
Oracle WebLogic Deserialization CVE-2020-2555_1
Patch Bypass
- CVE-2020-2555 패치에서 아래 PoC에 대한 패치가 이루어지지 않았습니다.
BadAttributeValueExpException.readObject()
com.tangosol.util.filter.LimitFilter.toString() //<--- CVE-2020-2555 patched here
com.tangosol.util.extractor.ChainedExtractor.extract()
com.tangosol.util.extractor.ReflectionExtractor().extract()
Method.invoke()
//...
com.tangosol.util.extractor.ReflectionExtractor().extract()
Method.invoke()
Runtime.exec()
- ChainedExtractor.extract ()를 호출하는 모든 기능은 여전히 원격 코드 실행이 발생합니다. Quynh Le 보고서에 따르면 ExtractorComparator 및 AbstractExtractor 클래스를 통해 여전히 ChainedExtractor.extract()를 호출하는 것이 가능합니다. 먼저 ExtractorComparator 클래스의 compare() 메소드를 살펴 보겠습니다.
public int compare(T o1, T o2) {
Comparable a1 = (o1 instanceof InvocableMap.Entry) ? (Comparable)((InvocableMap.Entry)o1).extract(this.m_extractor)
: (Comparable)this.m_extractor.extract(o1);
Comparable a2 = (o2 instanceof InvocableMap.Entry) ? (Comparable)((InvocableMap.Entry)o2).extract(this.m_extractor)
: (Comparable)this.m_extractor.extract(o2);
if (a1 == null)
{
return (a2 == null) ? 0 : -1;
}
if (a2 == null)
{
return 1;
}
return a1.compareTo(a2);
}
-
위와 같이, this.m_extractor를 ChainedExtractor의 인스턴스로 세팅함으로써 ChainedExtractor.extract()를 호출하는 것이 가능합니다.
-
마찬가지로, AbstractExtractor 클래스의 compare() 메소드 또한 사용가능합니다.
public int compare(Object o1, Object o2) { return SafeComparator.compareSafe(null, extract(o1), extract(o2)); }
- MultiExtractor 클래스를 사용하여 ChainedExtractor.extract() 메소드를 호출할 수 있습니다.
public abstract class AbstractCompositeExtractor<T, E>
extends AbstractExtractor<T, E>
[...Truncated...]
public class MultiExtractor
extends AbstractCompositeExtractor
[...Truncated...]
public Object extract(Object oTarget) {
if (oTarget == null)
{
return null;
}
ValueExtractor[] aExtractor = getExtractors();
int cExtractors = aExtractor.length;
Object[] aValue = new Object[cExtractors];
for (int i = 0; i < cExtractors; i++)
{
aValue[i] = aExtractor[i].extract(oTarget);<-----------------------
}
return new ImmutableArrayList(aValue);
}
The Full Gadget Chain
- full gadget chain을 개발하기 위해, readObject ()에서 임의의 Comparator.compare() 메서드를 호출할 수 있어야 합니다. 이는 ysoserial gadget에서 소개한 바와 같이 PriorityQueue 클래스를 사용하는 것 입니다.
BeanShell1, Jython1, CommonsCollections2, CommonsBeanutils1, CommonsCollections4 and Groovy1
java.util.PriorityQueue.readObject()
java.util.PriorityQueue.heapify()
java.util.PriorityQueue.siftDown()
java.util.PriorityQueue.siftDownUsingComparator()
- SiftUpUsingComparator()를 통해 임의의 Comparator.compare() 메서드를 호출할 수 있습니다.
private void siftUpUsingComparator(int paramInt, E paramE) {
while (paramInt > 0) {
int i = paramInt - 1 >>> 1;
Object object = this.queue[i];
if (this.comparator.compare(paramE, object) >= 0)<----------------
break;
this.queue[paramInt] = object;
paramInt = i;
}
this.queue[paramInt] = paramE;
}
- 이를 수행하는 또 다른 방법은 아래와 같습니다.
javax.management.BadAttributeValueExpException.readObject()
com.tangosol.internal.sleepycat.persist.evolve.Mutations.toString()
java.util.concurrent.ConcurrentSkipListMap$SubMap.size()
java.util.concurrent.ConcurrentSkipListMap$SubMap.isBeforeEnd()
java.util.concurrent.ConcurrentSkipListMap.cpr()
- 요약하면, Mutations 클래스의 toString() 메서드는 ConcurrentSkipListMap.size()를 호출할 수 있습니다. ConcurrentSkipListMap.size()에서 임의의 Comparator.compare() 메서드를 호출할 수 있습니다.
ConcurrentSkipListMap$SubMap.class
public int size() {
Comparator<? super K> cmp = m.comparator;
long count = 0;
for (ConcurrentSkipListMap.Node<K,V> n = loNode(cmp);
isBeforeEnd(n, cmp); <---------------------------------------------------
n = n.next) {
if (n.getValidValue() != null)
++count;
}
return count >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)count;
}
[...Truncated...]
boolean isBeforeEnd(ConcurrentSkipListMap.Node<K,V> n, Comparator<? super K> cmp) {
....
int c = cpr(cmp, k, hi);<------------------------------------------------------
if (c > 0 || (c == 0 && !hiInclusive))
return false;
return true;
}
[...Truncated...]
static final int cpr(Comparator c, Object x, Object y) {
return (c != null) ? c.compare(x, y) : ((Comparable)x).compareTo(y); <--------
}
Demonstrating the Gadget Chains
- 위의 방법을 사용하여 ExtractorComparator을 통한 full gadget chain이 구성되었습니다.
javax.management.BadAttributeValueExpException.readObject()
com.tangosol.internal.sleepycat.persist.evolve.Mutations.toString()
java.util.concurrent.ConcurrentSkipListMap$SubMap.size()
java.util.concurrent.ConcurrentSkipListMap$SubMap.isBeforeEnd()
java.util.concurrent.ConcurrentSkipListMap.cpr()
com.tangosol.util.comparator.ExtractorComparator.compare()
- AbstractExtractor 의 경우 아래와 같은 체인이 사용되었습니다.
java.util.PriorityQueue.readObject()
java.util.PriorityQueue.heapify()
java.util.PriorityQueue.siftDown()
java.util.PriorityQueue.siftDownUsingComparator()
com.tangosol.util.extractor.AbstractExtractor.compare()
com.tangosol.util.extractor.MultiExtractor.extract()
com.tangosol.util.extractor.ChainedExtractor.extract()
//...
Method.invoke()
//...
Runtime.exec()
Conclusion
- 위 가젯을 사용하여 이전 게시글과 동일한 익스플로잇을 할 수 있으며, 이러한 Deserialize 취약점들이 여전히 많이 존재할 수 있음을 확인 하였습니다.
- 오라클 블로그에서 T3/T3S 프로토콜 트래픽을 제한하는 방법등 과 같이 임시 조치 방안이 제시되어 있습니다.
- 또한, 해당 취약점에 대한 패치는 2020년 7월 14일에 릴리즈 되었기 때문에 현재 바로 패치버전을 받을 수 있습니다.
Reference
- https://www.thezdi.com/blog/2020/5/8/details-on-the-oracle-weblogic-vulnerability-being-exploited-in-the-wild
- https://www.zerodayinitiative.com/advisories/ZDI-20-570/
- https://us-cert.cisa.gov/ncas/current-activity/2020/05/01/unpatched-oracle-weblogic-servers-vulnerable-cve-2020-2883
- https://www.zerodayinitiative.com/blog/2020/3/5/cve-2020-2555-rce-through-a-deserialization-bug-in-oracles-weblogic-server
- https://core-research-team.github.io/2020-07-01/Oracle-WebLogic-Deserialization-CVE-2020-2555