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:名称。要在命名系统中查找对象,需要提供对象的名称
- Naming Convention:命名规范。一个命名系统中的所有名称必须遵循的语法规范。
- Binding:绑定。一个名称和一个对象的关联
- Reference:引用。一些命名服务系统不是直接存储对象,而是保存对象的引用。引用包含了如何访问实际对象的信息。
- Address:地址。引用通常用一个或多个地址(通信端口)来表示
- Context:上下文。一个上下文是一系列名称和对象的绑定的集合。一个上下文中的名称可以绑定到一个具有相同命名规范的上下文中,称之为子上下文(subcontext)。例如:在文件系统中,/usr是一个Context,/usr/bin是usr的subcontext。
- Naming System:命名系统。一个相同类型的上下文集合
- Namespace:命名空间。一个命名系统的所有名称的集合
- Atomic Name:原子名。一个简单基本结构
- Compound Name:混合名。由多个原子名一起构成的名称
- Composite Name:复合名称。是跨越多个命名系统的名称
目录服务:是命名服务的扩展,除了提供名称和对象的关联,还允许对象具有属性。目录服务中的对象称之为目录对象,目录对象可以跟属性关联,一个目录是由相关联的目录对象组成的系统。目录服务提供创建、添加、删除目录对象以及修改目录对象属性等操作。
相关概念:
- Attribute:属性。一个目录对象可以包含属性。一个属性具有一个属性标识符和一系列属性值
- Search Filter:查找过滤器。通常还提供通过目录对象的属性来查找对象的操作。这种的查找一般通过规定的表达式来表示,称之为查找过滤器。
简单使用
1 | import javax.naming.Context; |

代码分析
核心API
javax.naming.InitialContext
javax.naming.directory.InitialDirContext
用于初始化上下文环境

需要传入一个Hashtable
1 | env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.dns.DnsContextFactory"); |
环境设置需要上下文工厂与URL

将环境传入InitialDirContext中 完成上下文初始化
目录服务可以获取到属性名

输出了dns解析结果
InitialDirContext继承于InitialContex
设置环境信息不是必须的
也可以直接这样写 会自动的协议转换
1 | Context init = new InitialContext(); |
这样就可以直接获取到rmi的远程对象

JNDI支持这些协议 主要使用 RMI与LDAP
JNDI定义了命名引用,简称引用。其大致过程是通过绑定一个引用,将引用对象存储到命名/目录服务中,命名管理器(Naming Manager)可以将引用解析为关联的原始对象。
引用由Reference类来表示,每个Reference包含如何构造对应的对象的信息,包括引用对象的全限定类名,服务地址,以及用于创建对象的工厂类的名称和位置

主要利用这个构造器

className:类名 如果本地找不到会去根据factoryLocation提供的URL去下载对应的.class文件 并自动加载
factory:工厂类的名字 一般我们提供的类已经引用了这个工厂类
factoryLocation:工厂类的URL 可以是file://、ftp://、http://等协议
初始化
跟进一手这个无参初始化


进入到init

进入到getInitialEnvironment


函数内部就是获取环境变量信息 但是因为我们没有填入所以都是null


最终就返回了initialContext对象 无参构造器主要通过后面的lookup去动态配置环境信息
查找对象
重点关注这个lookup


进入getURLOrDefaultInitCtx

进入到hasInitialContextFactoryBuilder

由于没有初始化上下文工厂所以为null
最后返回的是False
跳过了第一个if判断

进入getURLScheme函数


返回协议类型
进入到NamingManager.getURLContext

根据刚刚的协议返回rmiurlcontext

回到lookup 进入getRootURLContext

进入到RegistryContext

进入getRegistry

会去查找rmi的注册中心并返回

后面通过createproxy创建一个registImpl_stub返回回去 和rmi查找注册中心一样


配置注册中心上下文

注册中心上下文搞好了 开始进入ResolveResult
var15是注册中心上下文 var13是远程对象的名字

进入构造器 跟进setRemainingName

这里将远程对象的名字赋给 remainingName

回到lookup 进入getResolvedObj

就是把刚刚的注册中心上下文赋给它
进入注册中心上下文的lookup


开始调用registryImpl_stub的lookup和rmi的一样
后续也是new一个RemoteCall 然后开始请求
最终也是收到一个动态代理对象

Reference对象的利用
由于JNDI引入了Reference对象 我们可以利用这个让lookup对象的那一端自动的下载并加载类
但是RMI中并没有Reference对象 需要使用ReferenceWrapper包装器 将一个Reference对象包装一下可以绑定在注册中心上
看看ReferenceWrapper类


ReferenceWrapper类继承自RemoteReference接口 -> RemoteReference接口又继承了Remote接口
通过ReferenceWrapper包装的Reference对象自然也就继承Remote接口 此时就可以绑定在注册中心上
跟进一手lookup

我们先跳过前面部分
直接到RegistryContext的lookup中看看
var2会获得RegistryImpl_stub.lookup()查找的对象
这里就是我们设置的ReferenceWrapper_Stub对象

之后就要开始通过这个 ReferenceWrapper_Stub -> Reference -> 恶意对象
跟进decodeObject()

检测是否是RemoteReference
如果是就会获取其中的Reference对象

这里会调用super.ref.invok 猜测就是根据Reference对象中包装好的工厂类地址去下载这个工厂类对象

到了这里 var11已经就是Reference对象了
跟进unmarshalValue

通过readObject 可以得到最终的Reference对象中的信息

回到decodeObject

注意一下NamingManager.getObjectInstance

上面会有一些列的检测 到了这里就开始获取工厂类了
跟进getObjectFactoryFromReference()

通过刚刚获得的工厂类名 开始加载工厂类了

最终clas返回null
是因为这里的loadclass 是尝试加载本地的类 而且codebase也没填入 显然本地是没有的 所以得通过其他的类加载器去加载
通过调用ref.getFactoryClassLocation() 获取我们设置的工厂类的url


跟进一下下面的loadClass

这里的构造器 就是直接使用URLClassLoader类加载器
获取URLClassLoader实例 也把刚刚的codebase塞进去了
最后来到这个本地forName0

调用完之后就成功下载并加载我们的恶意类了

栈帧中可以看到加载的Calc 与 http请求的响应信息
之后就是成功触发
也关注一下http服务的情况
利用代码
1 | import javax.naming.Context; |
1 | import com.sun.jndi.rmi.registry.ReferenceWrapper; |
1 | import javax.naming.Context; |
LADP + JNDI
LDAP也支持Reference
引用这位师傅的代码
1 | package jndi.ldapAttack; |
JNDI_RMI_Reference限制
在JDK 6u132, JDK 7u122, JDK 8u113之后Java限制了通过RMI远程加载Reference工厂类。
com.sun.jndi.cosnaming.object.trustURLCodebase 默认值变为false,即默认不允许通过RMI从远程的Codebase加载工厂类
JNDI_LDAP_Reference限制
在JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase属性的默认值同样被修改为了false,即默认不允许通过LDAP从远程的Codebase加载工厂类
说些什么吧!