JNDI注入-基础知识
前言
弄清JNDI RMI LDAP等基础概念以及手动操作实现
JNDI
JNDI 全称为 Java Naming and Directory Interface(Java 命名与目录接口) 是SUN公司提供的一种标准的 Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。目录服务是命名服务的一种自然扩展。
命名的意思 就是,在一个目录系统,它实现了把一个服务名称和对象或命名引用相关联,在客户端,我们可以调用目录系统服务,并根据服务名称查询到相关联的对象或命名引用,然后返回给客户端。而 目录的意思 就是在命名的基础上,增加了属性的概念,我们可以想象一个文件目录中,每个文件和目录都会存在着一些属性,比如创建时间、读写执行权限等等,并且我们可以通过这些相关属性筛选出相应的文件和目录
也就是说:JNDI是一个接口,在这个接口下会有多种目录系统服务的实现,我们能通过名称等去找到相关的对象,并把它下载到客户端中来。
JNDI可访问的现有的目录及服务主要有:
RMI: Java Remote Method Invocation,Java 远程方法调用;特有的远程调用框架
LDAP: 轻量级目录访问协议;通用的服务与标准
CORBA: Common Object Request Broker Architecture,通用对象请求代理架构; 通用的服务与标准
JNDI结构
在Java JDK里面提供了5个包,提供给JNDI的功能实现,分别是:
javax.naming:主要用于命名操作,它包含了命名服务的类和接口,该包定义了Context接口和InitialContext类; |
在 javax.naming 中包含了访问目录服务所需的 类和接口,比如 Context、Bindings、References、lookup 等。
这里解释一下
InitialContext类
构造方法
InitialContext() |
构建初始上下文,其实通俗点来讲就是获取初始目录环境
常用方法
bind(Name name, Object obj) |
example:
import javax.naming.InitialContext; |
References
该类表示对在命名/目录系统 外部找到的对象的引用
构造方法
Reference(String className) |
参数1:className
- 远程加载时所使用的类名
参数2:classFactory
- 加载的class
中需要实例化类的名称
参数3:classFactoryLocation
- 提供classes
数据的地址可以是file/ftp/http
协议
常用方法
void add(int posn, RefAddr addr) |
example:
import com.sun.jndi.rmi.registry.ReferenceWrapper; |
RMI
RMI,即 Remote Method Invocation,Java 的远程方法调用。RMI 为应用提供了远程调用的接口,可以理解为 Java 自带的 RPC 框架
一个简单的 RMI hello world
主要由三部分组成,分别是接口、服务端和客户端
远程方法调用(RMI)原理与示例 - Pickle - 博客园 (cnblogs.com)
过程
当客户端调用远程对象方法时, 存根 负责把要调用的远程对象方法的方法名及其参数编组打包,并将该包向下经远程引用层、传输层 转发给远程对象所在的服务器。通过 RMI 系统的 RMI 注册表实现的简单服务器名字服务, 可定位远程对象所在的服务器。该包到达服务器后, 向上经远程引用层, 被远程对象的 Skeleton 接收, 此 Skeleton 解析客户包中的方法名及编组的参数后, 在服务器端执行客户要调用的远程对象方法, 然后将 该方法的返回值( 或产生的异常) 打包后通过相反路线返回给客户端, 客户端的 Stub 将返回结果解析后传递给客户程序。
RMI远方法程调用步骤:
- 1、客户调用客户端辅助对象stub上的方法
- 2、客户端辅助对象stub打包调用信息(变量、方法名),通过网络发送给服务端辅助对象skeleton
- 3、服务端辅助对象skeleton将客户端辅助对象发送来的信息解包,找出真正被调用的方法以及该方法所在对象
- 4、调用真正服务对象上的真正方法,并将结果返回给服务端辅助对象skeleton
- 5、服务端辅助对象将结果打包,发送给客户端辅助对象stub
- 6、客户端辅助对象将返回值解包,返回给调用者
- 7、客户获得返回值
Stub获取方式
Stub的获取方式有很多,常见的方法是调用某个远程服务上的方法,向远程服务获取存根。但是调用远程方法又必须先有远程对象的Stub,所以这里有个死循环问题。JDK提供了一个RMI注册表(RMIRegistry)来解决这个问题。RMIRegistry也是一个远程对象,默认监听在1099端口上,可以使用代码启动RMIRegistry,也可以使用rmiregistry命令。要注册远程对象,需要RMI URL和一个远程对象的引用。
IHello rhello = new HelloImpl(); |
LocateRegistry.getRegistry() 会使用给定的主机和端口等信息 本地创建一个Stub对象作为Registry远程对象的代理,从而启动整个远程调用逻辑。服务端应用程序可以向RMI注册表中注册远程对象,然后客户端向RMI注册表查询某个远程对象名称,来获取该远程对象的Stub。
动态加载类
RMI核心特点之一就是动态加载类,如果当前JVM中没有某个类的定义,它可以从远程URL去下载这个类的class,java.rmi.server.codebase属性值表示一个或多个URL位置,可以从中下载本地找不到的类,相当于一个代码库。动态加载的对象class文件可以使用Web服务的方式(如http://、ftp://、file://)进行托管。客户端使用了与RMI注册表相同的机制。RMI服务端将URL传递给客户端,客户端通过HTTP请求下载这些类。
无论是客户端还是服务端要远程加载类,都需要满足以下条件:
由于Java SecurityManager的限制,默认是不允许远程加载的,如果需要进行远程加载类,需要安装RMISecurityManager并且配置java.security.policy,这在后面的利用中可以看到。
属性 java.rmi.server.useCodebaseOnly 的值必需为false。但是从 JDK 6u45、7u21 开始,java.rmi.server.useCodebaseOnly 的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前虚拟机的java.rmi.server.codebase 指定路径加载类文件。使用这个属性来防止虚拟机从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。
示例
在Server与Client中都需要定义相同接口,继承Remote
C/S的接口:
import java.rmi.Remote; |
Server端:
import java.rmi.AlreadyBoundException; |
Client端:
import java.rmi.NotBoundException; |
先启动Server端,在启动Client端后,接受远程返回的 hello world
LDAP
LDAP(Lightweight Directory Access Protocol)-轻量目录访问协议。但看了这个解释等于没说,其实差不多是个数据库,
具有以下特点:
- 基于TCP/IP协议
- 同样也是分成服务端/客户端;同样也是服务端存储数据,客户端与服务端连接进行操作
- 相对于mysql的表型存储;不同的是LDAP使用 树型存储
树层次分为以下几层:
- dn:一条记录的详细位置,由以下几种属性组成
- dc: 一条记录所属区域(哪一个树,相当于MYSQL的数据库)
- ou:一条记录所处的分叉(哪一个分支,支持多个ou,代表分支后的分支)
- cn/uid:一条记录的名字/ID(树的叶节点的编号,想到与MYSQL的表主键?)
举个例子一条记录就是
dn="uid=songtao.xu,ou=oa,dc=example,dc=com" |
详细介绍:LDAP 协议入门(轻量目录访问协议) - 知乎 (zhihu.com)
参考
远程方法调用(RMI)原理与示例 - Pickle - 博客园 (cnblogs.com)
十一、RMI、JNDI、LDAP介绍+log4j漏洞分析 - FreeBuf网络安全行业门户
JNDI 注入漏洞的前世今生 - 知乎 (zhihu.com)
Java安全之JNDI注入 - nice_0e3 - 博客园 (cnblogs.com)
JNDI with RMI - 安全客,安全资讯平台 (anquanke.com)
LDAP 协议入门(轻量目录访问协议) - 知乎 (zhihu.com)
Java 中 RMI、JNDI、LADP、JRMP、JMX、JMS那些事儿(上) - 云+社区 - 腾讯云 (tencent.com)