前面一篇介绍了基础的RMI的使用case JDNI注入:RMI基本知识点介绍 - 一灰灰Blog,其中有说到客户端通过rmi访问server时,表现和我们常见的rpc也一致,客户端拿到代理执行的方法,也是在远程服务端执行的,怎么就存在注入问题呢?
接下来我们再来看一个知识点,RMI + Reference,利用反序列化来实现注入
1. Reference服务端使用姿势
区别于前面一篇rmi提供的远程接口访问方式,这里借助Refernce来实现,当客户单连接请求时,返回一个Class,当客户端拿到这个class并实例化时,实现我们预期的注入
服务器的实现与前面的大体相同,通过Registry起一个rmi服务,区别在于将之前的注册一个服务类改成注册一个Reference,如下
1 | public static void main(String[] args) throws Exception { |
注意上面的Reference的定义,三个参数
- className:远程加载时所使用的类名;
- classFactory:加载的class中需要实例化类的名称;
- classFactoryLocation:远程加载类的地址,提供classes数据的地址可以是file/ftp/http等协议;
上面表示的是当客户端连接到这个rmi发起请求之后,会尝试从 http://127.0.0.1:9999/Inject.class
获取并加载class文件
接下来写一个简单的Inject类,在静态块中可以执行任何你想执行的代码
1 | public class Inject { |
启动一个简单的python服务器,这样可以直接通过网络加载这个class文件
1 | python3 http.server -m 9999 |
这样一个支持代码注入的rmi服务器就搭建完成了;
2. 客户端实测
接下来看下客户单的访问姿势
1 | public static void injectTest() throws Exception { |
当jdk版本较高时,会发现有下面这种提示,表示默认不允许读取远程的class文件
1 | Exception in thread "main" javax.naming.NamingException [Root exception is java.lang.ClassCastException: Inject cannot be cast to javax.naming.spi.ObjectFactory] |
我们先模拟一下注入的case,所以先将这个开关开上,直接在启动中添加下面这一行配置
1 | -Dcom.sun.jndi.rmi.object.trustURLCodebase=true |
接下来看一下执行结果
重点关注上面输出的hello world
,这个输出实际上是在Inject类的静态方法中输出的,在客户端被执行了;
接下来我们模拟一下,直接唤起客户单本地应用的case,在Inject类中,实现一个打开计算器的功能(可以借助 Runtime)
1 | public class Inject { |
接下来我们再来执行一下看看会发生什么,计算器是否会如期被唤起
看到上面这个的小伙伴可能会有疑问,不过是打开我的计算器,也没啥了不起的影响,但是请注意,上面这个Inject的静态类可以任由我们自己发挥
- 如果你的客户端是linux,那么直接在
~/.ssh/authorized_keys
中写入黑客的公钥,这样就可以直接登录服务器 - 直接下载木马、病毒在本机执行
- ….
所以上面这个问题还是相当可怕的,幸好的是在Oracle JDK11.0.1, 8u191, 7u201, 6u211及之后的版本,trustURLCodebase
这个配置默认是false,一般也不会有人特意去开启这个配置,所以问题不大
那么真的是问题不大么?且待后续博文
一灰灰的联系方式
关联博文
尽信书则不如无书,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
- 个人站点:https://blog.hhui.top
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840
- 微信公众号:一灰灰blog