Java安全之ysoserial-JRMP模塊分析(一)
首發安全客:Java安全之ysoserial-JRMP模塊分析(一)
0x00 前言
在分析到Weblogic後面的一些繞過方式的時候,分析到一半需要用到ysoserial-JRMP該模塊。不止是Weblogic的反序列化漏洞會利用到,其他的反序列化漏洞也會利用到,所以在此對該模塊做一個分析。瞭解底層原理,一勞永逸。但看到網上分析文章偏少,如有分析錯誤望師傅們指出。
概述
在這裏簡單來講講JRMP協議相關內容,JRMP是一個Java遠程方法協議,該協議基於TCP/IP之上,RMI協議之下。也就是說RMI該協議傳遞時底層使用的是JRMP協議,而JRMP底層則是基於TCP傳遞。
RMI默認使用的JRMP進行傳遞數據,並且JRMP協議只能作用於RMI協議。當然RMI支持的協議除了JRMP還有IIOP協議,而在Weblogic裏面的T3協議其實也是基於RMI去進行實現的。
RMI內容,具體參考:Java安全之RMI協議分析
0x01 JRMP模塊利用
一、 ysoserial中的exploit/JRMPClient
是作爲攻擊方的代碼,一般會結合payloads/JRMPLIstener
使用。
攻擊流程如下:
- 需要發送
payloads/JRMPLIstener
內容到漏洞服務器中,在該服務器反序列化完成我們的payload後會開啓一個RMI的服務監聽在設置的端口上。 - 我們還需要在我們自己的服務器使用
exploit/JRMPClient
與存在漏洞的服務器進行通信,並且發送一個gadgets對象,達到一個命令執行的效果。(前面說過RMI協議在傳輸都是傳遞序列化,接收數據後進行反序列化操作。)
簡單來說就是將一個payload發送到服務器,服務器反序列化操作該payload過後會在指定的端口開啓RMI監聽,然後通過exploit/JRMPClient
去發送攻擊 gadgets對象。
二、第二種利用方式和上面的類似exploit/JRMPListener
作爲攻擊方進行監聽,在反序列化漏洞位置發送payloads/JRMPClient
向我們的exploit/JRMPListener
進行連接,連接後會返回在exploit/JRMPListener
的gadgets對象並且進行反序列化
攻擊流程如下:
-
攻擊方在自己的服務器使用
exploit/JRMPListener
開啓一個rmi監聽 -
往存在漏洞的服務器發送
payloads/JRMPClient
,payload中已經設置了攻擊者服務器ip及JRMPListener監聽的端口,漏洞服務器反序列化該payload後,會去連接攻擊者開啓的rmi監聽,在通信過程中,攻擊者服務器會發送一個可執行命令的payload(假如存在漏洞的服務器中有使用org.apacje.commons.collections
包,則可以發送CommonsCollections
系列的payload),從而達到命令執行的結果。
在前文中的 Java 安全之Weblogic 2017-3248分析文章中,用到的時候第二種方式進行繞過補丁。前文中並沒有對該模塊去做分析,只是知道了利用方式和繞過方式,下面對JRMP模塊去做一個深入的分析。查看內部是如何實現該功能的。
0x01 payloads/JRMPListener
該鏈的作用是在反序列化過後,在指定端口開啓一個JRMP Server。後面會配合到exploit/JRMPClient
連接並且發送payload。
利用鏈
下面來看一下他的利用鏈
/**
* Gadget chain:
* UnicastRemoteObject.readObject(ObjectInputStream) line: 235
* UnicastRemoteObject.reexport() line: 266
* UnicastRemoteObject.exportObject(Remote, int) line: 320
* UnicastRemoteObject.exportObject(Remote, UnicastServerRef) line: 383
* UnicastServerRef.exportObject(Remote, Object, boolean) line: 208
* LiveRef.exportObject(Target) line: 147
* TCPEndpoint.exportObject(Target) line: 411
* TCPTransport.exportObject(Target) line: 249
* TCPTransport.listen() line: 319
*
* Requires:
* - JavaSE
*
* Argument:
* - Port number to open listener to
*/
構造分析
首先需要查看一下yso裏面是如何生成gadget對象的。
可以直接定位到getObject方法中。
getObject方法中前面第一行代碼獲取了外部傳入進來的端口,轉換成int類型。
這個比較簡單,主要內容在下面這段代碼中。
使用Reflections.createWithConstructor
方法傳入三個參數獲取到一個UnicastRemoteObject
的實例對象。傳入的參數第一個是ActivationGroupImpl.class
,第二個是RemoteObject.class
,而第三個則是一個Object的數組,數組中裏面是RemoteRef.class
,第四個是UnicastServerRef
傳入了剛剛獲取的端口的一個實例對象。
第一個參數使用的是 ActivationGroupImpl 是因爲在利用的時候,本身就是利用的 UnicastRemoteObject 的 readObject 函數,第二個參數需要滿足兩個條件:
-
要爲 UnicastRemoteObject 的父類
-
不能在創建的過程中有其他什麼多餘的操作,滿足這兩個條件的兩個類是:RemoteObject、RemoteServer
最後具體是怎麼獲取到的UnicastRemoteObject
實例對象,這裏需要調試跟蹤一下。
UnicastServerRef分析
在此之前,先來看看new UnicastServerRef(jrmpPort)
的內部實現。先跟蹤最裏層的方法。
UnicastServerRef
的構造方法,內部會去再new一個LiveRef對象並且傳入輸入進來的端口的參數。
選擇跟蹤。
內部是new了一個ObjID,繼續跟蹤。
裏面還會去new一個UID賦值給space成員變量,UID這裏自然都知道是啥意思,這裏就不跟了,而下面隨機獲取一個值賦值給objNum。
ObjID
-
ObjID
用於標識導出到RMI運行時的遠程對象。 導出遠程對象時,將根據用於導出的API來隱式或明確地分配一個對象標識符。 -
構造方法:
ObjID() 生成唯一的對象標識符。 ObjID(int objNum) 創建一個“衆所周知”的對象標識符。
執行完成後返回到這一步。
這裏調用了構造方法的重載方法。選擇跟蹤一下。
到了這一步,var1的參數自然不用解釋,而後面的則是傳入的端口。
裏面再一次調用重載方法,並且在傳遞的第二個參數調用了TCPEndpoint.getLocalEndpoint
並且傳入端口進行獲取實例化對象。繼續跟蹤。
內部調用getLocalEndpoint
重載方法,跟蹤。
getLocalEndpoint
方法說明:
獲取指定端口上本地地址空間的終結點。如果端口號爲0,則返回共享的默認端點對象,其主機名和端口可能已確定,也可能尚未確定。
內部調用localEndpoints.get
方法並且傳入var5,也就是TCPEndpoint的實例對象。
localEndpoints是一個map類型的類對象,這裏get方法獲取了var5,對應的value值,類型爲LinkedList。這裏獲取到的是一個null。
執行到下一步
調用resampleLocalHost方法獲取String的值,跟蹤查看實現。
localHost的值是通過getHostnameProperty方法進行獲取的。
執行完成後,返回到sun.rmi.transport.tcp#TCPEndpoint
,執行到一下代碼中。
這裏的代碼比較容易理解,var爲空,new一個TCPEndpoint對象,並且傳入var7,var0,var1,var2。參數值是ip,端口,null,null。將該對象添加到var6裏面。
後面則是對var3的對象進行賦值,ip和端口都賦值到var3的成員變量裏面去。
最後就是調用localEndpoints.put(var5, var6);
講var5, var6存儲到localEndpoints
中。
最後進行返回var3對象。
執行完成後,回到這裏
繼續跟蹤,構造方法的重載方法。
這裏就沒啥好說的了,就是賦值。
最後返回到外面入口的地方
調用了父類的構造方法
到了這裏其實就已經跟蹤完了。
yos利用鏈分析
返回到這一步跟蹤Reflections.createWithConstructor
查看內部實現。
簡化一下代碼:
Constructor<? super T> objCons = RemoteObject.class.getDeclaredConstructor(new UnicastServerRef(jrmpPort));
其實也就是反射調用獲取 RemoteObject參數爲UnicastRef的構造方法。並且傳遞new UnicastServerRef(jrmpPort)
實例化對象作爲構造方法參數。
而下面的setAccessible(objCons);
這個就不做分析了,分析過前面的利用鏈都大概清楚,這個其實就是修改暴力反射的一個方法類。
看到下面這段代碼
這裏進行跟蹤。
其實藉助ReflectionFactory.getReflectionFactory()
工廠方法在這裏就是返回了ReflectionFactory的實例對象。
跟蹤newConstructorForSerialization
方法
這裏傳遞的var1 參數是ActivationGroupImpl.class
對象,而var2是剛剛反射獲取的Constructor
對象。
下面是個三目運算,如果var2.getDeclaringClass() == var1
的話,返回var2,如果不低於的話,調用this.generateConstructor(var1, var2);
後的執行結果進行返回。
將代碼簡單化:
ActivationGroupImpl.class.getDeclaringClass()==ActivationGroupImpl.class ? var2
:this.generateConstructor(ActivationGroupImpl.class, var2)
這裏調用了this.generateConstructor
方法並且傳入了兩個參數。後來才發現後面的這些內容是屬於反射的底層實現,跟蹤跑偏了。感興趣的師傅們可以自行查看。
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
返回到這段代碼,後來的查詢資料發現newConstructorForSerialization
這個方法返回的是一個無參的constructor對象,但是絕對不會與原來的constructor衝突,被稱爲munged 構造函數
這裏先來思考到一個問題,爲什麼不能使用反射直接調用呢?
其實並非所有的java類都有無參構造方法的,並且有的類的構造方法還是private的。所以這裏採用這種方式進行獲取。
再來看到上面的代碼:
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
前面參數爲ActivationGroupImpl.class
,指定獲取ActivationGroupImpl.class
的 Constructor。後面的參數爲反射獲取RemoteObject的RemoteRef類型構造方法獲取到的Constructor類。
最後將參數傳遞進行,返回創建一個ActivationGroupImpl
實例化對象。
執行完成回到這個方法內,發現該地方對ActivationGroupImpl
進行了向上轉型爲UnicastRemoteObject
類型
最後調用反射將UnicastRemoteObject
的實例對象的port字段修改成我們設置的端口的值。
0x02 調試分析
test類:
package ysoserial.test;
import ysoserial.payloads.JRMPClient;
import ysoserial.payloads.JRMPListener;
import java.io.*;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class test {
public static void main(String[] args) throws Exception {
JRMPListener jrmpListener = new JRMPListener();
UnicastRemoteObject object = jrmpListener.getObject("9999");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream bjos = new ObjectOutputStream(bos);
bjos.writeObject(object);
ByteArrayInputStream bait = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ojis = new ObjectInputStream(bait);
Object o = ojis.readObject();
}
}
這裏是利用了UnicastRemoteObject
的readObject
作爲反序列化的入口點。
在此處下斷點開始調試分析。
readObject
方法處調用了reexport
方法,跟蹤查看。
csf和ssf爲空,執行到這裏。
調用exportObject
方法並且傳入this和port 這裏的this,實際上是ActivationGroupImpl
,因爲前面進行了向上轉型。跟蹤exportObject
。
這裏再次調用重載方法,跟蹤查看。
到了這一步,調用sref.exportObject
傳入前面創建的實例對象。跟蹤。
這裏下面調用this.ref,而this.ref爲LiveRef對象。這一段則是調用LiveRef.exportObject
。繼續跟蹤。
this.ep爲Endpoint對象,這裏調用的是Endpoint.exportObject
,這裏的對象是怎麼賦值的前面的構造分析的時候去講過,這裏不做多的贅述。
調用this.transport.exportObject;
繼續跟蹤。
到了這一步就調用了this.listen()
進行啓動監聽。
參考文章
ysoserial JRMP相關模塊分析(一)- payloads/JRMPListener
0x03 結尾
JRMP的這個模塊第一次分析還是挺費勁的,網上的相關資料也偏少。