JAVA-RMI反序列闲谈

JAVA-RMI反序列闲谈

3月 21, 2018 阅读 8407 字数 4219 评论 0 喜欢 3

一、相关概念简述

1. 什么是JAVA RMI?

RMI是Remote Method Invocation的简称,是J2SE的一部分,能够让程序员开发出基于Java的分布式应用。一个RMI对象是一个远程Java对象,可以从另一个Java虚拟机上(甚至跨过网络)调用它的方法,可以像调用本地Java对象的方法一样调用远程对象的方法,使分布在不同的JVM中的对象的外表和行为都像本地对象一样。Java RMI 支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。

Java RMI极大地依赖于接口。在需要创建一个远程对象的时候,程序员通过传递一个接口来隐藏底层的实现细节。客户端得到的远程对象句柄正好与本地的根代码连接,由后者负责透过网络通信。这样一来,程序员只需关心如何通过自己的接口句柄发送消息。

 

很多使用Java开发的Web也都使用了分布式分发的结构,比如我所了解的很多大型组织都会在后台部署一些Java应用,用于向对外网站发布更新的静态页面,而这种发布命令的下达使用的就是RMI。

2. 什么是Apache Commons Collections?

在Java集合框架 是JDK 1.2的一大补充。它增加了许多强大的数据结构,加速了最重要的Java应用程序的开发。从那时起,它已经成为Java中集合处理的公认标准。

Commons-Collections试图通过提供新的接口,实现和实用程序来构建JDK类。有许多功能,都是对java方法的二次封装,大大减少开发难度。

一些通过Java实现的CS架构应用(比如:大型国企都喜欢用的会计软件、内容发布系统),也会用到Apache Commons Collections这个库。

3. 什么是序列化与反序列化?

序列化是指将对象的状态信息转换为可以存储或传输的形式的过程。

在Java中创建的对象,只要没有被回收就可以被复用,但是,创建的这些对象都是存在于JVM的堆内存中,JVM处于运行状态时候,这些对象可以复用,

但是一旦JVM停止,这些对象的状态也就丢失了。

在实际生活中,需要将对象持久化,需要的时候再重新读取出来,通过对象序列化,可以将对象的状态保存为字节数组,需要的时候再将字节数组反序列化为对象。

对象序列化可以很容易的在JVM中的活动对象和字节数组(流)之间转换,广泛用于RMI(远程方法调用)以及网络传输中。

4. 三者结合会产生什么?

Apache Commons Collections是java程序员使用非常多的基础类库,一旦编程人员误用了反序列化这一机制,在RMI远程调用时使得用户输入可以直接被反序列化,就能导致任意代码执行。

WebLogic、WebSphere、JBoss、Jenkins、OpenNMS都是出名的java应用,最近被爆出都能利用java反序列实现命令执行。

RMI的传输过程必然会用到序列化和反序列化,那么如果RMI服务端接口对外开放,并且服务端使用了像Apache Commons Collections这样的库,很容易被攻击。

二、攻击工具演变

emmmmmm文章是之前写的,word里面保存的图片好像画质不够,没劲。。。。。

1.ysoserial

最初RMI服务测试主要使用ysoserial,只能通过执行命令来监听反弹的shell。值得注意的是,ysoserial更加地通用,它是Java对象反序列化的有效负载的概念验证工具,能够进行各种反序列化测试。

该工具目前在github上可以找到。下载地址:https://jitpack.io/com/github/frohoff/ysoserial/

2.RMIexploit.jar

然后出现了通过远程加载服务器上jar包执行命令,达到了命令回显。工具的缺点在于存在漏洞的主机不能出外网(或防火墙)就不能反弹shell,同时需要在外网主机搭建反弹shell得环境。

 

3.attackRMI.jar

然后出现了新的利用工具,通过序列化在目标主机生成jar,再通过rmi进行回显,解决了2中的问题。只需要输入ip port 就可以进行远程命令执行

稍后会将这个工具进行解包反编译,重写代码,达到我们想要的功能。

三、原理分析

这里参考长亭科技的文章,膜拜一波。

1. 序列化与反序列化分析

序列化就是把对象转换成字节流,便于保存在内存、文件、数据库中;反序列化即逆过程,由字节流还原成对象。Java中的ObjectOutputStream类的writeObject()方法可以实现序列化,类ObjectInputStream类的readObject()方法用于反序列化。下面是将字符串对象先进行序列化,存储到本地文件,然后再通过反序列化进行恢复的样例代码:

