之前我们已经构造了一个通用版本的CC6利用链,为什么还需要一个CC3利用链呢?
为什么要有CC3利用链?
2015年初,@frohoff和@gebl发布了Talk《Marshalling Pickles: how deserializing objects will ruin
your day》,以及Java反序列化利⽤⼯具ysoserial,随后引爆了安全界。开发者们⾃然会去找寻⼀种安
全的过滤⽅法,于是类似SerialKiller这样的⼯具随之诞⽣。
SerialKiller是⼀个Java反序列化过滤器,可以通过⿊名单与⽩名单的⽅式来限制反序列化时允许通过的
类。在其发布的第⼀个版本代码中,我们可以看到其给出了最初的⿊名单:
这个⿊名单中InvokerTransformer赫然在列,也就切断了CommonsCollections1的利⽤链。有攻就有
防,ysoserial随后增加了不少新的Gadgets,其中就包括CommonsCollections3,代码如下:
CommonsCollections3的⽬的很明显,就是为了绕过⼀些规则对InvokerTransformer的限制。
CommonsCollections3并没有使⽤到InvokerTransformer来调⽤任意⽅法,⽽是⽤到了另⼀个
类, com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter 。
ClassLoader
目前看这些代码还有一些懵,因为缺少了一些必要的知识,下面来简单了解一下。
ClassLoader,类如其名,这是一个类加载器,用来明确如何加载一个类。
众所周知,JAVA源代码的后缀是以.java结尾的,而编译完之后是以.class为结尾的,这个.class文件就是编译后的字节码文件,每一个类都会被编译成一个class文件。而加载器定义了如何加载这些类:
以上代码是一个通过URLClassLoader加载类的Demo,通过一个URL地址去下载类文件并且加载,运行代码,可以得到如下输出:
利用ClassLoader#defineClass直接加载字节码
上一节中我们认识到了如何利用URLClassLoader加载远程class文件,也就是字节码。其实,不管是加
载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用:
其中:
loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机
制),在前面没有找到的情况下,执行 findClass
findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在
本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类。
所以可见,真正核心的部分其实是 defineClass ,他决定了如何将一段字节流转变成一个Java类,Java
默认的 ClassLoader#defineClass 是一个native方法,逻辑在JVM的C语言代码中。
我们可以编写一个简单的代码,来演示如何让系统的 defineClass 来直接加载字节码:
这个Class文件和上面通过URLClassLoader加载的类文件是一个文件,运行这段代码,同样可以看到输入:
因为系统的 ClassLoader#defineClass 是一个保护属性,所以我们无法直接在外部访问,不得
不使用反射的形式来调用。
在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我
们常用的一个攻击链 TemplatesImpl 的基石。
利用TemplatesImpl加载字节码
虽然大部分上层开发者不会直接使用到defineClass方法,但是Java底层还是有一些类用到了它(否则他
也没存在的价值了对吧),这就是 TemplatesImpl 。
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 这个类中定义了一个内部类
TransletClassLoader :
这个类里重写了 defineClass 方法,并且这里没有显式地声明访问权限。Java中默认情况下,如果一个
方法没有显式声明访问权限,那么默认为包访问权限。这也就意味着当前包中所有其他类对这个成员都有访问权限,但对于这个包之外的所有类,这个成员就是private。
我们从 TransletClassLoader#defineClass() 向前追溯一下调用链:
追到最前面两个方法 TemplatesImpl#getOutputProperties() 、
TemplatesImpl#newTransformer() ,这两者的作用域是public,可以被外部调用。我们尝试用
newTransformer() 构造一个简单的POC:
其中, setFieldValue 方法用来设置私有属性,可见,这里我设置了三个属性: _bytecodes 、 _name
和 _tfactory 。
_bytecodes 是由字节码组成的数组;
_name 可以是任意字符串,只要不为null即可;
_tfactory 需要是一个 TransformerFactoryImpl 对象,因为
TemplatesImpl#defineTransletClasses() 方法里有调用到
_tfactory.getExternalExtensionsMap() ,如果是null会出错。
另外,值得注意的是, TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类。
所以,我们需要构造一个特殊的类:
它继承了 AbstractTranslet 类,并在构造函数里插入Hello的输出。将其编译成字节码,即可被TemplatesImpl 执行了:
这里需要注意一下,被执行的字节码的类,是不能有包信息的,如果有包信息则会执行失败。
CC3利用链的实现
说了这么多前置知识,现在回头来看看CC3的具体实现:
首先可以看到没有了InvokerTransformer类,取而代之的是InstantiateTransformer类,这个类也是Transformer接口的子类,看一下这个类的定义:
可以看到这个类的transform方法调用了input的构造函数。
这就说明,关键点就在于TrAXFilter类的构造函数了:
可以看到TrAXFilter的构造函数需要一个Templates类的对象,然后调用了这个对象的newTransformer方法。看到这个方法有没有觉得眼熟?
没错,之前我们追溯的TransletClassLoader#defineClass()调用链第二个方法就是这个newTransformer方法。
通过调用这个方法,就可以执行任意字节码。接下来我们按照CC3的方式构造一个POC,完整代码如下:
这里利用的字节码还是跟之前的Hello类一样,运行这段代码,得到如下输出:
这段代码只是为了验证利用链的可行性,并没有加入反序列化方面的代码,看一下CC3利用链的反序列化代码如下:
可以看到,反序列化方面还是和CC1利用链是一样的代码,这种方式在JDK8U71之后就无法使用了,原因之前已经分析过了这里就不在赘述了。
构建通用的POC
不过有之前分析CC6的经验,我们可以用CC6的方法自己改造一个通用的POC,代码如下:
运行代码得到如下输出:
总结
该篇文章主要参考P牛Java安全漫谈。
CC3利用链重点在于理解TemplatesImpl通过ClassLoader加载字节码,理解了这一点其他的和之前的链差不多。