使用自定义ClassLoader解决反序列化serialVesionUID不一致问题
0x01 背景
serialVesionUid
不一致导致反序列化失败也算是Java反序列化漏洞利用比较常见的问题了。查了下资料,发现了各种各样的方法,但没有找到一种适合所有gadget的通用解决方案,为此我花了一些时间,算是找到了自己心中比较完美的解决方案:自定义ClassLoader。目前已经将其集成到ysoserial中,可完美解决各类gadget serialVesionUID不一致问题。
0x02 各方案的优劣
在解决这个问题之前,我尝试的很多方法,简单说下它们各自能解决的问题和存在的缺陷。
方案1:修改序列化byte数据
该方法可解决序列化最终数据的serialVesionUID不一致,但无法解决Object的serialVesionUID不一致
方案2:反射修改serialVesionUID
可以解决1的缺陷,但无法解决Gadget依赖的class没有serialVesionUID属性的情况,因为反射只能修改Object的属性,不能添加。
方案3:修改Class字节码,添加或修改serialVesionUID
能解决Gadget直接依赖Class的serialVesionUID不一致问题,可弥补方案2的缺陷。但不好解决Gadget间接依赖class存在serialVesionUID不一致的情况。
方案4:Hook ObjectStreamClass.getSerialVesionUID()
该方法负责返回所有参与序列化Class的serialVesionUID,Hook它并修改返回值,可解决所有class的serialVesionUID不一致问题。但它无法解决Gadget依赖jar版本之间,class差异较大,属性类型不同的情况。因为serialVesionUID发生改变取决于两个因素:Class的属性和方法。如果属性类型改变了,单单只修改serialVesionUID是不够的。
方案5:URLClassLoader
使用URLClassLoader动态引入依赖jar可以很好的解决以上方案的缺陷。只是用在该场景下有些费劲,原因有三:
第一,不方便隔离依赖。包含serialVesionUID不一致class的jar(这里简称
不一致jar
)是需要被隔离的。由于URLClassLoader是双亲委派模式,存在被父ClassLoader中的同名Class覆盖的风险。
第二,不方便共享依赖。Gadget依赖的部分jar可能不存在serialVesionUID不一致问题(这里简称
可共用jar
),我们需要共享。
第三,不方便添加Class到ClassLoader中,URLClassLoader只提供添加jar的方法。
0x03 自定义ClassLoader解决方案
在我看来比较完美的方案不仅要解决以上方案的缺陷,还要能防止各种未知的”副作用”。使用ClassLoader来解决的思路肯定是没错,但我们需要结合解决serialVesionUID不一致问题这个场景量身设计一个ClassLoader,核心有两点:
- 改双亲委派为当前ClassLoader优先,方便隔离不一致jar共享可共用jar
- 方便添加Class和Jar到ClassLoader中
那么自定义ClassLoader是如何解决serialVesionUID不一致问题的呢?
自定义ClassLoader可以很方便地切换不一致jar
为漏洞环境的对应版本,生成的发序列化数据自然不会存在serialVesionUID不一致问题。具体实现如下图,我们自定义ClassLoader包含了Gadget class和不一致jar。当Gadget class实例化生成序列化对象时,由于当前ClassLoader优先原则,存在不一致问题的class使用的是自定义ClassLoader加载的,实现隔离。而其他Class找不到,自然走双亲委派模式,去父ClassLoader中查找,实现共享。
下面我们分别来实现。
0x04 addClass && addJar
首先我们自定义的ClassLoader需要维护要一个装载Class的Map classByteMap
,类名
为键
,类文件byte数据
为值
。方便后续添加和获取Class。
1 | private Map<String, byte[]> classByteMap = new HashMap<String,byte[]>(); |
addClass方法,主要是为了方便我们我们把Gadget对应的class添加的自定义ClassLoader中。
1 | public void addClass(String className,byte[] classByte){ |
addJar方法,主要是为了方便把gadget的不一致jar快速添加到ClassLoader中。具体来说就是读取不一致jar中所有class的class name
和class byte
,存储到classByteMap
中。
1 | private void readJar(JarFile jar) throws IOException{ |
0x05 改双亲委派为自定义ClassLoader优先
要想打破双亲委派,我们需要重新loadClass方法,修改加载逻辑为优先使用自定义ClassLoader加载。
1 |
|
findClass方法定义的是自定义ClassLoader查找Class的逻辑
1 |
|
0x06 编写版本兼容gadget
依然以ysoserial CommonsBeanutils1
为例子。ysoserial中默认commons-beanutils是1.9.2版本,下面我们给它添加一个兼容1.8.3版本的CommonsBeanutils1_183
。
通过对比1.9.2和1.8.3序列化数据,发现serialVesionUID不一致的只有org.apache.commons.beanutils.BeanComparator
类,它在commons-beanutils-<version>.jar
中,剩余的commons-collections-3.1.jar
和commons-logging-1.2.jar
为可共用jar。
接着就可以编写代码,调用自定义ClassLoader SuidClassLoader来解决serialVesionUID不一致问题了。
1 | "commons-beanutils:commons-beanutils:1.8.3", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"}) ({ |
Weblogic coherence.jar的gadget可如法炮制。近期忙完会将完整的代码上传到github项目ysoserial-woodpecker