问题在于,如果Java应用对用户输入,即不可信数据做了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就有可能带来任意代码执行。

所以这个问题的根源在于类ObjectInputStream在反序列化时,没有对生成的对象的类型做限制;假若反序列化可以设置Java类型的白名单,那么问题的影响就小了很多。

2.利用Apache Commons Collections实现远程代码执行

接下来以Apache Commons Collections 3为例,来解释如何构造对象,能够让程序在反序列化,即调用readObject()时,就能直接实现任意代码执行。

Map类是存储键值对的数据结构,Apache Commons Collections中实现了类TransformedMap,用来对Map进行某种变换,只要调用decorate()函数,传入key和value的变换函数Transformer,即可从任意Map对象生成相应的TransformedMapdecorate()函数如下:

Transformer是一个接口,其中定义的transform()函数用来将一个对象转换成另一个对象。如下所示:

当Map中的任意项的Key或者Value被修改,相应的Transformer就会被调用。除此以外,多个Transformer还能串起来,形成ChainedTransformer

Apache Commons Collections中已经实现了一些常见的Transformer,其中有一个可以通过调用Java的反射机制来调用任意函数,叫做InvokerTransformer,代码如下:

 

只需要传入方法名、参数类型和参数,即可调用任意函数。因此要想任意代码执行,我们可以首先构造一个Map和一个能够执行代码的ChainedTransformer,以此生成一个TransformedMap,然后想办法去触发Map中的MapEntry产生修改(例如setValue()函数),即可触发我们构造的Transformer

测试代码如下:

 

当上面的代码运行到setValue()时,就会触发ChainedTransformer中的一系列变换函数:首先通过ConstantTransformer获得Runtime类,进一步通过反射调用getMethod找到invoke函数,最后再运行命令calc.exe。

但是目前的构造还需要依赖于触发Map中某一项去调用setValue(),我们需要想办法通过readObject()直接触发。

我们观察到java运行库中有这样一个类AnnotationInvocationHandler,这个类有一个成员变量memberValuesMap类型,如下所示:

更令人惊喜的是,AnnotationInvocationHandlerreadObject()函数中对memberValues的每一项调用了setValue()函数,如下所示:

 

因此,我们只需要使用前面构造的Map来构造AnnotationInvocationHandler,进行序列化,当触发readObject()反序列化的时候,就能实现命令执行。另外需要注意的是,想要在调用未包含的package中的构造函数,我们必须通过反射的方式,综合生成任意代码执行的payload的代码如下:

以上解释了如何通过Apache Commons Collections 3这个库中的代码,来构造序列化对象,使得程序在反序列化时可以立即实现任意代码执行。

四、工具解包重写

1.解包反编译分析

目前拿到的是attackRMI.jar 用IDEA进行解包反编译得到源码(ps:太菜了不怎么会java,也不做开发,IDEA自带反编译插件,eclipse并没有。)

得到如下图目录结构:

org目录是Apache Commons Collections库

com目录是UI库

AttackRMI.class是提供攻击方法

RMIattack.class是主类文件

AttackRMI.class提供了如下方法:

GEN_FirstPayload()用于第一次攻击前的数据包构造,bytes内容为用于回显的ErrorBaseExec.class

exeuateFirstRMI()用于执行第一次攻击

其他函数构造与此类似

GEN_secondPayload()用于封装需要执行的命令的数据

exeuateSecondRMI()用于执行命令,并利用ErrorBaseExec.class回显结果。

当然,也可以写几行简单的代码ErrorBaseExec.class显示出来研究,说不定有意外收获,比如bypass waf啥的。

得到的ErrorBaseExec.class如下:

2.工具重写

反编译后的ui库不是完整,所以弃用了,采用命令行传参。(其实是太菜不会java哈哈哈)

原来工具只有一个命令执行,个人想添加文件上传功能,关键代码如下:

思路:从本地读入文件,转化成字节流

然后再用Apache Commons Collections中提供的方法进行上传:

判断主机类型,上传msf-payload,执行即可返回shell。我就上传一个msf生成的马吧

当然,也可以上传任何你想上传的文件。

 

改写后增加了自动化流程,能够一键getshell

 

3.攻击效果演示

 

这里我准备将msf生成的木马上传了上去,自动执行命令,得到shell

效果如下:

Msf开始监听

开始执行攻击:

接收到回应:

得到系统shell:

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注