从代码层面看RMI规范的实现与攻击原理(一)

从代码层面看RMI规范的实现与攻击原理(一)

上一篇文章粗糙的讲了RMI规范相关的一些内容,今天通过代码跟踪了一下具体的实现过程,发现昨天的理解有一些是错误的,首先看一段客户端的代码:
在这里插入图片描述
我们重点关注第15行与16行,这两行分别是获得一个注册器与从hash表中查询market键对应的对象的操作,我们首先打断掉跟进到LocateRegistry#getRegistry方法里面:

public static Registry getRegistry(String host, int port,
                                       RMIClientSocketFactory csf)
        throws RemoteException
    {
        Registry registry = null;

        if (port <= 0)
            port = Registry.REGISTRY_PORT;

        if (host == null || host.length() == 0) {
            // If host is blank (as returned by "file:" URL in 1.0.2 used in
            // java.rmi.Naming), try to convert to real local host name so
            // that the RegistryImpl's checkAccess will not fail.
            try {
                host = java.net.InetAddress.getLocalHost().getHostAddress();
            } catch (Exception e) {
                // If that failed, at least try "" (localhost) anyway...
                host = "";
            }
        }

        /*
         * Create a proxy for the registry with the given host, port, and
         * client socket factory.  If the supplied client socket factory is
         * null, then the ref type is a UnicastRef, otherwise the ref type
         * is a UnicastRef2.  If the property
         * java.rmi.server.ignoreStubClasses is true, then the proxy
         * returned is an instance of a dynamic proxy class that implements
         * the Registry interface; otherwise the proxy returned is an
         * instance of the pregenerated stub class for RegistryImpl.
         **/
        LiveRef liveRef =
            new LiveRef(new ObjID(ObjID.REGISTRY_ID),
                        new TCPEndpoint(host, port, csf, null),
                        false);
        RemoteRef ref =
            (csf == null) ? new UnicastRef(liveRef) : new UnicastRef2(liveRef);

        return (Registry) Util.createProxy(RegistryImpl.class, ref, false);
    }

该方法是一个重载方法,我们调用的方法有三个参数分别是host, port, csf,比较疑惑的可能就是csf了,该参数传递的是一个RMIClientSocketFactory接口的对象,我们跟进去看一下:
在这里插入图片描述
根据名字很容易看到只要有一个该接口的实现类调用了createSocket方法就会创建一个socket,继续回到getRegistry方法
它首先判断了你是否为你的方法传入了port参数,如果没有传入,则获取系统默认的端口1099
然后判断你是否传入了主机名,如果你没有传入则会使用本地主机名,loacahost
下一步将会创建一个LiveRef对象,至于干什么的我不清楚,简单看了看就是为一些变量赋值,后面会用到它们:
在这里插入图片描述
可以看到host, port,csf都被他使用了。具体是通过TCPEndpoint这个方法传进去的,这些方法的作用具体不太清楚,毕竟不是专业的。
然后创建了一个RemoteRet对象,
csf == null) ? new UnicastRef(liveRef) : new UnicastRef2(liveRef)
如果csf为null,则实例化UnicastRef
我们跟进去看一看:
在这里插入图片描述
我们看这个单播引用类是实现了RemoteRef接口的
在这里插入图片描述
而这个RemoteRef接口又是继承了Externalizable接口,这意味着该类可以被序列化与反序列化。
我们实例化时调用的构造方法是这一个:
在这里插入图片描述
ref是一个LiveRef类型的变量。除了这个动作没有其他的动作了
心血来潮我又回去看了一下LiveRef类,他继承了Cloneable接口,证明它可以被克隆,至于什么事克隆暂时不清楚,写完了去看看。
在这里插入图片描述
继续回到getRegistry方法,
在这里插入图片描述
createProxy方法接受了三个参数,第一个是RegistryImpl类的Class实例,第二个是上面提到的ref,第三个是一个boolean值,看提示是是否强制使用Stub,我们这里传递的是false,也就是不使用。
我们跟进该方法:

public static Remote createProxy(Class<?> implClass,
                                     RemoteRef clientRef,
                                     boolean forceStubUse)
        throws StubNotFoundException
    {
        Class<?> remoteClass;

        try {
            remoteClass = getRemoteClass(implClass);
        } catch (ClassNotFoundException ex ) {
            throw new StubNotFoundException(
                "object does not implement a remote interface: " +
                implClass.getName());
        }

        if (forceStubUse ||
            !(ignoreStubClasses || !stubClassExists(remoteClass)))
        {
            return createStub(remoteClass, clientRef);
        }

        final ClassLoader loader = implClass.getClassLoader();
        final Class<?>[] interfaces = getRemoteInterfaces(implClass);
        final InvocationHandler handler =
            new RemoteObjectInvocationHandler(clientRef);

        /* REMIND: private remote interfaces? */

        try {
            return AccessController.doPrivileged(new PrivilegedAction<Remote>() {
                public Remote run() {
                    return (Remote) Proxy.newProxyInstance(loader,
                                                           interfaces,
                                                           handler);
                }});
        } catch (IllegalArgumentException e) {
            throw new StubNotFoundException("unable to create proxy", e);
        }
    }

