Java安全之ysoserial-JRMP模块分析(一)

首发安全客:Java安全之ysoserial-JRMP模块分析(一)

0x00 前言

在分析到Weblogic后面的一些绕过方式的时候,分析到一半需要用到ysoserial-JRMP该模块。不止是Weblogic的反序列化漏洞会利用到,其他的反序列化漏洞也会利用到,所以在此对该模块做一个分析。了解底层原理,一劳永逸。但看到网上分析文章偏少,如有分析错误望师傅们指出。

概述

在这里简单来讲讲JRMP协议相关内容,JRMP是一个Java远程方法协议,该协议基于TCP/IP之上,RMI协议之下。也就是说RMI该协议传递时底层使用的是JRMP协议,而JRMP底层则是基于TCP传递。

RMI默认使用的JRMP进行传递数据,并且JRMP协议只能作用于RMI协议。当然RMI支持的协议除了JRMP还有IIOP协议,而在Weblogic里面的T3协议其实也是基于RMI去进行实现的。

RMI内容,具体参考:Java安全之RMI协议分析

0x01 JRMP模块利用

一、 ysoserial中的exploit/JRMPClient是作为攻击方的代码,一般会结合payloads/JRMPLIstener使用。

攻击流程如下:

  1. 需要发送payloads/JRMPLIstener内容到漏洞服务器中,在该服务器反序列化完成我们的payload后会开启一个RMI的服务监听在设置的端口上。
  2. 我们还需要在我们自己的服务器使用exploit/JRMPClient与存在漏洞的服务器进行通信,并且发送一个gadgets对象,达到一个命令执行的效果。(前面说过RMI协议在传输都是传递序列化,接收数据后进行反序列化操作。)

简单来说就是将一个payload发送到服务器,服务器反序列化操作该payload过后会在指定的端口开启RMI监听,然后通过exploit/JRMPClient 去发送攻击 gadgets对象。

二、第二种利用方式和上面的类似exploit/JRMPListener作为攻击方进行监听,在反序列化漏洞位置发送payloads/JRMPClient向我们的exploit/JRMPListener进行连接,连接后会返回在exploit/JRMPListener的gadgets对象并且进行反序列化

攻击流程如下:

  1. 攻击方在自己的服务器使用exploit/JRMPListener开启一个rmi监听

  2. 往存在漏洞的服务器发送payloads/JRMPClient,payload中已经设置了攻击者服务器ip及JRMPListener监听的端口,漏洞服务器反序列化该payload后,会去连接攻击者开启的rmi监听,在通信过程中,攻击者服务器会发送一个可执行命令的payload(假如存在漏洞的服务器中有使用org.apacje.commons.collections包,则可以发送CommonsCollections系列的payload),从而达到命令执行的结果。

在前文中的 Java 安全之Weblogic 2017-3248分析文章中,用到的时候第二种方式进行绕过补丁。前文中并没有对该模块去做分析,只是知道了利用方式和绕过方式,下面对JRMP模块去做一个深入的分析。查看内部是如何实现该功能的。

0x01 payloads/JRMPListener

该链的作用是在反序列化过后,在指定端口开启一个JRMP Server。后面会配合到exploit/JRMPClient连接并且发送payload。

利用链

下面来看一下他的利用链

/**
* Gadget chain:
* UnicastRemoteObject.readObject(ObjectInputStream) line: 235
* UnicastRemoteObject.reexport() line: 266
* UnicastRemoteObject.exportObject(Remote, int) line: 320
* UnicastRemoteObject.exportObject(Remote, UnicastServerRef) line: 383
* UnicastServerRef.exportObject(Remote, Object, boolean) line: 208
* LiveRef.exportObject(Target) line: 147
* TCPEndpoint.exportObject(Target) line: 411
* TCPTransport.exportObject(Target) line: 249
* TCPTransport.listen() line: 319
*
* Requires:
* - JavaSE
*
* Argument:
* - Port number to open listener to
*/

构造分析

首先需要查看一下yso里面是如何生成gadget对象的。

可以直接定位到getObject方法中。

getObject方法中前面第一行代码获取了外部传入进来的端口,转换成int类型。

这个比较简单,主要内容在下面这段代码中。

