什么是JNDI?
简单来说,
JNDI
(Java Naming and Directory Interface) 是一组应用程序接口,它为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定位用户、网络、机器、对象和服务等各种资源。比如可以利用JNDI
在局域网上定位一台打印机,也可以用JNDI
来定位数据库服务或一个远程Java对象。JNDI
底层支持RMI
远程对象,RMI
注册的服务可以通过JNDI接口来访问和调用。
JNDI
支持多种命名和目录提供程序(Naming and Directory Providers),RMI
注册表服务提供程序(RMI Registry Service Provider)允许通过JNDI
应用接口对RMI
中注册的远程对象进行访问操作。将RMI
服务绑定到JNDI
的一个好处是更加透明、统一和松散耦合,RMI
客户端直接通过URL来定位一个远程对象,而且该RMI
服务可以和包含人员,组织和网络资源等信息的企业目录链接在一起。

就个人的理解,JNDI
相当于在LDAP
RMI
等服务外面再套了一层API
,方便统一调用。
JNDI的注入点
假设client端地址为10.0.0.1
,先来看下面一段,JNDI的client端的代码
其中providerURL
为可控变量,此时,可以传入任意JNDI服务路径来实现注入,如
但是问题来了,此时即使执行了evil所绑定的类,依然是在10.0.0.2
上执行,无法影响到10.0.0.1
,因此要引入一个新的概念
JNDI References
在JNDI服务中,RMI服务端除了直接绑定远程对象之外,还可以通过References类来绑定一个外部的远程对象(当前名称目录系统之外的对象)。绑定了Reference之后,服务端会先通过Referenceable.getReference()获取绑定对象的引用,并且在目录中保存。当客户端在lookup()查找这个远程对象时,客户端会获取相应的object factory,最终通过factory类将reference转换为具体的对象实例。
通过查阅References的源码,可以得知,其主要记录了如下信息
其中classFactoryLocation
实际上是LDAP
或者RMI
的地址
真正的JNDI注入
假设server地址为10.0.0.2
,构造如下恶意RMI
服务代码
上述代码非常简单,主要是将/exec
这个路径绑定到一个Reference
上,而这个Reference
指向127.0.0.1:8080/Exec.class
,其中Reference
的构造函数第一个参数是className
,第二个参数是classFactory
紧接着让我们构造Exec
这个恶意类
将其编译为Exec.class
文件,然后拷贝到web目录下
|
|
假设client地址为10.0.0.1
,构造如下漏洞代码
即可成功执行whoami
命令
其中前两行代码主要用于解除安全限制
在RMI服务中引用远程对象将受本地Java环境限制即本地的java.rmi.server.useCodebaseOnly配置必须为false(允许加载远程对象),如果该值为true则禁止引用远程对象。除此之外被引用的ObjectFactory对象还将受到com.sun.jndi.rmi.object.trustURLCodebase配置限制,如果该值为false(不信任远程引用对象)一样无法调用远程的引用对象。
JDK 5 U45,JDK 6 U45,JDK 7u21,JDK 8u121开始java.rmi.server.useCodebaseOnly默认配置已经改为了true。
JDK 6u132, JDK 7u122, JDK 8u113开始com.sun.jndi.rmi.object.trustURLCodebase默认值已改为了false。
深入源码探索
前面提到了,实际原因是触发了object factory,下面我们来看一下具体的触发调用链
核心代码触发代码从decodeObject
开始
在decodeObject
中,会判断传入对象是满足RemoteReference
接口,满足则通过getReference
函数获取reference
对象,然后进入getObjectInstance
函数
在getObjectInstance
函数中,一共有两处可执行RMI
中定义的恶意代码的地方,一处是getObjectFactoryFromReference
,在getObjectFactoryFromReference
中会通过获取到对应的Class
对象,通过clas.newInstance()
触发恶意构造函数
另外一处,则是通过实例化的类,调用其getObjectInstance
函数,只要我们实现了ObjectFactory
接口,复写getObjectInstance
函数,即可执行恶意代码
JdbcRowSetImpl的JNDI注入利用链
在实战过程中,context.lookup
直接被外部调用的情况比较少,但是我们可以通过间接调用context.lookup
实现JNDI的注入,JdbcRowSetImpl
就是这样一条利用链,先来看一下最终的POC
调用链如下
可以看到,唯一的不同在于lookup
前调用了setAutoCommit
以及connect
方法
坑
1.在POC复现的过程中,由于编译Exec
使用了1.8,运行Server
以及Client
使用了1.7,导致无法运行。由于JAVA版本向下兼容,因此实际利用过程中,建议使用1.6编译Exec.class
,笔者偷懒,均采用了1.8
2.Exec
的声明不能带package,否则无法触发,具体原因仍未查明。