Java JNDI注入深度解析
本文介绍了Java命名和目录接口(JNDI)的基本概念及其功能。JNDI允许应用程序通过名称查找资源和其他程序对象,并支持多种服务如JDBC、LDAP和RMI等。文章详细解释了命名服务和目录服务的区别及用途,并提供了使用RMI和DNS的示例代码。此外,还讨论了动态协议切换及初始化上下文的过程。 2025-9-6 16:41:3 Author: www.freebuf.com(查看原文) 阅读量:0 收藏

概念

JNDI(Java Naming Directory Interface),Java命名和目录接口,是SUN公司提供的一种标准的Java命名系统接口。通过调用JNDI的API应用程序可以定位资源和其他程序对象。

JNDI可访问的现有目录及服务包括:JDBC(Java数据库连接)、LDAP(轻型目录访问协议)、RMI(远程方法调用)、DNS(域名服务)、NIS(网络信息服务)、CORBA(公共对象请求代理系统结构)

命名服务/目录服务

JNDI包括命名服务(Naming Service)和目录服务(Directory Service)。

命名服务

是一种通过名称来查找实际对象的服务。例如在RMI中,Naming.lookup方法通过查找名称来获取远程对象的代理类

相关概念:

  • Name:名称。要么在命名系统中查找对象,需要提供对象的名称,如java:comp/env/jdbc/DataSource

  • Naming Convention:命名规范。一个命名系统中的所有名称必须遵循的语法规范

  • Binding:绑定。一个名称和一个对象的关联,如bind("payService", paymentObj)

  • Reference:引用。一些命名服务系统不是直接存储对象,而是保存对象的引用。引用包含了如何访问实际对象的信息。如<Reference class="com.Evil" url="http://attacker/"/>

  • Address:地址。引用通常用一个或多个地址(通信端口)来表示,如ldap://192.168.1.1:389

  • Context:上下文。一个上下文是一系列名称和对象的绑定的集合。一个上下文中的名称可以绑定到一个具有相同命名规范的上下文中,称之为子上下文(subcontext)。例如:在文件系统中,/usr是一个Context,/usr/bin是usr的subcontext

  • Naming System:命名系统。一个相同类型的上下文集合,例如整个 DNS 系统

  • Namespace:命名空间。一个命名系统的所有名称的集合,例如DNS的所有域名

  • Atomic Name:原子名。一个简单基本结构,如文件名config.txt

  • Compound Name:混合名。由多个原子名一起构成的名称,如文件路径/home/user/.bashrc

  • Composite Name:复合名称。是跨越多个命名系统的名称,如ldap://ldap.com/cn=user,dc=com+file:/shared.txt

目录服务

是命名服务的扩展,除了提供名称和对象的关联,还允许对象具有属性。目录服务中的对象称为目录对象,目录对象可以跟属性关联,一个目录是由相关联的目录对象组成的系统。目录服务提供创建、添加、删除目录对象以及修改目录对象属性等操作。

相关概念:

  • Attribute:属性。一个目录对象可以包含属性。一个属性具有一个属性标识符和一系列属性值

  • Search Filter:查找过滤器。通常还提供通过目录对象的属性来查找对象的操作,这种的查找一般通过规定的表达式来表示,称之为查找过滤器。

JNDI简单利用

RMI示例

开启RMI服务

远程接口:

package rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RMIInterface extends Remote {
    public String Hello() throws RemoteException;
}

远程对象:

package rmi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RMItest extends UnicastRemoteObject implements RMIInterface {
    protected RMItest() throws Exception{
        super();
    }

    @Override
    public String Hello() throws RemoteException {
        System.out.println("hello");
        return "b1uel0n3";
    }
}

开启服务:

