朋友们现在只对常读和星标的公众号才展示大图推送,建议大家把“亿人安全****“设为星标”,否则可能就看不到了啦
**原文由作者授权,首发在奇安信攻防社区
**
https://forum.butian.net/share/1679
Java生态中基本只有Jackson和Fastjson组件,但是两者相关的版本均存在相应的漏洞(反序列化、DDOS),所以对目标是否使用了对应的组件需要有相关的判断方法。方便信息收集进行进一步的测试。
Java生态中基本只有Jackson和Fastjson组件,但是两者相关的版本均存在相应的漏洞(反序列化、DDOS),那么如何有效识别目标使用了哪种对应的组件就很有必要了。
理想状态下如果站点有原始报错回显,可以用不闭合花括号的方式进行报错回显,报错中往往中会有Fastjson/Jackson的关键字:
Jackson:
Fastjson:
但是实际上并不可能那么的理想,所以需要一些其他的trick来进行区分。下面探讨下两个解析器之间有什么区别。
FastJson和Jackson在序列化和反序列化的过程中提供了很多特性(Feature),例如Fastjson的Feature.DisableFieldSmartMatch(1.2.30引入)。如果没有选择该Feature,那么在反序列的过程中,FastJson会自动把下划线命名的Json字符串转化到驼峰式命名的Java对象字段中。
简单看下两个解析器是如何加载Feature的。
以1.2.24版本为例,查看常用的解析方法,在对json文本进行解析时,一般会使用JSON.parse(text),默认配置如下:
public static Object parse(String text) {
return parse(text, DEFAULT_PARSER_FEATURE);
}
DEFAULT_PARSER_FEATURE是一个缺省默认的feature配置:
public static int DEFAULT_PARSER_FEATURE;
static {
int features = 0;
features |= Feature.AutoCloseSource.getMask();
features |= Feature.InternFieldNames.getMask();
features |= Feature.UseBigDecimal.getMask();
features |= Feature.AllowUnQuotedFieldNames.getMask();
features |= Feature.AllowSingleQuotes.getMask();
features |= Feature.AllowArbitraryCommas.getMask();
features |= Feature.SortFeidFastMatch.getMask();
features |= Feature.IgnoreNotMatch.getMask();
DEFAULT_PARSER_FEATURE = features;
}
可以通过Feature类的isEnabled方法来判断相关的Feature是否开启:
package com.alibaba.fastjson.parser;
public enum Feature
{
AutoCloseSource, AllowComment, AllowUnQuotedFieldNames, AllowSingleQuotes, InternFieldNames, AllowISO8601DateFormat, AllowArbitraryCommas, UseBigDecimal, IgnoreNotMatch, SortFeidFastMatch, DisableASM, DisableCircularReferenceDetect, InitStringFieldAsEmpty, SupportArrayToBean, OrderedField, DisableSpecialKeyDetect, UseObjectArray, SupportNonPublicField;
public final int mask;
private Feature() {
this.mask = (1 << ordinal());
}
public final int getMask() {
return this.mask;
}
public static boolean isEnabled(int features, Feature feature) {
return (features & feature.mask) != 0;
}
......
}
Jackson主要是在com.fasterxml.jackson.core.JsonFactory对Feature进行管理。在类加载时会先把相关Feature的默认值进行采集:
每个Feature都会有自己的默认值,例如下图中的USE_BIG_DECIMAL_FOR_FLOATS主要是将浮点数反序列化为BIG_DECIMAL,默认是False:
同样的,springboot在org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration进行装配时,如果没有其他配置,会把这些默认的Feature配置进行装载:
既然两者都在在序列化和反序列化的过程中提供了很多特性(Feature),而两者之间的Feature肯定是有区别的,可以利用这一点看看能不能找到一些思路用户两者的区分。
根据前面的思路,可以根据两者默认的Feature配置或者设计上的区别来进行区分。下面列举一些可用的trick。
JsonReadFeature的配置也是一样的:
public enum JsonReadFeature implements FormatFeature {
ALLOW_JAVA_COMMENTS(false, JsonParser.Feature.ALLOW_COMMENTS),
ALLOW_YAML_COMMENTS(false, JsonParser.Feature.ALLOW_YAML_COMMENTS),
ALLOW_SINGLE_QUOTES(false, JsonParser.Feature.ALLOW_SINGLE_QUOTES),
ALLOW_UNQUOTED_FIELD_NAMES(false, JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES),
ALLOW_UNESCAPED_CONTROL_CHARS(false, JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS),
ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER(false, JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER),
ALLOW_LEADING_ZEROS_FOR_NUMBERS(false, JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS),
ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS(false, JsonParser.Feature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS),
ALLOW_NON_NUMERIC_NUMBERS(false, JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS),
ALLOW_MISSING_VALUES(false, JsonParser.Feature.ALLOW_MISSING_VALUES),
ALLOW_TRAILING_COMMA(false, JsonParser.Feature.ALLOW_TRAILING_COMMA);
......
}
这里以JsonParser.Feature为例进行举例:
Jackson的objectMapper默认情况下是不能解析以"0"为开头的数字的,但是fastjson是可以的:
/**
Feature that determines whether parser will allow JSON integral numbers to start with additional (ignorable) zeroes (like: 000001). If enabled, no exception is thrown, and extra nulls are silently ignored (and not included in textual representation exposed via getText).
Since JSON specification does not allow leading zeroes, this is a non-standard feature, and as such disabled by default.
**/
ALLOW_NUMERIC_LEADING_ZEROS(false),
Fastjson会把01解析成1:
Jackson在解析01时会抛出异常:
/**
Feature that allows parser to recognize set of "Not-a-Number" (NaN) tokens as legal floating number values (similar to how many other data formats and programming language source code allows it). Specific subset contains values that XML Schema (see section 3.2.4.1, Lexical Representation) allows (tokens are quoted contents, not including quotes):
"INF" (for positive infinity), as well as alias of "Infinity"
"-INF" (for negative infinity), alias "-Infinity"
"NaN" (for other not-a-numbers, like result of division by zero)
Since JSON specification does not allow use of such values, this is a non-standard feature, and as such disabled by default.
**/
ALLOW_NON_NUMERIC_NUMBERS(false)
Fastjson 1.2.70会把NaN解析成0:
Fastjson 1.2.37会抛出异常:
Jackson会抛出异常:
当json字符串里存在注释符时,默认情况下Jackson的ObjectMapper解析器不能解析(Fastjson的AllowComment默认是开启的,所以支持注释符的解析):
/**
* Feature that determines whether parser will allow use
* of Java/C++ style comments (both '/'+'*' and
* '//' varieties) within parsed content or not.
*<p>
* Since JSON specification does not mention comments as legal
* construct,
* this is a non-standard feature; however, in the wild
* this is extensively used. As such, feature is
* <b>disabled by default</b> for parsers and must be
* explicitly enabled.
*/
ALLOW_COMMENTS(false)
Fastjson支持注释符:
Jackson默认情况下会报错:
Fastjson的Feature.AllowSingleQuote 是默认开启的,支持使用单引号包裹字段名,但是jackson受到JsonParser.Feature.ALLOW_SINGLE_QUOTES的影响,默认是不支持的:
/**
Feature that determines whether parser will allow use of single quotes (apostrophe, character '\'') for quoting Strings (names and String values). If so, this is in addition to other acceptable markers. but not by JSON specification).
Since JSON specification requires use of double quotes for field names, this is a non-standard feature, and as such disabled by default.
**/
ALLOW_SINGLE_QUOTES(false)
Fastjson正常解析:
Jackson解析抛出异常:
fastjson的AllowUnQuotedFieldNames默认开启,允许json字段名不被引号包裹,但是jackson的ALLOW_UNQUOTED_FIELD_NAMES默认不开启,无法解析:
/**
* Feature that determines whether parser will allow use
* of unquoted field names (which is allowed by Javascript,
* but not by JSON specification).
*<p>
* Since JSON specification requires use of double quotes for
* field names,
* this is a non-standard feature, and as such disabled by default.
*/
ALLOW_UNQUOTED_FIELD_NAMES(false)
如果数组中两个逗号之间缺失了值,形如这样[value1, , value3]
。对于fastjson来说可以解析,jackson受到ALLOW_MISSING_VALUES
的影响会抛出异常:
/**
Feature allows the support for "missing" values in a JSON array: missing value meaning sequence of two commas, without value in-between but only optional white space. Enabling this feature will expose "missing" values as JsonToken.VALUE_NULL tokens, which typically become Java nulls in arrays and java.util.Collection in data-binding.
For example, enabling this feature will represent a JSON array ["value1",,"value3",] as ["value1", null, "value3", null]
Since the JSON specification does not allow missing values this is a non-compliant JSON feature and is disabled by default.
**/
ALLOW_MISSING_VALUES(false)
Fastjson正常解析,会把缺失的值忽略掉:
Jackson会抛出异常:
假设Bean的结构如下:
public class User {
private int id;
private String userName;
private String sex;
private String[] nickNames;
//对应的getter和setter方法
}
在代码里里属性id是小写的,在fastjson和jackson解析时会有区别。
FastJson在反序列化的时候,是对大小写不敏感的:
在Jackson中,MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES
默认设置为FALSE,在反序列化时是大小写敏感的,可以看到下面的例子中Id因为大小写敏感的问题并未赋值:
Fastjson中Feature.AllowArbitraryCommas是默认开启的,允许在json字符串中写入多个连续的逗号。
Fastjson正常解析:
Jackson会抛出异常,类似的的Feature是ALLOW_TRAILING_COMMA(是否允许json尾部有逗号,默认是False):
除了通过默认Feature的差异以外,FastJSON存在智能匹配的特性,即使JavaBean中的字段和JSON中的key并不完全匹配,在一定程度上还是可以正常解析的。通过这些特性也可以简单的进行区分。
主要是在JavaBeanDeserializer.smartMatch方法进行实现。通过这一特点可以在一定程度上做区分。
在1.2.36版本及后续版本,部分具体代码如下,具体处理方法在TypeUtils.fnv1a_64_lower:
public FieldDeserializer smartMatch(String key, int[] setFlags) {
if (key == null) {
return null;
}
FieldDeserializer fieldDeserializer = getFieldDeserializer(key, setFlags);
if (fieldDeserializer == null)
{
long smartKeyHash = TypeUtils.fnv1a_64_lower(key);
if (this.smartMatchHashArray == null)
{
long[] hashArray = new long[this.sortedFieldDeserializers.length];
for (int i = 0; i < this.sortedFieldDeserializers.length; i++) {
hashArray[i] = TypeUtils.fnv1a_64_lower(this.sortedFieldDeserializers[i].fieldInfo.name);
}
Arrays.sort(hashArray);
this.smartMatchHashArray = hashArray;
}
查看TypeUtils.fnv1a_64_lower的具体实现,这里忽略字母大小写和-和_:
public static long fnv1a_64_lower(String key) {
long hashCode = -3750763034362895579L;
for (int i = 0; i < key.length(); i++)
{
char ch = key.charAt(i);
if ((ch != '_') && (ch != '-'))
{
if ((ch >= 'A') && (ch <= 'Z')) {
ch = (char)(ch + ' ');
}
hashCode ^= ch;
hashCode *= 1099511628211L;
}
}
return hashCode;
}
也就是说fastjson1.2.36版本及后续版本支持同时使用_和-对字段名进行处理:
但是jackson默认是没有这一特性的,例如下面的例子,并没有识别到经过-
和_
处理后的userName:
Fastjson在做智能匹配时,如果key以is开头,则忽略is开头,相关代码如下:
int pos = Arrays.binarySearch(this.smartMatchHashArray, smartKeyHash);
if ((pos < 0) && (key.startsWith("is")))
{
smartKeyHash = TypeUtils.fnv1a_64_lower(key.substring(2));
pos = Arrays.binarySearch(this.smartMatchHashArray, smartKeyHash);
}
同样的Jackson是不具备该特点的。
根据上面的思路可以发掘出很多别的思路,但是实际在环境测试时却与之前的想法有差异,这里对遇到的其中一个点进行分析。
很容易发现Jackson反序列化多余的属性会抛出异常,其实是受到DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
的影响(默认设置为true):
/**
Feature that determines whether encountering of unknown properties (ones that do not map to a property, and there is no "any setter" or handler that can handle it) should result in a failure (by throwing a JsonMappingException) or not. This setting only takes effect after all other handling methods for unknown properties have been tried, and property remains unhandled.
Feature is enabled by default (meaning that a JsonMappingException will be thrown if an unknown property is encountered).
**/
FAIL_ON_UNKNOWN_PROPERTIES(true)
所以相比fastjson,jackson会比较严格,因为强制key与javabean属性对齐,只能少不能多key,所以在解析时会报错。服务器的响应包中多少会异常回显(或者是通用报错页面)。看一个具体的例子:
例如JavaBean中有如下属性:
public class User {
private int id;
private String userName;
private String sex;
//对应属性的getter和setter方法
}
使用ObjectMapper对对应的Json字符串进行解析,因为没有passwd属性,在解析时会抛出异常:
public static void main(String[] args) throws IOException {
String jsonStr="{\"id\":1,\"sex\":\"male\",\"userName\":\"admin\",\"passwd\":\"123456\"}";
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(jsonStr,User.class);
System.out.println(user);
}
根据上面的猜想,理论上应该是可以通过这个属性对齐特性来简单区分使用的是Jackson还是fastjson解析器的。
进一步在springboot环境下进行测试(Springboot默认使用的是Jackson):
同样是刚刚的JavaBean,可以看到增加了新的无关属性passwd后,并未抛出异常:
这是为什么呢?
其实在Spring/Spring Boot环境下,DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
默认是关闭的。这里简单说下原因:
以springboot为例,如果在编码时没提供自定义的配置,会遵循springboot的默认配置,主要是在org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
类进行配置,这里通过Jackson2ObjectMapperBuilder
来创建ObjectMapper:
如果没有额外的配置的话,会使用默认的Jackson2ObjectMapperBuilder,查看具体build()的实现:
在configure方法里进行了相关的配置,这里通过调用customizeDefaultFeatures()配置了一些feature:
继续查看customizeDefaultFeatures方法的具体实现,可以看到这里将DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
设置成了false,Jackson的属性对齐特性不生效了,也就应证了前面增加了新的无关属性后,依旧正常解析的现象了:
所以想要在Spring/Sping Boot环境下区分使用的是哪个解析器,需要另辟蹊径。
除此以外,虽然说大多数都是使用的Jackson/Fastjson,但是不排除还有使用gson等其他解析库的。一些Feature同样的会有影响,例如标准JSON里面是不能包含换行符的(必须以\n表示),但是Fastjson和gson都是支持的,这里也会引入一些干扰项。总的来说,通过上述的一些技巧在一定程度上还是能进行区分的。