freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

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

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

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

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

说实话我每次看到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类

可试读前30%内容
¥ 14.9 全文查看
9.9元开通FVIP会员
畅读付费文章
最低0.3元/天
# RMI漏洞 # Java反序列化漏洞分析 # RMI服务攻击
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 godownio 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
JAVA—反序列化探究
godownio LV.3
Something for nothing
  • 14 文章数
  • 9 关注者
apache Dubbo反序列化全集
2025-02-25
JNDI绕过高版本注入限制那些事
2024-12-04
XStream反序列化漏洞合集
2024-10-31
文章目录