今年早些时候,作者在博客中公开了一个Oracle WebLogic Server中的反序列化漏洞。此漏洞是由Oracle补丁的,并分配了CVE-2020-2555。但是,VNPT ISC的研究员Quynh Le向ZDI提交了一个漏洞,该漏洞表明了可以绕过补丁利用此漏洞。Oracle 已通报此标记为CVE-2020-2883的漏洞可用于攻击。在此博客文章中,我们将详细介绍此最新补丁中的漏洞。
https://www.zerodayinitiative.com/blog/2020/3/5/cve-2020-2555-rce-through-a-deserialization-bug-in-oracles-weblogic-server https://www.us-cert.gov/ncas/current-activity/2020/05/01/unpatched-oracle-weblogic-servers-vulnerable-cve-2020-2883 https://www.zerodayinitiative.com/advisories/ZDI-20-570/
CVE-2020-2555的原始补丁程序未修补以下gdaget利用链的部分:
BadAttributeValueExpException.readObject() com.tangosol.util.filter.LimitFilter.toString() // <--- CVE-2020-2555在此处补丁 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的报告显示,ChainedExtractor.extract()仍然可以通过ExtractorComparator和AbstractExtractor类进行访问。我们从查看compare()方法ExtractorComparator:开始分析
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); }
如上所示,仍然可以通过设置ChainedExtractor.extract()为this.m_extractor的实例来调用ChainedExtractor。
同样,compare()也可以使用AbstractExtractor抽象类的方法。
public int compare(Object o1,Object o2){ return SafeComparator 。compareSafe(null,extract(o1),extract(o2)); }
MultiExtractor类扩展AbstractExtractor可以到达ChainedExtractor.extract():
public abstract class AbstractCompositeExtractor extends AbstractExtractor [...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); }
为了开发一个完整的gadget利用链,我们需要有调用compare()的能力,从方法Comparator到达readObject()。使用的公开记录的方法有PriorityQueue,有如下gadget类:BeanShell1,Jython1,CommonsCollections2,CommonsBeanutils1,CommonsCollections4和Groovy1:
java.util 。PriorityQueue 。readObject() java.util 。PriorityQueue 。heapify() java.util 。PriorityQueue 。siftDown() java.util 。PriorityQueue 。siftDownUsingComparator()
SiftUpUsingComparator()可以调用compare()任意方法Comparator:
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 。变异。toString() java.util.concurrent 。ConcurrentSkipListMap $ SubMap 。尺寸() java.util.concurrent 。ConcurrentSkipListMap $ SubMap 。isBeforeEnd() java.util.concurrent 。ConcurrentSkipListMap 。cpr()
总而言之,toString()Mutations类的方法可能导致调用ConcurrentSkipListMap.size():
ConcurrentSkipListMap$SubMap.class public int size() { Comparator cmp = m.comparator; long count = 0; for (ConcurrentSkipListMap.Node n = loNode(cmp); isBeforeEnd(n, cmp); = Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)count; } [...Truncated...] boolean isBeforeEnd(ConcurrentSkipListMap.Node n, Comparator cmp) { .... int c = cpr(cmp, k, hi); 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); <-------- }
从ConcurrentSkipListMap.size()中可以调用compare()任意方法Comparator。
通过使用上述方法,针对ExtractorComparator编译了以下完整的gadget链:
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()
以下视频演示了此gadget链用于通过T3协议获得RCE。
https://www.youtube.com/watch?v=HM3Z-I998b4
对于该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()
以下视频演示了此gadget链用于通过T3协议获得RCE:
https://youtu.be/juIucTRZUL8
应当注意,此漏洞位于 Coherence 库中。 在其代码路径中具有反序列化路径的任何具有Coherence 库的应用程序也容易受到攻击。一个产品示例是Oracle Business Intelligence,它部署在Oracle WebLogic上。
可以将这些gadget链与 CVE-2020-2950 / ZDI-20-505 结合使用,该工具由GreenDog的研究人员报告给ZDI,以通过HTTP实现远程代码执行。
此漏洞位于BIRemotingServlet中,会侦听TCP端口7780,并且不需要任何身份验证:
BIRemotingServlet oracle.bi.nanserver.fwk.servlet.as.BIRemotingServlet 1 BIRemotingServelet /messagebroker/as/* BIRemotingServlet /messagebroker/cs/*
BIRemotingServlet 使用AMF(操作消息格式)与客户端进行通信。
protected void handleRequest(HttpServletRequest paramHttpServletRequest, HttpServletResponse paramHttpServletResponse) throws ServletException, IOException { [...Truncated...] RemotingSvs remotingSvs = BISvsManagerBase.getRemotingSvs(); <---------------------------------------------------- remotingSvs.processCall(); <-------------------------------------------------------------------------------------------- setContentType(paramHttpServletResponse, OutputForm.AMF3); paramHttpServletResponse.setContentLength(byteArrayOutputStream.size()); byteArrayOutputStream.writeTo(paramHttpServletResponse.getOutputStream()); paramHttpServletResponse.flushBuffer(); } public int processCall() throws BISvsException { [...Truncated...] AMF3Packet aMF3Packet1 = deserializePacket(dataInputStream); <------------------------------------------------------------ if (logger.isLoggable(Level.FINE)) { logger.fine("De-serialized request packet: " + aMF3Packet1.toString()); } [...Truncated...] } oracle.bi.nanserver.fwk.util.remoting.RemotingSvsImpl.class public AMF3Packet deserializePacket(DataInputStream paramDataInputStream) throws BISvsException { try { AMFObjectInput aMFObjectInput = getAMF3DeSerializer(paramDataInputStream); <------------------------------------------------ LegacyObjectInput legacyObjectInput = new LegacyObjectInput(paramDataInputStream, aMFObjectInput); AMF3Packet aMF3Packet = new AMF3Packet(); aMF3Packet.deserialize(legacyObjectInput); <---------------------------------------------------------------------------------- return aMF3Packet; } catch (Exception exception) { handleException(exception); return null; } } public AMFObjectInput getAMF3DeSerializer(DataInputStream paramDataInputStream) throws BISvsException { try { Class clazz = (Class)amf3DeSerializerClass.get(); if (clazz == null) { String str = (String)BISvsManagerBase.getContextSvs().getValue("amf3DeSerializer"); if (str == null || str.trim().length() == 0) { clazz = oracle.bi.nanserver.fwk.util.amf.AMF3ObjectInput.class; <-------------------------------------------------------- amf3DeSerializerClass.compareAndSet(null, clazz); logger.info("Using default AMF3 De-Serializer"); [...Truncated...] }
如图所示,当对AMF数据包进行反序列化时,可以通过调用AMF3ObjectInput来重建任意对象readComplexObject()。
protected Object readComplexObject(GenericTypeInfo paramGenericTypeInfo) throws ClassNotFoundException, IOException { try { int i = readAMF3IntegerVal(); if ((i & true) == 0) { return getVisitedObject(i >> 1); } ClassMetadata classMetadata = readClassMetadata(i); String str = this.proxySvs.getConcreteClassName(classMetadata.name); if (str == null) { str = classMetadata.name; } // CVE-2020-2950 patch //if (isBlacklisted(str)) //{ // throw new SecurityException("Unsupport class type:" + str); //} Class clazz = Class.forName(str); ClassProxy classProxy = this.proxySvs.getProxy(clazz); Object object1 = classProxy.newInstance(clazz); int j = this.objectRefList.size(); markObjectVisited(object1); if (classMetadata.externalizable) { if (paramGenericTypeInfo != null) { classProxy.readExternal(new GenericResult(object1, paramGenericTypeInfo), this); } else { classProxy.readExternal(object1, this); } } else if (clazz == oracle.bi.nanserver.fwk.util.remoting.messages.RemotingMessage.class) { populateRemotingMessage(object1, classMetadata, classProxy); } else { String[] arrayOfString = classMetadata.getFieldNames(); Object[] arrayOfObject = new Object[arrayOfString.length]; for (byte b = 0; b < arrayOfString.length; b++) { arrayOfObject[b] = readObject(); } this.proxySvs.setFieldValues(object1, arrayOfString, arrayOfObject, classProxy); if (classMetadata.dynamic) { while (true) { String str1 = readAMF3String(); if (str1 == null || str1.length() == 0) { break; } Object object = readObject(); this.proxySvs.setFieldValue(object1, str1, object, classProxy); } } }
在此示例中,UnicastRef重构了一个对象,从而导致对远程对象的服务器端分布式垃圾回收器的调用,从而使我们能够响应任意的序列化对象。如上所述响应gadget链之一会产生RCE。
有关在Java AMF实现中利用Java反序列化的更多详细信息,请参阅 Code White的 这篇文章。gadget链已添加到 ysoserial中 ,并且使用JRMP侦听器来利用此漏洞。
https://codewhitesec.blogspot.com/2017/04/amf.html https://github.com/frohoff/ysoserial
以下视频演示了这一操作:
https://www.youtube.com/embed/h1c0sfVZNO8
有关Java反序列化漏洞的更多详细信息,请参阅Moritz Bechler的 白皮书 。Oracle的 博客 没有说明攻击的广泛性,但是其指导很明确:需要立即打补丁。他们还提供有关如何限制Oracle WebLogic Server的T3 / T3S协议流量的指南。Oracle补丁程序的下一个版本计划于2020年7月14日发布,我们将持续关注在该更新之后还剩下多少反序列化漏洞。
https://github.com/mbechler/marshalsec/blob/master/marshalsec.pdf https://blogs.oracle.com/security/apply-april-2020-cpu