package rmi;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class server {
    //rmi服务地址
    public static String HOST="127.0.0.1";
    public static int PORT=5432;
    public static String RMI_PATH="/b1uel0n3";
    public static String RMI_NAME="rmi://"+HOST+":"+PORT+RMI_PATH;
    public static void main(String[] args) throws Exception {
        //注册RMI端口
        LocateRegistry.createRegistry(PORT);
        //绑定远程对象
        RMIInterface o=new RMItest();
        Naming.rebind(RMI_NAME,o);
        System.out.println("RMI服务在:"+RMI_NAME);
    }
}

image.png

设置JNDI环境参数

在访问JNDI目录服务时会通过预先设置好环境变量访问对应的服务,这里主要通过Context接口实现的,在Context接口中定义的变量,分别用到了INITIAL_CONTEXT_FACTORYPROVIDER_URL

  • INITIAL_CONTEXT_FACTORY:保存环境属性名称的常量,用于指定要使用的初始上下文工厂,即指定 JNDI 服务提供者的入口类。

image.png

  • PROVIDER_URL:保存环境属性名称的常量,用于指定服务提供者要使用的配置信息,该属性应包含一个URL字符串

image.png

先设置环境变量:

Hashtable env = new Hashtable();
env.put(INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(PROVIDER_URL, "rmi://127.0.0.1:5432");

这里相当于告诉JNDI要连接的是RMI注册表,它在127.0.0.1的5432端口

初始化上下文

初始化上下文需要用到InitialContext对象来为我们获取命名服务资源,JNDI也提供InitialDirContext对象为我们获取目录服务资源

这里初始化上下文,传入我们设置好的环境变量:

InitialContext ctx = new InitialContext(env);

image.png

传入的是Hashtable对象

如果不指定环境变量的话:

InitialContext ctx = new InitialContext();

JNDI就会自动搜索系统属性System.getProperty()、applet参数和应用程序资源文件jndi.properties

所以也可以通过System.getProperty()设置环境变量:

System.getProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
System.getProperty(Context.PROVIDER_URL, "rmi://127.0.0.1:5432");
InitialContext ctx = new InitialContext();

通过上下文查找远程对象

利用提供的lookup方法,通过查询名字获取远程对象代理类:

RMIInterface o = (RMIInterface) ctx.lookup("b1uel0n3");

调用远程方法

最后调用远程方法即可:

System.out.printf(o.Hello());

完整代码:

package rmi;

import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Hashtable;

public class Client {
    public static void main(String[] args) throws Exception {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
        env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:5432");
        InitialContext ctx = new InitialContext(env);
        RMIInterface o=(RMIInterface)ctx.lookup("b1uel0n3");
        System.out.printf(o.Hello());
    }
}

image.png

DNS示例

与rmi一样:

package rmi;

import javax.naming.Context;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;

public class Client {
    public static void main(String[] args) throws Exception {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
        env.put(Context.PROVIDER_URL, "dns://114.114.114.114");
        Context ctx = new InitialDirContext(env);
        Object o=ctx.lookup("www.baidu.com");
        System.out.println(o);
    }
}

image.png

这里我们打算使用DNS服务,从dns://114.114.114.114这个dns服务器上查询www.baidu.com域名对应的IP地址。但这里返回的是一个对象,由于我们用的是JNDI目录服务,而目录服务允许目录对象具有属性,那么我们就能通过目录服务获取ip这个属性值:

DirContext ctx = new InitialDirContext(env);
Attributes o= ctx.getAttributes("www.baidu.com", new String[]{"A"});
System.out.println(o);

DNS记录类型:

记录类型含义示例
AIPv4 地址记录www.baidu.com → 14.215.177.38
AAAAIPv6 地址记录www.example.com → 2001:db8::1
CNAME别名记录www → real-server.example.com
MX邮件交换记录@ → mail.example.com
TXT文本记录"v=spf1 include:_spf.google.com ~all"
NS域名服务器记录@ → ns1.alidns.com

image.png

获得ipv4地址,通过这个地址可以访问百度

完整代码:

package rmi;

import javax.naming.Context;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;


public class Client {
    public static void main(String[] args) throws Exception {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
        env.put(Context.PROVIDER_URL, "dns://114.114.114.114");
        DirContext ctx = new InitialDirContext(env);
        Attributes o= ctx.getAttributes("www.baidu.com", new String[]{"A"});
        System.out.println(o);
    }
}

动态协议切换

前面rmi和dns例子中我们都是通过设置INITIAL_CONTEXT_FACTORY和PROVIDER_URL的值来告诉JNDI我们要调用何种服务,从哪里获取这个服务。

而在Context.lookup()方法的参数中,用户可以指定自己的查找协议。JNDI会通过用户输入来动态识别要调用的服务以及路径:

package rmi;

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


public class Client {
    public static void main(String[] args) throws Exception {
        String str="rmi://127.0.0.1:5432/b1uel0n3";
        Context ctx = new InitialContext();
        RMIInterface o=(RMIInterface)ctx.lookup(str);
        System.out.println(o.Hello());
    }
}

image.png

这里我们并没有设置相应的环境变量来初始化Context,但是JNDI仍旧通过lookup()的参数识别出要调用的服务以及路径

默认支持自动转换的协议有:

image.png

源码分析

命名服务初始化上下文

这里拿rmi为例子

InitialContext ctx = new InitialContext(env);

image.png

此时env不为空,调用enviroment.clone()方法:

image.png

重新赋值,这里对原始环境变量没用改动,随后调用init方法,传入环境变量:

image.png

调用了ResourceManager的静态方法getInitialEnvironment,传入环境变量,看文件名像时获取初始环境变量:

image.png

image.png

定义了一个PROPS变量,里面记录了Context接口的常量信息

image.png

然后获取APPLET属性,由于我们传入的环境变量只记录了Context.INITIAL_CONTEXT_FACTORY和Context.PROVIDER_URL的信息,所以这里applet参数为null

image.png

image.png

helper为VersionHelper对象,这里调用VersionHelper.getJndiProperties静态方法:

image.png

最后返回了长度为7的字符串数组,且都为null,似乎更像是在初始化

image.png

初始化Context接口每个常量的值,这里通过三个方面来获取值,首先从传入的环境变量找值,如果没用则通过applet,如果还没有就通过系统属性来获取值,这也正好解释了前面初始化上下文时可通过不传入env来设置环境变量

初始化环境变量后回到initialContext.init方法:

image.png

设置了初始化上下文工厂就会调用getDefaultInitCtx()方法,获取默认初始化上下文,先看下此时的变量:

image.png

跟进getDefaultInitCtx()方法:

image.png

调用了NamingManager.getInitialContext静态方法,传入myProps变量:
image.png

获取用户设置的工厂类

image.png

如果不为null就加载并实例化这个类:

image.png

最后调用RegistryContextFactory.getInitialContext方法,传入环境变量:

image.png

image.png

先拷贝参数值给var1,再调用URLToContext(getInitCtxURL(var1), var1),先看getInitCtxURL方法:

image.png

image.png

获取环境变量指定的服务地址,这里就是rmi://127.0.0.1:5432

然后看URLToContext方法:

image.png

这里实例化了一个rmiURLContextFactory,这是一个专门用于处理 RMI URL 的上下文工厂,然后调用了getObjectInstance方法传入了服务地址和环境变量:

image.png

调用 getUsingURL方法:

image.png

实例化rmiURLContext对象,传入环境变量:

image.png

image.png

然后调用var2.lookup(var0);方法,即rmiURLContext父类GenericURLContext.lookup方法,传入服务地址:
image.png

最终调用RegistryContext.lookup方法:

image.png

根据上述过程中获取的信息初始化了一个新的RegistryContext,RegistryCo


文章来源: https://www.freebuf.com/articles/vuls/447545.html
如有侵权请联系:admin#unsafe.sh