主要有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遠(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é)果:
可以看到,我們成功的在宿主機(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