使用Reflections.createWithConstructor方法传入三个参数获取到一个UnicastRemoteObject的实例对象。传入的参数第一个是ActivationGroupImpl.class,第二个是RemoteObject.class,而第三个则是一个Object的数组,数组中里面是RemoteRef.class,第四个是UnicastServerRef传入了刚刚获取的端口的一个实例对象。

第一个参数使用的是 ActivationGroupImpl 是因为在利用的时候,本身就是利用的 UnicastRemoteObject 的 readObject 函数,第二个参数需要满足两个条件:

  1. 要为 UnicastRemoteObject 的父类

  2. 不能在创建的过程中有其他什么多余的操作,满足这两个条件的两个类是:RemoteObject、RemoteServer

最后具体是怎么获取到的UnicastRemoteObject实例对象,这里需要调试跟踪一下。

UnicastServerRef分析

在此之前,先来看看new UnicastServerRef(jrmpPort)的内部实现。先跟踪最里层的方法。

UnicastServerRef的构造方法,内部会去再new一个LiveRef对象并且传入输入进来的端口的参数。

选择跟踪。

内部是new了一个ObjID,继续跟踪。

里面还会去new一个UID赋值给space成员变量,UID这里自然都知道是啥意思,这里就不跟了,而下面随机获取一个值赋值给objNum。

ObjID

  • ObjID用于标识导出到RMI运行时的远程对象。 导出远程对象时,将根据用于导出的API来隐式或明确地分配一个对象标识符。

  • 构造方法:

    ObjID()
    生成唯一的对象标识符。
    ObjID(int objNum)
    创建一个“众所周知”的对象标识符。

执行完成后返回到这一步。

这里调用了构造方法的重载方法。选择跟踪一下。

到了这一步,var1的参数自然不用解释,而后面的则是传入的端口。

里面再一次调用重载方法,并且在传递的第二个参数调用了TCPEndpoint.getLocalEndpoint并且传入端口进行获取实例化对象。继续跟踪。

内部调用getLocalEndpoint重载方法,跟踪。

getLocalEndpoint方法说明:

获取指定端口上本地地址空间的终结点。如果端口号为0,则返回共享的默认端点对象,其主机名和端口可能已确定,也可能尚未确定。

内部调用localEndpoints.get方法并且传入var5,也就是TCPEndpoint的实例对象。

localEndpoints是一个map类型的类对象,这里get方法获取了var5,对应的value值,类型为LinkedList。这里获取到的是一个null。

执行到下一步

调用resampleLocalHost方法获取String的值,跟踪查看实现。

localHost的值是通过getHostnameProperty方法进行获取的。

执行完成后,返回到sun.rmi.transport.tcp#TCPEndpoint,执行到一下代码中。

这里的代码比较容易理解,var为空,new一个TCPEndpoint对象,并且传入var7,var0,var1,var2。参数值是ip,端口,null,null。将该对象添加到var6里面。

后面则是对var3的对象进行赋值,ip和端口都赋值到var3的成员变量里面去。

最后就是调用localEndpoints.put(var5, var6);讲var5, var6存储到localEndpoints中。

最后进行返回var3对象。

执行完成后,回到这里

继续跟踪,构造方法的重载方法。

这里就没啥好说的了,就是赋值。

最后返回到外面入口的地方

调用了父类的构造方法

到了这里其实就已经跟踪完了。

yos利用链分析

返回到这一步跟踪Reflections.createWithConstructor查看内部实现。

简化一下代码:

Constructor<? super T> objCons = RemoteObject.class.getDeclaredConstructor(new UnicastServerRef(jrmpPort));

其实也就是反射调用获取 RemoteObject参数为UnicastRef的构造方法。并且传递new UnicastServerRef(jrmpPort)实例化对象作为构造方法参数。

而下面的setAccessible(objCons);这个就不做分析了,分析过前面的利用链都大概清楚,这个其实就是修改暴力反射的一个方法类。

看到下面这段代码

这里进行跟踪。

其实借助ReflectionFactory.getReflectionFactory()工厂方法在这里就是返回了ReflectionFactory的实例对象。

跟踪newConstructorForSerialization方法