在这里插入图片描述
注意到第13行的方法,跟一下看看:
在这里插入图片描述
getRemoteClass运用了java的放射原理,
194行获得了RegistryImpl类实现的所有接口的Class对象,然后遍历这些接口通过调用Remote类Class对象的isAssignagleFrom方法判断Remote是不是这些接口的父类、超接口、或者同一类型。
那么到底是不是呢,我们找到了RegistryImpl类:
在这里插入图片描述
发现它集成了RemoteServer类,实现了Register接口
RemoteServer的父类实现了Remote接口,Register接口继承了Remote接口,所以上面的条件是成立的,将会放回lc,也就是Registry类的Class对象,
在这里插入图片描述
调试结果也证实了我们的推断。
然后进入一个if判断:
在这里插入图片描述
我们知道forceStubUserfalse的,但是调试的结果是计入了该if的判断中,那么后面判断条件就必须是true,我们先看ignoreStubClasses
这个值是在Util类被加载的时候赋值的:
在这里插入图片描述
跟进booleanValue方法在这里插入图片描述
发现到了Boolean类,这里返回的是其默认值,我们知道Boolean的默认值为False。
那么看第三个参数stubClassExists的返回值必须为true才能满足调试的结果:
在这里插入图片描述
影响判断的结果的语句是
!withoutStubs.containsKey(remoteClass)
如果为true则返回true,我们知道containsKey方法一般是在集合类型中,判断是否存在指定映射关系,我们看看withoutStubs是否为集合类型:
在这里插入图片描述
确实是,那么有没有对应的映射呢?很明显是没有的,那么到这里条件就满足了。注意到了在if里面还通过java的反射机制将RegisterImpl_Stub类加载到了内存中。
createProxy的if条件满足后,就进入到方法createStub,看着方法名就知道是准备创建一个客户端存根了,不过奇怪的地方来了,昨天不是讲了,客户端存根是在注册中兴创建然后远程加载到客户端的吗?这儿怎么又在客户端创建了?这里我的理解是,在注册中心创建的存根创建后经过序列化,传输到客户端,然后客户端需要有对应类型的对象来接收他,所有这里客户端也会创建一个存根,纯属个人理解不知道对不对。
createStub这个方法接收了连个参数,一个是remoteClass,也就是RegistryImpl类的Class对象,一个是clientRef这个是什么呢?好像给跟漏了,不着急,我们找一找:
在这里插入图片描述
原来createProxy方法的第二个参数就是,也就是刚刚传进来的ref一个单播引用对象UnicastRef继承乐RemoteRef
跟进createStub方法:
在这里插入图片描述
首先获取了stubnameRegistryImpl_Stub
然后291到294行加载了RegistryImpl_Stub类到内存中,并获取了其所有的构造方法,然后调用了其中的一个有参构造方法将ref作为参数传递了进去。
这时候得想办法进到RegistryImpl_Stub类里面看看调用了怎样的方法,因为最终放回的Registry会调用lookup方法,我们通过该方法找到对应的实现类:
在这里插入图片描述
进入后找到对应的构造方法:
在这里插入图片描述
使用super调用了父类的构造方法,找到RegistryImpl_Stub的父类:
在这里插入图片描述
这绝对是在套娃,没跑了:
在这里插入图片描述
我草,这啥也没干啊,就赋了个值罢了。不管了,我们继续。
所以createStub方法最终返回的是一个RemoteStub对象:
在这里插入图片描述

然后createProxy继续将这个对象往上一层抛:
在这里插入图片描述
最后在getRegistry方法中被强转为Registry类型的对象。
在这里插入图片描述
我们知道像下转型才是需要强转的,那么Registry到底是不是RemoteStub类型的子类呢?
貌似好像不是的,这个的原理我不太理解,是知识还有欠缺,实际上Registry是一个接口继承了Remote接口
在这里插入图片描述
RemoteStub继承了RemoteObject
在这里插入图片描述
RemoteObject类实现了Remote接口
在这里插入图片描述
所以这算什么?
这也不是向下转型,而是向上转型,不算一般意义上的强转,当然向上转型也是可以使用强转的。
这里的我的理解是RemoteStub向上转型到Remote,因为Registry是扩展了Remote接口的,所以也是可以的。
到了这里我们就获得了一个注册器了,到此为止我们仍然没有发现客户端向注册中心发送请求获取存根的代码,为了验证我的推导,我使用难了wireshark抓包,发现确实没有导1099端口的流量:
在这里插入图片描述

那么这部分代码最有可能是存在于lookup的过程中。
欲知后事如何,请听下回分说。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值