一,攻击点判断
rmi 本质是跨 jvm 调用方法,只要对象跨 jvm 传输,就一定会有序列化与反序列化,在 rmi 的 中,服务端,客户端,注册表都有涉及到对象序列化与反序列化的部分,所以三个都可能被攻击,而谁被攻击,主要看谁反序列化了恶意对象
二,攻击服务端
1.典型攻击点
攻击者控制客户端,在调用 stub 对象执行方法时传入恶意对象,服务端会先反序列化这个对象在作为方法参数传入调用的方法,整个反序列化过程在执行服务端对象的方法前就反序列化完毕,并不需要显示依赖服务端方面的 ObjectInputStream.readobject(),rmi 框架在执行任意远程参数时自动就会执行反序列化
调用链:
client —> remote.method(payload) —>客户端序列化 paylaod —>JRMP 发送到目标服务端 —>服务端反序列化方法参数 —>触发 gadget 链
2.攻击服务端条件:
1.目标开放 rmi/jrmp 服务端口
2.攻击者能连接目标 rmi 服务端
3.目标暴漏了可调用的 Remote 方法,方法参数存在可控对象传输
4.服务端 classpath 中存在可利用 gadget 链
三,攻击客户端
1.典型攻击点
依旧是找能向客户端传递序列化参数的点,在整个 rmi 流程中,主要有两种方式涉及到向客户端传递序列化对象
一个是客户端在获取到注册表后,使用 lookup 方法获得 stub 对象,这时注册表会取出名称对应的对象传给客户端,如果注册表可控,攻击者可以传递一个恶意对象给客户端
二是客户端在调用 stub 对象执行方法时,方法的实际执行是在服务端,方法执行的结果也由服务端传递给客户端,如果服务端可控,攻击者可以修改方法调用返回的参数,传递一个恶意链子
rmi 在远程调用失败时,异常对象也有可能通过序列化传递给客户端
调用链:
client ->连接 Registry ->lookup()或 remote.invoke() ->注册表或服务端返回恶意 payload -> MarshalInputStream.readObject() ->触发 gadget
2.攻击客户端条件:
-
客户端会连接攻击者控制或被劫持的 RMI Registry / RMI Server
-
客户端会执行 lookup 或远程方法调用
-
攻击者能控制返回值或异常对象
-
客户端 classpath 中存在 gadget
-
客户端没有有效反序列化过滤 6. 客户端没有限制远程地址、协议或返回类型
四,攻击注册表
注册表本质也是一个远程对象,所以也会涉及到参数的序列化与反序列化,所以也会成为攻击点
1.典型攻击点
注册表的 bind()与 rebind()方法接收两个参数 String name 与 Remote obj,也就是绑定名称与对象,客户端中可以调用注册表的这两个方法进行对象的绑定,导致了攻击点,同样的服务端也可以通过 bind()攻击注册表,但是服务端与注册表通常部署在同一台主机上,没有必要再去攻击
值得注意的是这两个方法接收的都是 Remote 对象,并不是任意对象,所以需要通过参数类型约束
注册表的其他几个方法如 lookup,list,unbind 的参数都不涉及到对象,所以并不是理想的可利用方式
2.调用链
attacker -> 连接 Registry ->registry.rebind(“evil”,controlledObj)->客户端序列化参数 -> Registry 反序列化参数 ->触发 gadget
五,DGC 反序列化
1.DGC 全称 distributed Garbage collection 分布式垃圾回收机制
jvm 的回收机制只会回收本地产生的类对象,但是在 rmi 中客户端拿到的不是真实对象,而是一个引用,jvm 的回收机制不会自动回收这种对象,服务端需要知道客户端还需不需要这个对象的引用,能否释放相关资源,所以需要 DGC 来进行管理
DGC 中有两个重要方法,dirty() 和 clean()
dirty 用来告诉服务端还在使用对象,clean 表示不再使用,可以清理
2.dgc 本身也是远程调用机制,需要接收远程传来的参数,所以也存在攻击点
调用链:
attacker -> 构造 JRMP DGC 请求 ->目标 RMI 端口接收请求 -> 请求备份发到 DGC ObjID -> 进入 DGCImpl.dirty()/DGCImpl.clean() -> 读取 dirty/clean 参数 ->反序列化 -> 触发 gadget