这里传递的var1 参数是ActivationGroupImpl.class对象,而var2是刚刚反射获取的Constructor对象。

下面是个三目运算,如果var2.getDeclaringClass() == var1的话,返回var2,如果不低于的话,调用this.generateConstructor(var1, var2);后的执行结果进行返回。

将代码简单化:

ActivationGroupImpl.class.getDeclaringClass()==ActivationGroupImpl.class ? var2
:this.generateConstructor(ActivationGroupImpl.class, var2)

这里调用了this.generateConstructor方法并且传入了两个参数。后来才发现后面的这些内容是属于反射的底层实现,跟踪跑偏了。感兴趣的师傅们可以自行查看。

Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);

返回到这段代码,后来的查询资料发现newConstructorForSerialization这个方法返回的是一个无参的constructor对象,但是绝对不会与原来的constructor冲突,被称为munged 构造函数

这里先来思考到一个问题,为什么不能使用反射直接调用呢?

其实并非所有的java类都有无参构造方法的,并且有的类的构造方法还是private的。所以这里采用这种方式进行获取。

再来看到上面的代码:

Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);

前面参数为ActivationGroupImpl.class,指定获取ActivationGroupImpl.class的 Constructor。后面的参数为反射获取RemoteObject的RemoteRef类型构造方法获取到的Constructor类。

最后将参数传递进行,返回创建一个ActivationGroupImpl实例化对象。

执行完成回到这个方法内,发现该地方对ActivationGroupImpl进行了向上转型为UnicastRemoteObject类型

最后调用反射将UnicastRemoteObject的实例对象的port字段修改成我们设置的端口的值。

0x02 调试分析

test类:

package ysoserial.test;

import ysoserial.payloads.JRMPClient;
import ysoserial.payloads.JRMPListener; import java.io.*;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject; public class test {
public static void main(String[] args) throws Exception {
JRMPListener jrmpListener = new JRMPListener();
UnicastRemoteObject object = jrmpListener.getObject("9999");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream bjos = new ObjectOutputStream(bos);
bjos.writeObject(object); ByteArrayInputStream bait = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ojis = new ObjectInputStream(bait);
Object o = ojis.readObject(); }
}

这里是利用了UnicastRemoteObjectreadObject作为反序列化的入口点。

在此处下断点开始调试分析。

readObject方法处调用了reexport方法,跟踪查看。

csf和ssf为空,执行到这里。

调用exportObject 方法并且传入this和port 这里的this,实际上是ActivationGroupImpl,因为前面进行了向上转型。跟踪exportObject

这里再次调用重载方法,跟踪查看。

到了这一步,调用sref.exportObject传入前面创建的实例对象。跟踪。

这里下面调用this.ref,而this.ref为LiveRef对象。这一段则是调用LiveRef.exportObject。继续跟踪。

this.ep为Endpoint对象,这里调用的是Endpoint.exportObject,这里的对象是怎么赋值的前面的构造分析的时候去讲过,这里不做多的赘述。

调用this.transport.exportObject;继续跟踪。

到了这一步就调用了this.listen()进行启动监听。

参考文章

ysoserial JRMP相关模块分析(一)- payloads/JRMPListener

0x03 结尾

JRMP的这个模块第一次分析还是挺费劲的,网上的相关资料也偏少。

