RMI介绍

RMI全程Remote Method Invocation (远程方法引用),RMI有客户端和服务端,还有一个注册中心,在java中客户端可以通过RMI调用服务端的方法,流程图如下:



服务端创建RMI后会在RMI Registry(注册中心)注册,之后客户端都是从注册中心调用方法,RMI分为三个主体部分:

  • Client-客户端:客户端调用服务端的方法
  • Server-服务端:远程调用方法对象的提供者,也是代码真正执行的地方,执行结束会返回给客户端一个方法执行的结果
  • Registry-注册中心:其实本质就是一个map,相当于是字典一样,用于客户端查询要调用的方法的引用,在低版本的JDK中,Server与Registry是可以不在一台服务器上的,而在高版本的JDK中,Server与Registry只能在一台服务器上,否则无法注册成功

RMI的简单使用

服务端

准备三个文件,接口,实现接口的类,服务端

package org.example.server;

import java.rmi.Remote;
import java.rmi.RemoteException; public interface RMIMethodServer extends Remote {
public String sayHello(String key) throws RemoteException;
}
package org.example.server.impl;

import org.example.server.RMIMethodServer;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject; public class RMIMethodImpl implements RMIMethodServer {
public RMIMethodImpl() throws RemoteException {
UnicastRemoteObject.exportObject(this, 0);
} @Override
public String sayHello(String key){
System.out.println(key.toUpperCase());
return key.toUpperCase();
}
}
package org.example;

import org.example.server.RMIMethodServer;
import org.example.server.impl.RMIMethodImpl; import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry; public class RMIServer{
public static void main(String[] args) throws RemoteException, AlreadyBoundException {
RMIMethodServer remoteServer = new RMIMethodImpl();
// 注册中心
Registry r = LocateRegistry.createRegistry(7788);
// 绑定对象到注册中心
r.bind("remoteServer", remoteServer);
}
}

客户端

package org.example;

import org.example.server.RMIMethodServer;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry; public class RMIClient {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry r = LocateRegistry.getRegistry("127.0.0.1", 7788);
RMIMethodServer remoteServer = (RMIMethodServer) r.lookup("remoteServer");
remoteServer.sayHello("I am handsome");
}
}

先运行服务端,在运行客户端,可以看到服务端有输出

RMI创建流程分析

创建远程对象

第一步创建实现类对象



我们的实现类继承了UnicastRemoteObject



下一步进入UnicastRemoteObject的构造方法



导出我们的远程对象



继续跟进,又调用了另一个同名方法,UnicastServerRef是封装RMI的入口



跟进UnicastServerRef,构造方法调用了父类的构造方法,记住LiveRef这个对象,这是核心类,后面所用到的其它大部分类都是对这个类的封装



继续跟进LiveRef,又调用了另一个类的构造方法



这个ObjID没啥用,就是一个标识,继续跟进,到这才是核心,TCPEndpoint.getLocalEndpoint(port)这里才真正调用了远程服务对象的信息



继续跟进,到这,能看出一些关于网络请求的信息



继续跟进,回到LiveRef,这里有一些赋值

执行完后就回到了UnicastServerRef,下一步对ref进行了赋值,这里是第一次封装

最后回到了UnicastRemoteObject,可以看到sref就是LiveRef的封装



继续跟进,进入Util.createProxy这个方法



getClientRef获取LiveRef的封装



进入createProxy,加载远程类,判断这个类是否有_Stub后缀,这里显然没有



下面是一个创建动态代理的过程



创建完后回到stub = Util.createProxy(implClass, getClientRef(), forceStubUse);

然后创建一个Target类,这里其实就是把这一大堆东西又进行一次封装



接下来是exportobject的套娃,LiveRef -> TCPEndpoint -> TCPTransport,进入listen方法



是一个建立socket的过程,里面对port进行了一个初始化,随机指定了一个端口



listen过后又来到父类Transport的exportobject

跟进putTarget方法,将我们层层封装的Target放入一个Map(objtable)中结束

创建注册中心和绑定

进入createRegistry



进入RegistryImpl,我们用的不是默认端口1099,所以直接看else



new一个LiveRef,然后用UnicastServerRef封装,进入setup方法,调用exportobject,到这是不是很眼熟,我们创建远程对象的时候走过



进入createProxy方法,这次就跟上次不一样了,因为RegisryImpl_stub这个类存在,所以进入if



进入createStub方法,这里有个反射实例化的过程,实例化了这个RegisryImpl_stub



执行完后跳出去,这里设置了Skeleton(骨架)



