搜尋
首頁運維安全java反序列化引發的遠端程式碼執行漏洞原理分析

java反序列化引發的遠端程式碼執行漏洞原理分析

主要有3個部分組成:

#1、Java的反省機制

2、Java的序列化處理

3、Java的遠端程式碼執行

Java的反射與程式碼執行

#我們先看個簡單的例子,使用Java呼叫計算器程式:

import java.io.IOException;
import java.lang.Runtime;
public class Test {
    public static void main(String[] args) {
        Runtime env = Runtime.getRuntime();
        String cmd = "calc.exe";        
    try {
            env.exec(cmd);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我們從java.lang套件中導入Runtime類,之後呼叫其getRuntime方法得到1個Runtime對象,該對象可以用於JVM虛擬機運行狀態的處理。接著我們呼叫其exec方法,傳入1個字串作為參數。

此時,將啟動本機上的計算器程式。

下面我們透過Java的反省機制來重寫上述的程式碼。透過Java的反省機制可以動態的呼叫程式碼,而逃過一些服務端黑名單的處理:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test {

    public static void main(String[] args) {
        try {
            Class<?> cls = Class.forName("java.lang.Runtime");            
            String cmd = "calc.exe";
            try {
                Method getRuntime = cls.getMethod("getRuntime", new Class[] {});                
                Object runtime = getRuntime.invoke(null);
                Method exec = cls.getMethod("exec", String.class);
                exec.invoke(runtime, cmd);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e1) {
            e1.printStackTrace();
        }
    }
}

上述程式碼看起來很繁瑣,實際上並不是很難。首先,透過Class.forName傳入1個字串作為參數,其傳回1個Class的實例。而其作用是根據對應的名稱找到對應的類別。

接著我們使用Class實例的getMethod方法取得對應類別的getRuntime方法,由於該類別沒有參數,因此可以將其設為null或使用匿名類別來處理。

Method getRuntime = cls.getMethod("getRuntime", new Class[] {});

之後透過得到的方法的實例的invoke方法呼叫對應的類別方法,由於沒有參數則傳入null即可。同理,我們再取得到exec方法。

Java序列化處理

對於Java中的序列化處理,對應的類別需要實作Serializable接口,例如:

import java.io.Serializable;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class Reader implements Serializable {
    private static final long serialVersionUID = 10L;    
    private void readObject(ObjectInputStream stream) {
        System.out.println("foo...bar...");
    }    public static byte[] serialize(Object obj) {        //序列化对象
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream output = null;        
    try {
            output = new ObjectOutputStream(out);
            output.writeObject(obj);
            output.flush();
            output.close();

        } catch (IOException e) {
            e.printStackTrace();
        }        return out.toByteArray();

    }    public static Object deserialize(byte[] bytes) {        //反序列化处理
        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        ObjectInputStream input;
        Object obj = null;        
    try {
            input = new ObjectInputStream(in);
            obj = input.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }        return obj;

    }    
    public static void main(String[] args) {        
    byte[] data = serialize(new Reader()); //对类自身进行序列化
        Object response = deserialize(data);
        System.out.println(response);
    }
}

在這裡我們重寫了該類別的readObject方法,用於讀取物件用於測試。其中比較重要的2個函數是serialize和deserialize,分別用於序列化和反序列化處理。

其中,serialize方法需要傳入1個物件作為參數,其輸出結果為1個位元組陣列。在該類別中,其中的物件輸出流ObjectOutputStream主要用於ByteArrayOutputStream進行包裝,之後使用其writeObject方法將物件寫入進去,最後我們透過ByteArrayOutputStream實例的toByteArray方法得到位元組數組。

而在deserialize方法中,需要傳入1個位元組數組,而傳回值為1個Object物件。與先前的序列化serialize函數類似,此時我們使用ByteArrayInputStream接收位元組數組,之後使用ObjectInputStream對ByteArrayInputStream進行包裝,接著呼叫其readObject方法得到1個Object對象,並將其傳回。

當我們執行該類別時,將得到如下的結果:

java反序列化引發的遠端程式碼執行漏洞原理分析

Java遠端通訊與傳輸

#為了實現Java程式碼的遠端傳輸及遠端程式碼執行,我們可以藉助RMI、RPC等方式。而在這裡我們使用Socket進行服務端及客戶端處理。

首先是伺服器端,監聽本地的8888端口,其程式碼為:

import java.net.Socket;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
public class Server {
    public static void main(String[] args) throws ClassNotFoundException {        
    int port = 8888;        
    try {
            ServerSocket server = new ServerSocket(port);
            System.out.println("Server is waiting for connect");
            Socket socket = server.accept();
            InputStream input = socket.getInputStream();            
            byte[] bytes = new byte[1024];
            int length = 0;            
            while((length=input.read(bytes))!=-1) {
                String out = new String(bytes, 0, length, "UTF-8");
                System.out.println(out);
            }
            input.close();
            socket.close();
            server.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我們透過傳入1個連接埠來實例化ServerSocket類,此時得到1個伺服器的socket,之後呼叫其accept方法接收客戶端的請求。此時,得到了1個socket對象,而透過socket對象的getInputStream方法取得輸入流,並指定1個長度為1024的位元組數組。

接著呼叫socket的read方法讀取那麼指定長度的位元組序列,之後透過String建構器將位元組陣列轉換為字串並輸出。這樣我們就得到了客戶端傳輸的內容。

而對於客戶端器,其程式碼類似如下:

import java.io.IOException;
import java.net.Socket;
import java.io.OutputStream;
public class Client {
    public static void main(String[] args) {
        String host = "192.168.1.108";        
        int port = 8888;
        try {
            Socket socket = new Socket(host, port);
            OutputStream output = socket.getOutputStream();
            String message = "Hello,Java Socket Server";
            output.write(message.getBytes("UTF-8"));
            output.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在客戶端,我們透過Socket物件傳遞要連接的IP位址和端口,之後透過socket物件的getOutputStream方法取得到輸出流,用於往伺服器端發送輸出。由於這裡只是演示,使用的是本地的主機IP。而在實際應用中,如果我們知道某個外網主機的IP及開放的端口,如果當前主機存在對應的漏洞,也是可以利用類似的方式來實現的。

這裡我們設定要傳輸的內容為UTF-8編碼的字串,俄日在輸出流的write方法中透過字串的getBytes指定其編碼,從而將其轉換為對應的位元組數組進行發送。

正常情況下,我們運行伺服器後再運行客戶端,在伺服器端可以得到以下輸出:

Server is waiting for connect
Hello,Java Socket Server

Java反序列化与远程代码执行

下面我们通过Java反序列化的问题来实现远程代码执行,为了实现远程代码执行,我们首先在Reader类中添加1个malicious方法,其代码为:

public Object malicious() throws IOException {
        Runtime.getRuntime().exec("calc.exe");
        System.out.println("Hacked the Server...");        
        return this;
    }

在该方法中我们使用之前的介绍调用宿主机器上的计算器程序,然后输出1个相关信息,最后返回当前类。

之后是对服务器端的代码进行如下的修改:

while((length=input.read(bytes))!=-1) {
    Reader obj = (Reader) Reader.deserialize(bytes);
    obj.malicious();
}

我们在接收到客户端对应的字符串后对其进行反序列处理,之后调用某个指定的函数,从而实现远程代码的执行。而在客户端,我们需要对其进行序列化处理:

Reader reader = new Reader();
byte[] bytes = Reader.serialize(reader);
String message = new String(bytes);
output.write(message.getBytes());

下面我们在宿主机器上运行服务器端程序,之后在本地机器上运行客户端程序,当客户端程序执行时,可以看到类似如下的结果:

java反序列化引發的遠端程式碼執行漏洞原理分析

可以看到,我们成功的在宿主机器上执行了对应的命令执行。

总结

为了实现通过Java的反序列问题来实现远程代码执行的漏洞,我们需要编写1个有恶意代码注入的序列化类。之后在客户端将恶意代码序列化后发送给服务器端,而服务器端需要调用我们期望的方法,从而触发远程代码执行。

为了避免服务器端进行一些安全处理,我们可以采用反射的方式来逃逸其处理。

这里只是1个简化的过程,更加实用的过程可以参考Apache Common Collections的问题导致的Weblogic漏洞CVE-2015-4852及Jboss的漏洞CVE-2015-7501

推荐相关文章教程:web安全教程

以上是java反序列化引發的遠端程式碼執行漏洞原理分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:freebuf。如有侵權,請聯絡admin@php.cn刪除

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!