Java安全之ysoserial-JRMP模块分析(一)的更多相关文章

  1. Java高级项目实战02:客户关系管理系统CRM系统模块分析与介绍

    本文承接上一篇:Java高级项目实战之CRM系统01:CRM系统概念和分类.企业项目开发流程 先来CRM系统结构图: 每个模块作用介绍如下: 1.营销管理 营销机会管理:针对企业中客户的质询需求所建立 ...

  2. Java 安全之Weblogic 2018-2628&2018-2893分析

    Java 安全之Weblogic 2018-2628&2018-2893分析 0x00 前言 续上一个weblogic T3协议的反序列化漏洞接着分析该补丁的绕过方式,根据weblogic的补 ...

  3. Java 9 揭秘(4. 模块依赖)

    文 by / 林本托 Tips 做一个终身学习的人. 在此章节中,主要学习以下内容: 如何声明模块依赖 模块的隐式可读性意味着什么以及如何声明它 限定导出(exports)与非限定导出之间的差异 声明 ...

  4. Java安全之Cas反序列化漏洞分析

    Java安全之Cas反序列化漏洞分析 0x00 前言 某次项目中遇到Cas,以前没接触过,借此机会学习一波. 0x01 Cas 简介 CAS 是 Yale 大学发起的一个开源项目,旨在为 Web 应用 ...

  5. Ysoserial Commons Collections2分析

    Ysoserial Commons Collections2分析 About Commons Collections2 CC2与CC1不同在于CC2用的是Commons Collections4.0; ...

  6. Ysoserial Commons Collections3分析

    Ysoserial Commons Collections3分析 写在前面 CommonsCollections Gadget Chains CommonsCollection Version JDK ...

  7. Ysoserial Commons Collections7分析

    Ysoserial Commons Collections7分析 写在前面 CommonsCollections Gadget Chains CommonsCollection Version JDK ...

  8. Java安全之C3P0利用与分析

    Java安全之C3P0利用与分析 目录 Java安全之C3P0利用与分析 写在前面 C3P0 Gadget http base C3P0.getObject() 序列化 反序列化 Class.forN ...

  9. Linux下java进程CPU占用率高分析方法

    Linux下java进程CPU占用率高分析方法 在工作当中,肯定会遇到由代码所导致的高CPU耗用以及内存溢出的情况.这种情况发生时,我们怎么去找出原因并解决. 一般解决方法是通过top命令找出消耗资源 ...

随机推荐

  1. CCNP之静态路由实验报告

                   静态路由实验报告 一.实验要求: 1.内网IP基于172.16.0.0/16自行子网划分 2.除了R2--R4路由器各有两个环回接口 3.R1下的PC自动获取IP地址 4 ...

  2. MySQL:判断逗号分隔的字符串中是否包含某个字符串 && 如何在一个以逗号分隔的列表中的一个字段中连接MySQL中的多对多关系中的数据

    需求:      sql语句中,判断以逗号分隔的字符串中是否包含某个特定字符串,类似于判断一个数组中是否包含某一个元素, 例如:判断 'a,b,c,d,e,f,g' 中是否包含 'a',sql语句如何 ...

  3. js Table表格选中一行变色或者多选 并获取值

    使用JQ <script> let old, oldColor; $("#sp_body tr").click(function (i) { if (old) oldC ...

  4. 通过PHP代码将大量数据插入到Sqlite3

    PHP代码 读入txt文件,并写入到sqlite数据库里 <?php date_default_timezone_set('PRC'); $pdo = new PDO('sqlite:db/qq ...

  5. 用anaconda的pip安装第三方python包

    启动anaconda命令窗口: 开始> 所有程序> anaconda> anaconda prompt会得到两行提示: Deactivating environment " ...

  6. [LeetCode]234. Palindrome Linked List判断回文链表

    重点是: 1.快慢指针找到链表的中点.快指针一次走两步,慢指针一次走一步,分清奇偶数情况. 2.反转链表.pre代表已经反转好的,每次将当前节点指向pre /* 快慢指针得到链表中间,然后用206题方 ...

  7. 01 . Go之从零实现Web框架(类似Gin)

    设计一个框架 大部分时候,我们需要实现一个 Web 应用,第一反应是应该使用哪个框架.不同的框架设计理念和提供的功能有很大的差别.比如 Python 语言的 django和flask,前者大而全,后者 ...

  8. Kafka 消费组消费者分配策略

    body { margin: 0 auto; font: 13px / 1 Helvetica, Arial, sans-serif; color: rgba(68, 68, 68, 1); padd ...

  9. volatile实现原理--为什么实现了可见性却不能保证原子性

    本篇文章我们来解决一个问题  这也是面试面的比较多的问题,进阶阶段(高级)一般都会问到. volatile变量怎么保证可见性  为什么在并发情况下无法保证原子性? 比较懒了  摘了一段JVM原理的片段 ...

  10. @Transactional注解失效的解决方案

    一.前言 开发中我们经常使用 @Transactional注解来启用Spring事务管理,但是如果使用方法不当,会遇到注解不生效该事务回滚的地方却没有回滚的问题. 总结下一般是以下几个原因: @Tra ...