从一开始的流程图我们知道,RMI是一个对称分布的结构,客户端有stub,服务端就对应有Skeleton,客户端通过stub请求远程方法,服务端就通过Skeleton去调用方法,然后通过Skeleton获取结果,最后传给客户端Stub,客户端就从Stub获取结果,因此继续跟进setSkeleton方法:



进入createSkeleton方法,反射获取Skeleton



之后又进入Target的构造方法,跟之前不同的地方是disp中的skel有值了



接着又是exportobject的套娃,来到TCPTransport进入listen方法,创建socket,之后一系列的操作跟第一步相同,返回一个stub

客户端请求注册中心(客户端被攻击)

这里重点在注册后使用了lookup方法,我们调试进去,调试个鸡毛,这是个class文件



直接硬看

漏洞点一

先获取了一个输出流,获得服务端序列化结果,之后在后面调用readObject反序列化,这里一看就有漏洞,如果服务端的返回是恶意的,直接就被rce了

漏洞点二

super.ref.invoke这东西看着就像漏洞,这里的ref是UnicastRef,我们跟进去



call.executeCall是重点,这里call其实StreamRemoteCall,进入StreamRemoteCall查看executeCall方法,在末尾的异常处理调用了in.readObject,这里也能被攻击

漏洞点三

我们调用的是lookup方法,那其它方法是不是也能利用,一看,全都调用了invoke方法,攻击点都是漏洞点二

客户端请求服务端(客户端被攻击)

漏洞点分析

由于获取的remoteServer是动态代理类,所以会调用invoke方法

跟进调试,RemoteObjectInvocationHandler



其中的invokeRemoteMethod,看名字知道是方法调用,跟进一看,有ref.invoke



跟进invoke,有个marshalValue



继续跟进,这是判断参数类型的,最后序列化,这里没判断String类型,所以用String类型可以到





出来了,又调用了call.executeCall(),上面的漏洞点二,攻击手法一样,然后会判断返回值是否为空



不为空调用unmarshalValue,进去一看,同样是判断类型,然后反序列化,这就能反序列化攻击了

客户端请求服务端(注册中心被攻击)

创建注册中心的流程走一边走到listen里,看这个线程

跟进AcceptLoop,注意这个executeAcceptLoop



继续跟进,这里的execute又开了一个线程池



继续跟进,进入ConnectionHandler的run方法,也就是run0方法



继续跟进,StreamRemoteCall这可太熟了,但是它不是重点



继续跟进serviceCall



放弃。。。这里怎么断点断不住,直接手动进去查看



进入dispatch方法,这里是UnicastServerRef的方法



跟进oldDispatch,skel是Registry_Impl_Skel



跟进dispatch方法,case 2中反序列化



然后这里case对应情况如下

  • bind : 0
  • list : 1
  • lookup : 2
  • rebind : 3
  • unbind : 4

如果客户端传入恶意对象,那注册中心就会被反序列化攻击

客户端请求服务端(服务端被攻击)

回到上面的oldDispatch处,过了这个继续往下,调用unmarshalValue,很熟

判断是否为基本类型,传入Object,同样反序列化,寄

客户端请求服务端(DGC)

DGC就是RMI里垃圾回收机制,具体介绍如下:

分布式垃圾回收,又称 DGC,RMI 使用 DGC 来做垃圾回收,因为跨虚拟机的情况下要做垃圾回收没办法使用原有的机制。我们使用的远程对象只有在客户端和服务端都不受引用时才会结束生命周期。

而既然 RMI 依赖于 DGC 做垃圾回收,那么在 RMI 服务中必然会有 DGC 层,在 yso 中攻击 DGC 层对应的是 JRMPClient,在攻击 RMI Registry 小节中提到了 skel 和 stub 对应的 Registry 的服务端和客户端,同样的,DGC 层中也会有 skel 和 stub 对应的代码,也就是 DGCImpl_Skel 和 DGCImpl_Stub,我们可以直接从此处分析,避免冗长的 debug。

这个有点复杂,没太看懂,学会了再补充..............

