初识FastJson
Background
FastJson提供了toJsonString和parseObject方法可用于将一个JavaBean转换为Json数据, 或将Json数据还原为JavaBean对象。
FastJson Version:1.2.24
1 | <!-- pom.xml --> |
Java Bean序列化Json
1 | public class FastJsonLearning { |
输出:
{“age”:19,”name”:”Search?=Null”}
其在序列化对象为Json时会调用Bean对象的get方法(构造方法是实例化对象时调用的, 非序列化所调用):

相反, 在将Json转换为Java对象时也会调用其set方法:

探测FastJson
1 | {"@type":"java.net.InetAddress","val":"dnslog"} |
使用如上Payload当dnslog收到请求时则证明后端使用了FastJson, 为何该Payload能够发起DNS请求。跟随本文从parseObject开始分析。
com.alibaba.fastjson.JSON#parseObject
接收json数据调用parse方法:

调用parse方法传入json数据和DEFAULT_PARSE_FEATURE(989):

实例化com.alibaba.fastjson.DefaultJSONParser对象时其会判断JSON数据的第一个是否为{还是[, 根据不同结果赋值token, 然后调用DefaultJSONParser#parse

进入case 12的分支实例化JSONObject对象后调用parseObject方法:

在parseObject中的while循环所谓又长又臭(足足400余行代码), 遂只能一行一行琢磨该循环中都做了哪些事。循环的第一行代码便是调用skipWhitespace方法, 在该方法中会判断字符是否为空或换行符等, 因此在JSON数据中加入空格和换行符或许可以规避WAF的检测并正常解析:

1 | // 如下两种形式均可解析发起DNS请求 |
回到循环中继续向下看代码, 如果AllowArbitaryCommas开启的话(默认开启)并且当前字符为,号会跳过该字符继续向下解析。因此这部分代码是用于跳过JSON数据中的空格, 换行符, 及逗号的解析。

下表为复现版本中各配置项属性默认值:
| 属性 | 默认值 |
|---|---|
| AutoCloseSource | True |
| AllowComment | False |
| AllowUnQuotedFieldNames | True |
| AllowSingleQuotes | True |
| InternFieldNames | True |
| AllowISO8601DateFormat | False |
| AllowArbitraryCommas | True |
| UseBigDecimal | True |
| IgnoreNotMatch | True |
| SortFeidFastMatch | True |
| DisableASM | False |
| DisableCircularReferenceDetect | False |
| InitStringFieldAsEmpty | False |
| SupportArrayToBean | False |
| OrderedField | False |
| DisableSpecialKeyDetect | False |
| UseObjectArray | False |
| SupportNonPublicField | False |
从代码172行开始就是正式解析JSON数据的部分了, 当前字符为"时调用scanSymbol方法获取其双引号中的内容为键, 获得完整的键名后如果其后面的字符不为:则会抛出异常。

获取到键的值后调用resetStringposition重置字符串位置为0, 判断key如果是@type并且DisableSpecialKeyDetect为关闭情况下, 依旧通过scanSymbol获取value的值(value是完整类名)。通过TypeUtils.loadClass加载类。

在TypeUtils#loadClass中获取当前线程的类加载器加载类并写入mappings中:

加载完类后nextToken用于判断当前字符为逗号时跳过逗号继续解析, 接着通过ParserConfig#getDeserializer方法获取ParserConfig对象deserializer属性指定Key的值。

其返回的是一个MiscCodec对象:

最后调用MiscCodec.deserialze(DefaultJSONParser, java.net.InetAddress.class, null)并return该返回值。对应代码:
1 | thisObj = deserializer.deserialze(this, clazz, fieldName); |
MiscCodec#deserialze在else分支中判断键的内容为val后解析其value的内容:

最终在MiscCodec322行处会调用InetAddress#getByName发起DNS请求:

总结
- FastJson1.2.24版本默认开启AutoType
- FastJson1.2.24默认会忽略空格 注释符并可以解析十六进制和unicode字符
- FastJson1.2.24可使用白名单的
InetAddress类进行DNS探测