国内经典的java反序列化漏洞利用大杀器。使用量大,漏洞频出。最后开发者甚至禁用fastjson组件。
- jdk 1.8
- fastjson 1.2.24
Fastjson是java的一个库,可以将Java对象转化为json格式的字符串,也可以将json格式的字符串转化为Java对象。
Fastjson反序列化函数:
com.alibaba.fastjson.JSON.parseObject
返回JSONObject类型com.alibaba.fastjson.JSON.parse
返回实际类型对象
反序列化操作触发函数(类似PHP魔法函数):构造方法、getter和setter方法都被调用了,所以构造恶意类的时候,只要使用fastjson反序列化的话就可以调用getter或者setter中的恶意方法了。
执行反序列化的根源定位在 @type
fastjson通过指定@type的值来实现定位某类,而这种方法进行反序列化,会执行类的构造方法和属性相关的get,set方法,也造成了这个漏洞的产生。
poc,@type特性调用自定义的恶意类ExecPoc的setCmdxxx方法,参数是calc.exe:
String poc = "{\"@type\":\"ExecPoC\",\"cmdxxx\":\"calc.exe\"}";
JSONObject b = JSON.parseObject(poc);
动态调试,寻找原因。
在com.alibaba.fastjson.JSON
类中,新建DefaultJSONParser
对象会对json数据进行初步解析构造。
public static Object parse(String text, int features) {
if (text == null) {
return null;
} else {
DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
Object value = parser.parse();
parser.handleResovleTask(value);
parser.close();
return value;
}
}
com.alibaba.fastjson.parser.DefaultJSONParser
构造函数中,进行json字符串的解析,{
token为12,[
的token为14,以此区分json数据对象是不是数组。
public DefaultJSONParser(Object input, JSONLexer lexer, ParserConfig config) {
...
if (ch == '{') {
lexer.next();
((JSONLexerBase)lexer).token = 12;
} else if (ch == '[') {
lexer.next();
((JSONLexerBase)lexer).token = 14;
} else {
lexer.nextToken();
}
}
DefaultJSONParser
类的parse()方法,根据不同token也就是{
和[
进行处理。
case 12:
JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
return this.parseObject((Map)object, fieldName);
case 14:
JSONArray array = new JSONArray();
this.parseArray((Collection)array, (Object)fieldName);
if (lexer.isEnabled(Feature.UseObjectArray)) {
return array.toArray();
}
return array;
这开始关键了,
DefaultJSONParser
类的parseObject()方法
获取json字符串第一个键名为@type
。后面有一个对key的判断,进入到这个if分支里。
if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
ref = lexer.scanSymbol(this.symbolTable, '"');
Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader());
if (clazz != null) {
lexer.nextToken(16);
if (lexer.token() == 13) {...}
this.setResolveStatus(2);
if (this.context != null && !(fieldName instanceof Integer)) {
this.popContext();
}
if (object.size() > 0) {
instance = TypeUtils.cast(object, clazz, this.config);
this.parseObject(instance);
thisObj = instance;
return thisObj;
}
ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
//反序列化点
thisObj = deserializer.deserialze(this, clazz, fieldName);
return thisObj;
com.alibaba.fastjson.parser.ParserConfig
中会通过createJavaBeanDeserializer
方法创建一个bean反序列化对象。
if (derializer != null) {
return (ObjectDeserializer)derializer;
} else {
if (clazz.isEnum()) {
derializer = new EnumDeserializer(clazz);
} else if (clazz.isArray()) {
derializer = ObjectArrayCodec.instance;
} else if (clazz != Set.class && clazz != HashSet.class && clazz != Collection.class && clazz != List.class && clazz != ArrayList.class) {
if (Collection.class.isAssignableFrom(clazz)) {
derializer = CollectionCodec.instance;
} else if (Map.class.isAssignableFrom(clazz)) {
derializer = MapDeserializer.instance;
} else if (Throwable.class.isAssignableFrom(clazz)) {
derializer = new ThrowableDeserializer(this, clazz);
} else {
derializer = this.createJavaBeanDeserializer(clazz, (Type)type);
}
继续跟进到com.alibaba.fastjson.util.JavaBeanInfo
的build,会检测@type字段指定的类的成员方法,将符合要求的setter和getter加入fieldList队列里。
Method[] var30 = methods;
int var29 = methods.length;
Method method;
for(i = 0; i < var29; ++i) {
method = var30[i];
ordinal = 0;
int serialzeFeatures = 0;
parserFeatures = 0;
String methodName = method.getName();
//符合要求的setter
if (methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && (method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) {
Class<?>[] types = method.getParameterTypes();
...
}
//符合要求的getter
if (methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3)) && method.getParameterTypes().length == 0 && (Collection.class.isAssignableFrom(method.getReturnType()) || Map.class.isAssignableFrom(method.getReturnType()) || AtomicBoolean.class == method.getReturnType() || AtomicInteger.class == method.getReturnType() || AtomicLong.class == method.getReturnType())) {
...
}
...
//符合setter和getter添加到filedList(com.alibaba.fastjson.util.FieldInfo)
add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures, annotation, fieldAnnotation, (String)null));
setter要求:
- 方法名长度不小于4(满足javabean规范的方法)
- 不能是静态方法
- 返回值是void
- 传入的参数个数为1
- 方法名为set打头
getter要求:
- 方法名长度不小于4
- 不能是静态方法
- 方法名要 get 开头同时第四个字符串要大写
- 方法返回的类型必须继承自 Collection Map AtomicBoolean AtomicInteger AtomicLong
- 传入的参数个数需要为 0
最后在com.alibaba.fastjson.parser.deserializer.FieldDeserializer
的setValue调用method.invoke(object, value);
触发反射调用类setter设置参数并执行。
调用链:
setValue:96, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:593, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:128, JSON (com.alibaba.fastjson)
parseObject:201, JSON (com.alibaba.fastjson)
main:9, PoC
getter是在setter之后执行。