freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

一命通关RMI三端反序列化攻击及JRMP高版本绕过
2024-09-20 09:38:00

说实话我每次看到RMI的流程我都觉得脑袋疼。而且分析完了都不记得分析了个什么鸟。

或许这次会好一点。

RMI远程类调用

RMI的作用就是客户端调用服务端的远程类。

我们开两个项目,一个做客户端,一个做服务端。用jdk8u65

RMIServer

定义一个继承了Remote的接口RemoteInterface

importjava.rmi.Remote;
importjava.rmi.RemoteException;

publicinterfaceRemoteInterfaceextendsRemote{
publicStringsayHello(Stringname) throwsRemoteException;
}

一个实现该接口的类RemoteImpl,需要继承UnicastRemoteObject

importjava.rmi.RemoteException;
importjava.rmi.server.UnicastRemoteObject;

publicclassRemoteImplextendsUnicastRemoteObjectimplementsRemoteInterface{
protectedRemoteImpl() throwsRemoteException{
}

publicStringsayHello(Stringname) {
System.out.println("Hello "+name);
return"Hello "+name;
}
}

RemoteImpl必须加入一个调用了父类构造函数的构造函数。

这里的无参构造函数实际上会自动向第一行插入一条隐式的super(),如下。而不写构造函数是自动生成无参构造函数,不满足调用super(args...);的要求,所以报错。

protectedRemoteImpl() throwsRemoteException{
super();
}

向外开放服务的RMIServer。需要LocateRegistry.createRegistry(1099),开放注册中心到1099端口。并把类绑定到注册中心。

importjava.rmi.AlreadyBoundException;
importjava.rmi.RemoteException;
importjava.rmi.registry.LocateRegistry;
importjava.rmi.registry.Registry;

publicclassRMIServer{
publicstaticvoidmain(String[] args) throwsRemoteException, AlreadyBoundException{
RemoteInterfaceremoteImpl=newRemoteImpl();
Registryregistry=LocateRegistry.createRegistry(1099);
registry.bind("remoteImpl", remoteImpl);
}
}


RMIClient

需要一个相同的接口RemoteInterface

importjava.rmi.Remote;
importjava.rmi.RemoteException;

publicinterfaceRemoteInterfaceextendsRemote{
publicStringsayHello(Stringname) throwsRemoteException;
}

使用服务端远程类的,客户端RMIClient

importjava.rmi.NotBoundException;
importjava.rmi.RemoteException;
importjava.rmi.registry.LocateRegistry;
importjava.rmi.registry.Registry;

publicclassRMIClient{
publicstaticvoidmain(String[] args) throwsRemoteException, NotBoundException{
Registryregistry=LocateRegistry.getRegistry("127.0.0.1", 1099);
RemoteInterfaceremoteImpl=(RemoteInterface) registry.lookup("remoteImpl");
System.out.println(remoteImpl.sayHello("RMI"));
}
}

先开服务端,再开客户端。客户端就能调用到服务端上的方法sayHello

源码分析

移步视频:

https://www.bilibili.com/video/BV1L3411a7ax?p=1&vd_source=732f44595cd3e361ab78ff559f3c5ab5

此处概述+只给利用点和利用方式,因为分析了也记不住。我直接偷偷分析,挂上来我自己都不看。

偷个包浆RMI通信老图

20210227013102-65c85794-7858-1

先从服务端开始

服务端

RemoteInterface remoteImpl = new RemoteImpl();

从UnicastServerRef一路跟到了TCPEndpoint.getLocalEndpoint,获取了IP

image-20240808174637721

在UnicastServerRef.exportObject完成了创建远程对象的主要逻辑,前面都是层层封装,没什么好看的。

生成了一个stub并赋值,这是服务端stub

image-20240808175819215

实际上,赋值调用的Util.creatProxy()内部,就是生成了一个代理类。等于说stub就是个代理类,如下

handler来自RemoteObjectInvocationHandler,看到该类满足代理handler的要求,继承了InvocationHandler

image-20240808180905120

image-20240808181139454

还记得动态代理吗,调用代理类的方法会自动走进handler的invoke方法(虽然这里没调用,但是为后面你使用sayHello的时候做了铺垫)

这里RemoteObjectInvocationHandler.invoke就是加了个判断的普通invoke执行方法

image-20240808181443160

下面一堆调用,从ref.exportObject跟到TCPTransport.exportObject。listen()方法开始监听。跟进去看看

image-20240808182452854

从ep中提取了Endpoint(翻译为终端)和端口。ep就是之前TCPEndpoint.getLocalEndpoint生成的。然后调用了newServerSocket

image-20240808182801736

在newServerSocket中,createServerSocket新建了一个Websocket。并且监听端口为0,会调用setDefaultPort获取端口。从前面的调试信息也可以看到,一路过来端口都是默认的0

image-20240808183049077

setDefaultPort内部,获取了所有的本地未开放端口,并异步循环取最后一个端口。就是随机取一个端口啦

image-20240808183329514

从这里开始,port就有值了。

image-20240808183541809

继续运行到UnicastServerRef.exportObject。在get不到值的时候会向map里put键值。

image-20240808202655547

image-20240808202749065

虽然是put进去了,但是只有个类名。方法名还需要跳转到UnicastServerRef$HashToMethod_Maps.computeValue中获取

image-20240808203348116

从接口中循环获取方法名,设为可访问,put进map中

image-20240808203552048

image-20240808203719584


最后我们生成的remoteImpl对象,有LiveRef包含的host和一个随机的端口号,还有装填了方法的hashToMethod_Map。这些都封装在UnicastServerRef的一个对象中。创建了一个代理类stub,但是return时并没有存储,只是写了个逻辑在这。

image-20240808203908620

但是服务端开随机的端口号,客户端怎么知道这个端口号来获取类呢?下一步就是解决这个问题


Registry registry = LocateRegistry.createRegistry(1099);

新建了一个指定端口为1099空的LiveRef对象

image-20240810201839454

在随后的setup函数中,同样的来到UnicastServerRef.exportObject完成代理对象stub的创建。这是注册端stub

image-20240810205629364

注意,这里创建stub时,跟进Util.creatProxy->stubClassExists()。

image-20240911123302805

如果存在remoteClass_Stub类(在这里就是RegistryImpl_Stub类),返回true

image-20240911103837939

可以看到该类是存在的

image-20240911104013368

在creatStub创建了RegistryImpl_Stub类

# RMI漏洞 # Java反序列化漏洞分析 # RMI服务攻击
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录