这里学习了Weblogic中的两条CVEs,在反序列化调用readResolve
方法进而调用defineClass
方法进行类的加载,并执行我们的恶意逻辑,同样也通过寻找不同于CVE-2020-2883中使用的extract
方法,来绕过之前的CVE的黑名单过滤
参考前面的环境
这个CVE主要是找到了一个在黑名单防护外的一个类RemoteConstructor
,在该类进行反序列化调用过程中将会调用defineClass
方法进行任意类的加载,并在之后进行了实例化的操作,造成了静态代码块中的恶意代码的执行
前面在原理的部分已经讲了该CVE的漏洞触发点,我们仔细的跟进一下,从漏洞原理到POC编写来学习一下这个CVE的利用方式
对于RemoteConstructor
这个类,我们首先来看一下他的继承关系
这个类实现了ExternalizableLite
接口,且该接口继承了Serializable
接口,所以该类是能够进行反序列化方法的调用的
但是我们可以发现在该类中是不存在有readObject
方法的实现的,但是存在有readResolve
方法的实现,在反序列化的过程中将会调用这个方法
首先,我们来认识一下readObject
方法和readResolve
方法的区别
readResolve
方法用于替换从流中读取的对象,将其替换为单例模式,即是确保了没有人可以通过反序列化或者序列化单例来创建另一个实例
当ObjectInputStream从流中读取对象并准备将其返回给调用者时,将调用readResolve方法。 ObjectInputStream检查对象的类是否定义readResolve方法。如果定义了方法,则调用readResolve方法以允许流中的对象指定要返回的对象。返回的对象应该是与所有用途兼容的类型。如果不兼容,则在发现类型不匹配时将抛出ClassCastException
总结一下,就是在调用readObject
方法之后将会紧接着调用readResolve
方法,能够覆盖readObject
的内容
好了,回到代码中来
在反序列化过程中在调用readResolve
方法的时候将会调用该类的newInstance
方法
在这个方法中首先调用了getClassLoader
方法获取类加载器
也就是该类的m_loader
属性值,但是该类的这个属性值是被transient
修饰的,是不能够在反序列化中保存数据的,所以此时这里的m_loader
属性为null
所以返回的是Base.getContextClassLoader(this)
返回的类加载器
之后我们回到newInstance
方法中来,调用了返回的RemotableSupport
类对象的realize
方法
这个方法传入的参数也就是我们在序列化时的RemoteConstructor
类,在调用realize
方法的开头,调用了RemoteConstructor#getDefinition
方法获取类中的m_definition
属性值
要保证这里的这个属性值不能为null,为什么呢?因为在之后将这个属性值传入RemotableSupport#registerIfAbsent
方法中进行调用过程中,将会抛出异常
这里使用assert
断言了传入的definition
参数值不为null值
所以我们看看m_definition
属性的来源
在RemoteConstructor
类的构造方法中存在有对m_definition
属性的赋值操作,所以我们需要创建一个有效的ClassDefinition
类对象进行传入
来到ClassDefinition
类的构造方法中
也就是需要创建一个ClassIdentity
类对象和一个字节数组进行传入,具体应该传入什么,我们之后会提及
现在回到RemotableSupport#realize
方法的调用中来
经过上面的分析,已经获得了我们自定义的ClassDefinition
类对象,之后在该方法中调用了该对象的getRemotableClass
方法获取远程类,怎么获取这个远程类的呢?我们跟进看下具体的实现代码
什么?直接返回了m_clz
这个属性值!
好巧不巧,这个属性值因为被transient
所修饰,也就是值是为null的,走不通了,不能通过这里传入我们的恶意类
回到readlize
方法中
这里如果没有获取到远程类对象,将会调用RemotableSupport#defineClass
方法进行获取,传入的参数是我们自定义的ClassDefinition
对象
这里将会将definition.getId().getName()
作为远程类的类名
这里getId
方法返回的是我们在前面的ClassDefinition
构造方法中传入的第一个参数,也即是ClassIdentity
对象
而在ClassIdentity#getName
方法中
这里的getPackage
方法返回的是m_sPackage
属性值,这里的getSimpleName
方法为:
也即是将m_sBaseName
属性值+$
+m_sVersion
属性值
所以,对于getName返回的值主要就是m_sPackage/m_sBaseName$m_sVersion
这种格式的值
我们接下来可以看看这些属性值的赋值方法,来到其构造方法中来
这里我们只需要传入一个恶意类作为参数就行了,在其构造方法中将会自动获取包名 / 类名 / 将类的md5摘要值转为16进制串,之后通过重载方法进行赋值操作
分析到这里,我们在创建ClassDefinition
类对象的第一个参数就大致清楚了
ClassIdentity classIdentity = new ClassIdentity(test1.class); new ClassDefinition(classIdentity, xxxxx)
这里传入的test1类对象就是我们的恶意类对象
之后我们回到defineClass
方法中看看第二个参数应该如何构造
最后在调用defineClass
方法进行类的加载中传入的字节数组是abClass
变量,该变量的值是通过调用definition.getBytes
方法获得的
也即是返回的m_abClass
属性值,也即是在ClassDefinition
构造方法中传入的第二个参数值设定的
还有我们值得注意的一点是,在defineClass
方法返回的类对象限定了是一个实现了Remotable
接口的类,所以,在我们的恶意类中必须要实现这个接口
但是,这里还存在有一个问题,就是在将类名和字节数组传入defineClass
方法获取类的过程中,传入的类名,并不是我们的恶意类的类名
而是我们在前面提到的m_sPackage/m_sBaseName$m_sVersion
这种格式的类名,所以如果直接获取我们恶意类的字节码数组是不能够成功达到我们的恶意目的,这里我们需要将我们恶意类的类名改成对应的格式之后进行传入
直接使用javassist
进行修改
到这里,我们就可以构造出一个关键的POC代码了
ClassIdentity classIdentity = new ClassIdentity(test1.class); ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get(test1.class.getName()); ctClass.replaceClassName(test1.class.getName(), test1.class.getName() + "$" + classIdentity.getVersion()); RemoteConstructor constructor = new RemoteConstructor( new ClassDefinition(classIdentity, ctClass.toBytecode()), new Object[]{} );
在设置了远程类之后就是通过实例化来触发我们的静态代码块了,没什么好分析的
POC:
直接将上面得到的RemoteConstructor
类对象进行序列化之后通过T3协议发送即可,当然也可以使用IIOP进行利用
调用栈:
exec:347, Runtime (java.lang) <clinit>:9, test1$2C7B1D9B24203562AE41FAE89389A68A (pers.weblogic) allocateInstance:-1, Unsafe (sun.misc) allocateInstance:439, DirectMethodHandle (java.lang.invoke) newInvokeSpecial__L:-1, 112302969 (java.lang.invoke.LambdaForm$DMH) reinvoke:-1, 1487470647 (java.lang.invoke.LambdaForm$BMH) invoker:-1, 574568002 (java.lang.invoke.LambdaForm$MH) invokeExact_MT:-1, 952486988 (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:1260, ObjectStreamClass (java.io) readOrdinaryObject:2078, ObjectInputStream (java.io) readObject0:1573, ObjectInputStream (java.io) readObject:431, ObjectInputStream (java.io)