RMI反序列化分析的更多相关文章

  1. Java安全之RMI反序列化

    Java安全之RMI反序列化 0x00 前言 在分析Fastjson漏洞前,需要了解RMI机制和JNDI注入等知识点,所以本篇文来分析一下RMI机制. 在Java里面简单来说使用Java调用远程Jav ...

  2. Java安全之RMI协议分析

    Java安全之RMI协议分析 0x00 前言 在前面其实有讲到过RMI,但是只是简单描述了一下RMI反序列化漏洞的利用.但是RMI底层的实现以及原理等方面并没有去涉及到,以及RMI的各种攻击方式.在其 ...

  3. Java安全之SnakeYaml反序列化分析

    Java安全之SnakeYaml反序列化分析 目录 Java安全之SnakeYaml反序列化分析 写在前面 SnakeYaml简介 SnakeYaml序列化与反序列化 常用方法 序列化 反序列化 Sn ...

  4. fastjson及其反序列化分析--TemplatesImpl

    fastjson及其反序列化分析 源码取自 https://www.github.com/ZH3FENG/PoCs-fastjson1241 参考 (23条消息) Json详解以及fastjson使用 ...

  5. [JavaWeb]反序列化分析(二)--CommonCollections1

    反序列化分析(二)--CommonCollections1 链子分析 首先新建一个TransformedMap,其中二三参数为可控,后续要用到 当TransformedMap执行put方法时,会分别执 ...

  6. javasec(五)URLDNS反序列化分析

    这篇文章介绍 URLDNS 就是ysoserial中⼀个利⽤链的名字,但准确来说,这个其实不能称作"利⽤链".因为其参数不是⼀个可以"利⽤"的命令,⽽仅为⼀个U ...

  7. Java学习---RMI 技术分析[Hessian]

    一.什么是Hessian Hessian 是一个基于 binary-RPC 实现的远程通讯 library.使用二进制传输数据.Hessian通常通过Web应用来提供服务,通过接口暴露.Servlet ...

  8. weblogic之CVE-2016-0638反序列化分析

    此漏洞是基于CVE-2015-4852漏洞进行黑名单的绕过,CVE-2015-4852补丁主要应用在三个位置上 weblogic.rjvm.InboundMsgAbbrev.class :: Serv ...

  9. shiro<1.2.4反序列化分析

    0x01.环境搭建 下载地址:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4 环境:Tomcat 8.5.27 + idea ...

  10. shiro550反序列化分析

    拖了很久的shiro分析 漏洞概述 Apache Shiro <= 1.2.4 版本中,加密的用户信息序列化后存储在Cookie的rememberMe字段中,攻击者可以使用Shiro的AES加密 ...

随机推荐

  1. CF1833F Ira and Flamenco

    题目链接 题解 知识点:组合数学,枚举,双指针. 注意到,长度为 \(m\) 且数字各不相同的子序列,那么最大值与最小值的差至少为 \(m-1\) .因此,对于任意子序列,它是合法的,当且仅当,将其从 ...

  2. NC24416 [USACO 2013 Nov G]No Change

    题目链接 题目 题目描述 Farmer John is at the market to purchase supplies for his farm. He has in his pocket K ...

  3. Rollup的基本使用

    Rollup的基本使用 rollup.js是一个模块打包工具,可以使项目从一个入口文件开始,将所有使用到的模块文件都打包到一个最终的发布文件中,Rollup极其适合构建一个工具库,Vue.js源码就是 ...

  4. lsattr命令

    lsattr命令 lsattr命令用于显示文件的属性. 语法 lsattr [-RVadlv] [file | folder] 参数 -a: 显示所有文件和目录,包括以.为名称开头字符的额外内建,即现 ...

  5. Vue+SpringBoot+ElementUI实战学生管理系统-10.学生管理模块

    1.章节介绍 前一篇介绍了教师管理模块,这一篇编写学生管理模块,需要的朋友可以拿去自己定制.:) 2.获取源码 源码是捐赠方式获取,详细请QQ联系我 :)! 3.实现效果 学生列表 修改学生 4.模块 ...

  6. Python之读取Excel

    介绍 现在交给你一份2010年美国各州县人口普查表:censuspopdata.xlsx.共72864条记录. 每一行代表一个县某统计区的人口数. 需要你统计出:各县统计区数量和人口数. 表格内容长这 ...

  7. MinGW 和 MSVC

    在 Winodws 上编译通常会用到这两种工具链 MinGW(Minimalist GNU for Windows) 通常用于跨平台开发,可以编译出在 Windows 系统上运行的 .exe 程序 M ...

  8. C++ 值,指针,引用的讨论

    源自 stackoverflow 论坛,很有意义 第一个问题,引用传递和按值传递的场合 There are four main cases where you should use pass-by-r ...

  9. day03--vi和vim快捷方式及操作系统目录介绍

    1.编辑命令vi vim是vi的增强版 提高编程的效率 1.vi命令模式信息 命令模式: 可实现很多特殊功能操作 不能输入任何内容 编辑模式: 可编辑内容信息 底行模式:可以输入特殊的命令信息 wq保 ...

  10. 【C++ OOP 02 对象的初始化和清理】构造/析构函数、深/浅拷贝、初始化列表以及静态成员

    [对象的初始化和清理] 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全 C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据 ...