前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >用一个 case 去理解 jdk8u20 原生反序列化漏洞

用一个 case 去理解 jdk8u20 原生反序列化漏洞

作者头像
p4nda
发布于 2023-01-03 06:32:40
发布于 2023-01-03 06:32:40
1.2K00
代码可运行
举报
文章被收录于专栏:技术猫屋技术猫屋
运行总次数:0
代码可运行

0x01 写在前面

jdk8u20原生反序列化漏洞是一个非常经典的漏洞,也是我分析过最复杂的漏洞之一。

在这个漏洞里利用了大量的底层的基础知识,同时也要求读者对反序列化的流程、序列化的数据结构有一定的了解

本文结合笔者自身对该漏洞的了解,写下此文,如有描述不当或者错误之处,还望各位师傅指出

0x02 jdk8u20 漏洞原理

jdk8u20其实是对jdk7u21漏洞的绕过,在《JDK7u21反序列化漏洞分析笔记》 一文的最后我提到了jdk7u21的修复方式:

首先来看存在漏洞的最后一个版本(611bcd930ed1):http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/611bcd930ed1/src/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java 查看其 children 版本(0ca6cbe3f350):http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/0ca6cbe3f350/src/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java compare一下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 改之前
        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; all bets are off
           return;
        }

// 改之后
        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }

可以发现,在第一次的修复中,官方采用的方法是网上的第二种讨论,即将以前的 return 改成了抛出异常。

我们来看第一次修复后的AnnotationInvocationHandler.readObejct()方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        // Check to make sure that types have not evolved incompatibly
        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }
        Map<String, Class<?>> memberTypes = annotationType.memberTypes();
        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }

AnnotationInvocationHandler类中,其重写了readObejct方法,那么根据 oracle 官方定义的 Java 中可序列化对象流的原则——如果一个类中定义了readObject方法,那么这个方法将会取代默认序列化机制中的方法读取对象的状态,可选的信息可依靠这些方法读取,而必选数据部分要依赖defaultReadObject方法读取;

可以看到在该类内部的readObject方法第一行就调用了defaultReadObject()方法,该方法主要用来从字节流中读取对象的字段值,它可以从字节流中按照定义对象的类描述符以及定义的顺序读取字段的名称和类型信息。这些值会通过匹配当前类的字段名称来赋予,如果当前这个对象中的某个字段并没有在字节流中出现,则这些字段会使用类中定义的默认值,如果这个值出现在字节流中,但是并不属于对象,则抛弃该值

在利用defaultReadObject()还原了一部分对象的值后,最近进行AnnotationType.getInstance(type)判断,如果传入的 type 不是AnnotationType类型,那么抛出异常。

也就是说,实际上在jdk7u21漏洞中,我们传入的AnnotationInvocationHandler对象在异常被抛出前,已经从序列化数据中被还原出来。换句话说就是我们把恶意的种子种到了运行对象中,但是因为出现异常导致该种子没法生长,只要我们解决了这个异常,那么就可以重新达到我们的目的。

这也就是jdk8u20漏洞的原理——逃过异常抛出。

那么具体该如何逃过呢?jdk8u20的作者用了一种非常牛逼的方式。

再具体介绍这种方式之前,先简单介绍一些与本漏洞相关的基础知识,以便读者更明白本文的分析流程和细节。

0x03 基础知识

1、Try/catch块的作用

写程序不可避免的出现一些错误或者未注意到的异常信息,为了能够处理这些异常信息或错误,并且让程序继续执行下去,开发者通常使用try ... catch语法。把可能发生异常的语句放在try { ... }中,然后使用catch捕获对应的Exception及其子类,这样一来,在 JVM 捕获到异常后,会从上到下匹配catch语句,匹配到某个catch后,执行catch代码块,从而达到继续执行代码的效果。

如jdk7u21中利用的正是这个:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

当检测的结果不是AbbitatuibType时,匹配到了IllegalArgumentException异常,然后执行了catch中的代码块。

但如果try ... catch嵌套,又该如何判定呢?

可以看个例子

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.panda.sec;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

public class test {
    static double TEST_NUMBER = 0;
    public static void math(int a, int b){
        double c;
        if (a != b) {
            try {
                TEST_NUMBER = a*(a+b);
                c = a / b;
            } catch (Exception e) {
                System.out.println("内层出错了");
            }
        } else {
            c = a * b;
        }
    }
    public static void urlRequest(int a, int b, String url) throws IOException {
        
            try {
                math(a, b);
                URL realUrl = new URL(url);
                HttpURLConnection connection = (HttpURLConnection)realUrl.openConnection();
                connection.setRequestProperty("accept", "*/*");
                connection.connect();
                System.out.println("状态码:" + connection.getResponseCode());
            } catch (Exception e) {
                System.out.println("外层出错了");
                throw e;
            }
        
        System.out.println(TEST_NUMBER);
    }

    public static void main(String[] args) throws IOException {
        urlRequest(1,0,"https://www.cnpanda.net");
        System.out.println("all end");
    }

}

先来看看代码逻辑,首先定义了全局变量TEST_NUMBER=0,然后定义了mathurlRequest两个方法,并且在urlRequest方法里,调用了math方法,最后在main函数中执行urlRequest方法。

请读者不看下文的分析,先思考当变量值为以下情况时,这段代码会输出什么?

  • a=1,b=0,url地址是https://www.cnpanda.net
  • a=1,b=0,url地址是https://test.cnpanda.net
  • a=1,b=2,url地址是https://www.cnpanda.net
  • a=1,b=2,url地址是https://test.cnpanda.net

来看具体运行结果:

当**a=1,b=0**,url地址是**https://www.cnpanda.net**时:

这种情况下,b=0使得a/b中的分母为0,导致内层出错,因此会进入catch块并打印出内层出错了字符串,但是由于内层的catch块并没有把错误抛出,因此继续执行剩余代码逻辑,向https://www.cnpanda.net地址发起http请求,打印状态码为200,由于在math方法中 TEST_NUMBER = a*(a+b)=1*(1+0)=1,因此打印出TEST_NUMBER1.0,最后打印all end结束代码逻辑。

当**a=1,b=0**,url地址是**https://test.cnpanda.net**时:

这种情况下,b=0使得a/b中的分母为0,导致内层出错,因此会进入catch块并打印出内层出错了字符串,但是由于内层的catch块并没有把错误抛出,因此继续执行剩余代码逻辑,向https://test.cnpanda.net地址发起http请求,但是由于无法解析导致出错,进入catch块,在catch块中打印外层出错了字符串,然后抛出错误,结束代码逻辑。

当**a=1,b=2**,url地址是**https://www.cnpanda.net**时:

这种情况下,b!=0,因此a/b会正常运算,不会进入catch块,继续执行剩余代码逻辑,向https://www.cnpanda.net地址发起http请求,打印状态码为200,由于在math方法中 TEST_NUMBER = a*(a+b)=1*(1+2)=3,因此打印出TEST_NUMBER为3,最后打印all end结束代码逻辑。

当**a=1,b=2**,url地址是**https://test.cnpanda.net**时:

这种情况下,b!=0,因此a/b会正常运算,不会进入catch块,继续执行剩余代码逻辑,向https://test.cnpanda.net地址发起http请求,但是由于无法解析导致出错,进入catch块,在catch块中打印外层出错了字符串,然后抛出错误,结束代码逻辑。

从上面的示例可以得出一个结论,在一个存在**try ... catch**块的方法(有异常抛出)中去调用另一个存在**try ... catch**块的方法(无异常抛出),如果被**调用的方法**(无异常抛出)出错,那么会继续执行完**调用方法**的代码逻辑,但是若**调用方法**也出错,那么****终止代码运行的进程

这是有异常抛出调用无异常抛出,那么如果是**无异常抛出**调用**有异常抛出**呢?

如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.panda.sec;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

public class test {
    static double TEST_NUMBER = 0;
    public static void math(int a, int b,String url) throws IOException {
        double c;
        try {
            urlRequest(url);
            if (a != b) {
                    TEST_NUMBER = a*(a+b);
                    c = a / b;
            } else {
                c = a * b;
            }
        } catch (Exception e) {
            System.out.println("外层出错了");
        }
    }
    public static void urlRequest(String url) throws IOException {
        try {
             URL realUrl = new URL(url);
             HttpURLConnection connection = (HttpURLConnection)realUrl.openConnection();
             connection.setRequestProperty("accept", "*/*");
             connection.connect();
             System.out.println("状态码:" + connection.getResponseCode());
        } catch (Exception e) {
                System.out.println("内层出错了");
                throw e;
            }
        System.out.println(TEST_NUMBER);
    }
    public static void main(String[] args) throws IOException {
        math(1,0,"https://test.cnpanda.net");
         System.out.println("all end");
     }
}

同上面示例一样的代码逻辑(为了方便,做了略微调整,有些代码无意义也没有删除),只是,不同的是,这里在math方法中调用了urlRequest方法。

那么如下情况又会输出什么呢?

同样的,请读者不看下文的分析,先思考当变量值为以下情况时,这段代码会输出什么?

  • a=1,b=0,url地址是https://www.cnpanda.net
  • a=1,b=0,url地址是https://test.cnpanda.net
  • a=1,b=2,url地址是https://www.cnpanda.net
  • a=1,b=2,url地址是https://test.cnpanda.net

当**a=1,b=0**,url地址是**https://www.cnpanda.net**时

这种情况下,urlhttps://www.cnpanda.net,因此会在内层向该地址发起http请求,并且打印状态码为200,内层执行完毕后,继续执行外层剩余代码逻辑,b=0使得a/b中的分母为0,导致外层出错,因此会进入catch块并打印出外层层出错了字符串,最后打印all end结束代码逻辑。

当**a=1,b=0**,url地址是**https://test.cnpanda.net**时

这种情况下,urlhttps://test.cnpanda.net,因此会在内层向该地址发起http请求,但是由于无法解析导致出错,进入catch块,在catch块中打印内层出错了字符串,由于内层出错,导致外层也出错,直接进入外层的catch块并打印出外层层出错了字符串,最后打印all end结束代码逻辑。

当**a=1,b=2**,url地址是**https://www.cnpanda.net**时

这种情况下,urlhttps://www.cnpanda.net,因此会在内层向该地址发起http请求,并且打印状态码为200,内层执行完毕后,继续执行外层剩余代码逻辑,b!=0使得a/b中的分母不为0,外层不会出错,因此执行完外层的逻辑,最后打印all end结束整个代码逻辑。

当**a=1,b=2**,url地址是**https://test.cnpanda.net**时

这种情况下,urlhttps://test.cnpanda.net,因此会在内层向该地址发起http请求,因此会在内层向该地址发起http请求,但是由于无法解析导致出错,进入catch块,在catch块中打印内层出错了字符串,由于内层出错,导致外层也出错,直接进入外层的catch块并打印出外层层出错了字符串,最后打印all end结束代码逻辑。

从上面的示例可以得出一个结论,在一个存在**try ... catch**块的方法(无异常抛出)中去调用另一个存在**try ... catch**块的方法(有异常抛出),如果被调用的方法(有异常抛出)出错,那么会导致**调用方法**出错且不会继续执行完**调用方法**的代码逻辑,但是**不会**终止代码运行的进程

2、序列化数据的结构

序列化数据的结构可以参考:

《Object Serialization Stream Protocol/对象序列化流协议》总结 https://cloud.tencent.com/developer/article/2204416

或者直接阅读官方文档:https://docs.oracle.com/javase/8/docs/platform/serialization/spec/protocol.html

使用SerializationDumper工具可以查看一段序列化数据的结构,如下图所示:

可以看到,序列化结构的骨架是由TC_*和各种字段描述符构成,各个TC_*及描述符的意思已经在《Object Serialization Stream Protocol/对象序列化流协议》一文中介绍了,想深入阅读的读者可以去看看。

3、序列化中的两个机制

引用机制

在序列化流程中,对象所属类、对象成员属性等数据都会被使用固定的语法写入到序列化数据,并且会被特定的方法读取;在序列化数据中,存在的对象有null、new objects、classes、arrays、strings、back references等,这些对象在序列化结构中都有对应的描述信息,并且每一个写入字节流的对象都会被赋予引用Handle,并且这个引用Handle可以反向引用该对象(使用TC_REFERENCE结构,引用前面handle的值),引用Handle会从0x7E0000开始进行顺序赋值并且自动自增,一旦字节流发生了重置则该引用Handle会重新从0x7E0000开始。

成员抛弃

在反序列化中,如果当前这个对象中的某个字段并没有在字节流中出现,则这些字段会使用类中定义的默认值,如果这个值出现在字节流中,但是并不属于对象,则抛弃该值,但是如果这个值是一个对象的话,那么会为这个值分配一个 Handle。

4、了解jdk7u21漏洞

这个是毋庸置疑要理解的,因为jdk8u20是对jdk7u21漏洞修复的绕过。

可以参考我之前写的文章:JDK7u21反序列化漏洞分析笔记:https://xz.aliyun.com/t/9704

0x04 从一个case说起

由于jdk8u20真的比较复杂,因此为了方便理解,我写了一个简单的case,用于帮助读者理解下文。

假设存在两个类AnnotationInvocationHandlerBeanContextSupport,具体内容如下:

AnnotationInvocationHandler.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.panda.sec;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class AnnotationInvocationHandler implements Serializable {
    private static final long serialVersionUID = 10L;
    private int zero;
    public AnnotationInvocationHandler(int zero) {
        this.zero = zero;
    }
    public void exec(String cmd) throws IOException {
        Process shell = Runtime.getRuntime().exec(cmd);
    }
    private void readObject(ObjectInputStream input) throws Exception {
        input.defaultReadObject();
        if(this.zero==0){
            try{
                double result = 1/this.zero;
            }catch (Exception e) {
                throw new Exception("Hack !!!");
            }
        }else{
            throw new Exception("your number is error!!!");
        }
    }
}

BeanContextSupport.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.panda.sec;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class BeanContextSupport implements Serializable {
    private static final long serialVersionUID = 20L;
    private void readObject(ObjectInputStream input) throws Exception {
        input.defaultReadObject();
        try {
            input.readObject();
        } catch (Exception e) {
            return;
        }
    }
}

Question:当传入**AnnotationInvocationHandler**方法中的**zero**等于**0**的时候,如何能在序列化结束时调用**AnnotationInvocationHandler.exec()**方法达到**RCE**?

我们首先令zero等于0,然后尝试调用AnnotationInvocationHandler.exec()方法看看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.io.*;
public class Main {
    public static void payload() throws IOException, ClassNotFoundException {
        AnnotationInvocationHandler annotationInvocationHandler = new AnnotationInvocationHandler(0);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("payload1"));
        out.writeObject(annotationInvocationHandler);
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("payload1"));
        AnnotationInvocationHandler str = (AnnotationInvocationHandler)in.readObject();
        str.exec("open /System/Applications/Calculator.app");
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        payload();
    }
}

不出意外,由于zero的值为0,所以使得result的分母为0,导致出现异常,抛出 Exception("Hack !!!")错误。

由于在代码中我们生成了序列化文件payload1,所以现在可以利用SerializationDumper工具来看看其数据结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
  TC_OBJECT - 0x73
    TC_CLASSDESC - 0x72
      className
        Length - 41 - 0x00 29
        Value - com.panda.sec.AnnotationInvocationHandler - 0x636f6d2e70616e64612e7365632e416e6e6f746174696f6e496e766f636174696f6e48616e646c6572
      serialVersionUID - 0x00 00 00 00 00 00 00 0a
      newHandle 0x00 7e 00 00
      classDescFlags - 0x02 - SC_SERIALIZABLE
      fieldCount - 1 - 0x00 01
      Fields
        0:
          Int - I - 0x49
          fieldName
            Length - 4 - 0x00 04
            Value - zero - 0x7a65726f
      classAnnotations
        TC_ENDBLOCKDATA - 0x78
      superClassDesc
        TC_NULL - 0x70
    newHandle 0x00 7e 00 01
    classdata
      com.panda.sec.AnnotationInvocationHandler
        values
          zero
            (int)0 - 0x00 00 00 00

由于该数据结构比较短,可以来具体介绍一下。

STREAM_MAGIC - 0xac ed是魔数,代表了序列化的格式;

STREAM_VERSION - 0x00 05表示序列化的版本;

Contents表示最终生成的序列的内容;

TC_OBJECT - 0x73表示序列化一个新对象的开始标记;

TC_CLASSDESC - 0x72表示一个新类的描述信息开始标记;

className表示当前对象的类全名信息,下面紧跟着的内容也是className的描述信息;

Length - 41 - 0x00 29表示当前对象的类的长度为41

Value - com.panda.sec.AnnotationInvocationHandler - 0x636f6d2e70616e64612e7365632e416e6e6f746174696f6e496e766f636174696f6e48616e646c6572表示当前对象的类的名称为com.panda.sec.AnnotationInvocationHandler,后面的字符串是其十六进制表示;

serialVersionUID - 0x00 00 00 00 00 00 00 0a定义了serialVersionUID的值为20

newHandle 0x00 7e 00 00 表示为对象分配一个值为007e0000handle(因为引用Handle会从0x7E0000开始进行顺序赋值并且自动自增),值得注意的是这里的handle实际上没有被真正的写入文件,如果我们把这里的007e0000加入到序列化数据中,会发生异常,从而终止反序列化进程,之所以会在这里显示出来,是因为serializationDumper的作者为了方便使用者分析序列化数据的结构;

classDescFlags - 0x02 - SC_SERIALIZABLE表示类描述信息标记为SC_SERIALIZABLE,代表在序列化的时候使用的是java.io.Serializable(如果使用的是java.io.Externalizable,这里的标记就会变成classDescFlags - 0x04 - SC_EXTERNALIZABLE);

fieldCount - 1 - 0x00 01表示成员属性的数量为1,值得注意的是这里的fieldCount同样是serializationDumper的作者为了方便使用者分析序列化数据的结构而新设置的描述符,在官方序列化规范中是没有fieldCount的;

Fields表示接下来的内容是类中所有字段的描述信息,Fields成员属性保存了当前分析的类对应的所有成员属性的元数据信息,它是一个数组结构,每一个元素都对应了成员属性的元数据描述信息,且不会重复;

0表示接下来的内容是第一个字段的描述信息;

Int - I - 0x49表示该字段的类型是int型;

fieldName表示当前字段的字段名信息,下面紧跟着的内容也是 fieldName的描述信息;

Length - 4 - 0x00 04表示当前字段名的长度为4

Value - zero - 0x7a65726f表示当前字段名为zero

classAnnotations表示和类相关的Annotation的描述信息,这里的数据值一般是由ObjectOutputStreamannotateClass()方法写入的,但由于annotateClass()方法默认为空,所以classAnnotations后一般会设置TC_ENDBLOCKDATA标识;(关于annotateClass具体可以看我写的序列化流程分析总结一文)

TC_ENDBLOCKDATA - 0x78数据块的结束标记,表示这个对象类型的描述符已经结束了;

superClassDesc表示父类的描述符信息,这里为空;

TC_NULL - 0x70表示当前对象是一个空引用;

newHandle 0x00 7e 00 01表示为对象分配一个值为007e0001handle,同上面的newHandle一样,这里的handle实际上没有被真正的写入文件;

classdata表示下面紧跟着的是类数据中的所有内容;

` com.panda.sec.AnnotationInvocationHandler

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    values
      zero
        (int)0 - 0x00 00 00 00`表示类数据中的所有内容

以上就是所有的序列化数据的结构,当进行反序列化的时候,会依次从上到下读取序列化内容进行还原数据。

现在思考一个问题:如果在上面的序列化数据中插入一部分源代码中没有的数据,那么在反序列化的时候会发生什么?

在解决这个问题前,首先再来深入理解一下我们之前提到的引用机制,举个例子

比如以下代码进行一次序列化的序列化数据结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.panda.sec;
import java.io.*;
public class test implements Serializable {
    private static final long serialVersionUID = 100L;
    public static int num = 0;
    private void readObject(ObjectInputStream input) throws Exception {
        input.defaultReadObject();
        System.out.println("hello!");
    }
    public static void main(String[] args) throws IOException {
        test t = new test();
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("testcase"));
        out.writeObject(t);
        out.close();
    }
}

如果上述代码进行两次序列化,那么这个数据结构会变成什么?

可以来看看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.panda.sec;
import java.io.*;
public class test implements Serializable {
    private static final long serialVersionUID = 100L;
    public static int num = 0;
    private void readObject(ObjectInputStream input) throws Exception {
        input.defaultReadObject();
        System.out.println("hello!");
    }
    public static void main(String[] args) throws IOException {
        test t = new test();
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("testcase"));
        out.writeObject(t);
        out.writeObject(t); //二次序列化
        out.close();
    }
}

对比一下可以发现,在该序列化数据的结构中最后多了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
TC_REFERENCE - 0x71
    Handle - 8257537 - 0x00 7e 00 01

这里对应的就是前文基础知识里“序列化中的两个机制”中引用机制里的一段话

每一个写入字节流的对象都会被赋予引用Handle,并且这个引用Handle可以反向引用该对象(使用TC_REFERENCE结构,引用前面handle的值),引用Handle会从0x7E0000开始进行顺序赋值并且自动自增,一旦字节流发生了重置则该引用Handle会重新从0x7E0000开始。

那么反序列化是如何处理TC_REFERENCE块的呢?

我在反序列化流程分析总结 一文中写到这样一个流程:

readObject0方法里有这样的一个判断:

是的,在反序列化的流程中,进入了readObject0方法后,会判断读取的字节流中是否有TC_REFERENCE标识,如果有,那么会调用readHandle函数,但是我没有在文中具体说明readHandle函数,可以一起来看看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    private Object readHandle(boolean unshared) throws IOException {
        if (bin.readByte() != TC_REFERENCE) {
            throw new InternalError();
        }
        passHandle = bin.readInt() - baseWireHandle;
        if (passHandle < 0 || passHandle >= handles.size()) {
            throw new StreamCorruptedException(
                String.format("invalid handle value: %08X", passHandle +
                baseWireHandle));
        }
        if (unshared) {
            // REMIND: what type of exception to throw here?
            throw new InvalidObjectException(
                "cannot read back reference as unshared");
        }
 
        Object obj = handles.lookupObject(passHandle);
        if (obj == unsharedMarker) {
            // REMIND: what type of exception to throw here?
            throw new InvalidObjectException(
                "cannot read back reference to unshared object");
        }
        return obj;
    }

这个方法会从字节流中读取TC_REFERENCE标记段,它会把读取的引用Handle赋值给passHandle变量,然后传入lookupObject(),在lookupObject()方法中,如果引用的handle不为空、没有关联的ClassNotFoundExceptionstatus[handle] != STATUS_EXCEPTION),那么就返回给定handle的引用对象,最后由readHandle方法返回给对象。

也就是说,反序列化流程还原到TC_REFERENCE的时候,会尝试还原引用的handle对象。

谈完了引用机制现在在来看数据插入的问题,如何能在类**AnnotationInvocationHandler**的序列化数据中插入一部分源代码中没有的数据?

利用objectAnnotation

继续来看个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.panda.sec;
import java.io.*;
public class test implements Serializable {
    private static final long serialVersionUID = 100L;
    public static int num = 0;
    private void readObject(ObjectInputStream input) throws Exception {
        input.defaultReadObject();
        System.out.println("hello!");
    }
    private void writeObject(ObjectOutputStream output) throws IOException {
        output.defaultWriteObject();
        output.writeObject("Panda");
        output.writeUTF("This is a test data!");
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        test t = new test();
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("testcase_new"));
        out.writeObject(t);
        out.writeObject(t);
        out.close();
    }
}

在这个示例中,我们重写了writeObject方法,并且在该方法中利用writeObjectwriteUTF方法写入了Panda对象以及This is a test data!字符串,该段序列化数据内容如下:

为了更直白的看变化,我们可以用compare工具来对比一下:

可以看到原先表示类描述信息标记由0x02 - SC_SERIALIZABLE变成了0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE,并且在原有序列化数据结构的最下方还多了由objectAnnotation标识的内容段,这里的内容段会在反序列化的时候被还原

为什么会有这种变化?

知识点1:如果一个可序列化的类重写了writeObject方法,而且向字节流写入了一些额外的数据,那么会设置SC_WRITE_METHOD标识,这种情况下,一般使用结束符TC_ENDBLOCKDATA来标记这个对象的数据结束;

知识点2:如果一个可序列化的类重写了writeObject方法,在该序列化数据的classdata部分,还会多出个objectAnnotation部分,并且如果重写的writeObject()方法内除了调用defaultWriteObject()方法写对象字段数据,还向字节流中写入了自定义数据,那么在objectAnnotation部分会有写入自定义数据对应的结构和值;

这样一来是不是就有点明了了?

正常情况下,我们没有办法修改可序列化类本身的内容,也就没办法重写这个类中的writeObject方法,也就没法让序列化数据中多出来objectAnnotation内容段

可真的没办法吗?当然不是了!

序列化数据只是一块二进制的数据而已,只要按照序列化预定的规则来修改其hex数据,那么实际上就是相当于在重写的writeObject方法中添加数据

在写入数据前,我们要考虑一件事,谁向谁写入数据?是先序列化AnnotationInvocationHandler类然后向其中插入BeanContextSupport对象,还是先序列化BeanContextSupport类然后向其中插入AnnotationInvocationHandler对象?

先思考jdk7u21被修复的原因是什么?是因为在反序列化的过程中有异常抛出,从而导致反序列化的进程被终止了!

这让我们不得不联想到我们在基础知识的Try/catch块的作用中做的结论:

在一个存在**try ... catch**块的方法(无异常抛出)中去调用另一个存在**try ... catch**块的方法(有异常抛出),如果被调用的方法(有异常抛出)出错,那么会导致**调用方法**出错且不会继续执行完**调用方法**的代码逻辑,但是**不会**终止代码运行的进程

我们要的就是不要终止我们的反序列化进程,这样我们就可以取得反序列化后的类对象。

所以我们需要先序列化BeanContextSupport类(无异常抛出)然后向其中插入AnnotationInvocationHandler对象(有异常抛出)

这里还有一点要注意,因为根据成员抛弃机制我们知道,如果序列化流新增的这个值是一个对象的话,那么会为这个值分配一个 Handle,但由于我们是手动插入Handle,所以需要修改引用Handle的值(就是TC_ENDBLOCKDATA块中handle的引用值)为AnnotationInvocationHandler对象的handle地址

具体过程如下:

第一步,先序列化BeanContextSupport类,然后利用SerializationDumper工具可以得到以下数据结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
  TC_OBJECT - 0x73
    TC_CLASSDESC - 0x72
      className
        Length - 32 - 0x00 20
        Value - com.panda.sec.BeanContextSupport - 0x636f6d2e70616e64612e7365632e4265616e436f6e74657874537570706f7274
      serialVersionUID - 0x00 00 00 00 00 00 00 14
      newHandle 0x00 7e 00 00
      classDescFlags - 0x02 - SC_SERIALIZABLE
      fieldCount - 0 - 0x00 00
      classAnnotations
        TC_ENDBLOCKDATA - 0x78
      superClassDesc
        TC_NULL - 0x70
    newHandle 0x00 7e 00 01
    classdata
      com.panda.sec.BeanContextSupport
        values

第二步,序列化AnnotationInvocationHandler类,然后利用SerializationDumper工具可以得到以下数据结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
  TC_OBJECT - 0x73
    TC_CLASSDESC - 0x72
      className
        Length - 41 - 0x00 29
        Value - com.panda.sec.AnnotationInvocationHandler - 0x636f6d2e70616e64612e7365632e416e6e6f746174696f6e496e766f636174696f6e48616e646c6572
      serialVersionUID - 0x00 00 00 00 00 00 00 0a
      newHandle 0x00 7e 00 00
      classDescFlags - 0x02 - SC_SERIALIZABLE
      fieldCount - 1 - 0x00 01
      Fields
        0:
          Int - I - 0x49
          fieldName
            Length - 4 - 0x00 04
            Value - zero - 0x7a65726f
      classAnnotations
        TC_ENDBLOCKDATA - 0x78
      superClassDesc
        TC_NULL - 0x70
    newHandle 0x00 7e 00 01
    classdata
      com.panda.sec.AnnotationInvocationHandler
        values
          zero
            (int)0 - 0x00 00 00 00

第三步,利用objectAnnotation插入AnnotationInvocationHandler对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
  TC_OBJECT - 0x73
    TC_CLASSDESC - 0x72
      className
        Length - 32 - 0x00 20
        Value - com.panda.sec.BeanContextSupport - 0x636f6d2e70616e64612e7365632e4265616e436f6e74657874537570706f7274
      serialVersionUID - 0x00 00 00 00 00 00 00 14
      newHandle 0x00 7e 00 00
      classDescFlags - 0x02 - SC_SERIALIZABLE
      fieldCount - 0 - 0x00 00
      classAnnotations
        TC_ENDBLOCKDATA - 0x78
      superClassDesc
        TC_NULL - 0x70
    newHandle 0x00 7e 00 01
    classdata
      com.panda.sec.BeanContextSupport
        values
      objectAnnotation       //     从这里开始
            TC_OBJECT - 0x73
            TC_CLASSDESC - 0x72
              className
                Length - 41 - 0x00 29
                Value - com.panda.sec.AnnotationInvocationHandler - 0x636f6d2e70616e64612e7365632e416e6e6f746174696f6e496e766f636174696f6e48616e646c6572
              serialVersionUID - 0x00 00 00 00 00 00 00 0a
              newHandle 0x00 7e 00 00
              classDescFlags - 0x02 - SC_SERIALIZABLE
              fieldCount - 1 - 0x00 01
              Fields
                0:
                  Int - I - 0x49
                  fieldName
                    Length - 4 - 0x00 04
                    Value - zero - 0x7a65726f
              classAnnotations
                TC_ENDBLOCKDATA - 0x78
              superClassDesc
                TC_NULL - 0x70
            newHandle 0x00 7e 00 01
            classdata
              com.panda.sec.AnnotationInvocationHandler
                values
                  zero
                    (int)0 - 0x00 00 00 00
              TC_ENDBLOCKDATA - 0x78
  TC_REFERENCE - 0x71
    Handle - 8257539 - 0x00 7e 00 03

第四步,修改handle值以及对应的classDescFlags值:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
  TC_OBJECT - 0x73
    TC_CLASSDESC - 0x72
      className
        Length - 32 - 0x00 20
        Value - com.panda.sec.BeanContextSupport - 0x636f6d2e70616e64612e7365632e4265616e436f6e74657874537570706f7274
      serialVersionUID - 0x00 00 00 00 00 00 00 14
      newHandle 0x00 7e 00 00
      classDescFlags - 0x03 - SC_WRITE_METHOD | SC_SERIALIZABLE
      fieldCount - 0 - 0x00 00
      classAnnotations
        TC_ENDBLOCKDATA - 0x78
      superClassDesc
        TC_NULL - 0x70
    newHandle 0x00 7e 00 01
    classdata
      com.panda.sec.BeanContextSupport
        values
      objectAnnotation            // 从这里开始
            TC_OBJECT - 0x73
            TC_CLASSDESC - 0x72
              className
                Length - 41 - 0x00 29
                Value - com.panda.sec.AnnotationInvocationHandler - 0x636f6d2e70616e64612e7365632e416e6e6f746174696f6e496e766f636174696f6e48616e646c6572
              serialVersionUID - 0x00 00 00 00 00 00 00 0a
              newHandle 0x00 7e 00 02
              classDescFlags - 0x02 - SC_SERIALIZABLE
              fieldCount - 1 - 0x00 01
              Fields
                0:
                  Int - I - 0x49
                  fieldName
                    Length - 4 - 0x00 04
                    Value - zero - 0x7a65726f
              classAnnotations
                TC_ENDBLOCKDATA - 0x78
              superClassDesc
                TC_NULL - 0x70
            newHandle 0x00 7e 00 03
            classdata
              com.panda.sec.AnnotationInvocationHandler
                values
                  zero
                    (int)0 - 0x00 00 00 00
              TC_ENDBLOCKDATA - 0x78
  TC_REFERENCE - 0x71
    Handle - 8257539 - 0x00 7e 00 03

注:这里最后的Handle - 8257539 - 0x00 7e 00 03中的8257539serializationDumper中生成的数值,具体在序列化或反序列化流程中的体现,没有具体深究,该值不影响我们最终序列化数据的生成,该值的生成算法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public static void num(){
        byte b1 = 0 ;
        byte b2 = 126;
        byte b3 = 0;
        byte b4 = 3;
        int handle = (
                ((b1 << 24) & 0xff000000) +
                        ((b2 << 16) &   0xff0000) +
                        ((b3 <<  8) &     0xff00) +
                        ((b4      ) &       0xff)
        );
        System.out.println("Handle - " + handle + " - 0x" + byteToHex(b1) + " " + byteToHex(b2) + " " + byteToHex(b3) + " " + byteToHex(b4));

    }

其中,b1 b2 b3 b4组合是 00 7e 00 xx,即代表了引用handle的值,然后将这些值经过运算就可以得到最终的8257539值。

然后根据这段数据结构,转换成十六进制数据如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ac ed 00 05 73 72 00 20 636f6d2e70616e64612e7365632e4265616e436f6e74657874537570706f7274
00 00 00 00 00 00 00 14 03 00 00 78 70 73 72 00 29 636f6d2e70616e64612e7365632e416e6e6f746174696f6e496e766f636174696f6e48616e646c6572
00 00 00 00 00 00 00 0a 02 00 01 49 00 04 7a65726f 78 70 00 00 00 00 78 71 00 7e 00 03

这里需要注意的是,我们在前文提到了

newhandle实际上没有被真正的写入文件,如果我们把这里的007e0000加入到序列化数据中,会发生异常,从而终止反序列化进程,之所以会在这里显示出来,是因为serializationDumper的作者为了方便使用者分析序列化数据的结构;

所以我们在构建十六进制数据的过程中要丢弃掉newhandle对应的十六进制数据

最后以4个字符为一组,8个组为一行,整理可得:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
aced 0005 7372 0020 636f 6d2e 7061 6e64
612e 7365 632e 4265 616e 436f 6e74 6578
7453 7570 706f 7274 0000 0000 0000 0014
0300 0078 7073 7200 2963 6f6d 2e70 616e
6461 2e73 6563 2e41 6e6e 6f74 6174 696f
6e49 6e76 6f63 6174 696f 6e48 616e 646c
6572 0000 0000 0000 000a 0200 0149 0004
7a65 726f 7870 0000 0000 7871 007e 0003

将这些内容替换掉在从一个case说起最开始部分生成的payload1里的内容

然后尝试再次运行以下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.panda.sec;
import java.io.*;
public class Main {
    public static void payload() throws IOException, ClassNotFoundException {
//        AnnotationInvocationHandler annotationInvocationHandler = new AnnotationInvocationHandler(0);
//
//        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("payload1"));
//        out.writeObject(annotationInvocationHandler);
//        out.writeObject(annotationInvocationHandler);
//        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("payload1"));
        System.out.println(in.readObject().toString());
        AnnotationInvocationHandler str = (AnnotationInvocationHandler)in.readObject();
        System.out.println(str.toString());
        str.exec("open /System/Applications/Calculator.app");
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        payload();
    }
}

结果如下:

成功实现了RCE,并且可以看到,我们第一次反序列还原的对象是com.panda.sec.BeanContextSupport,第二次反序列化还原的对象是com.panda.sec.AnnotationInvocationHandler,也正对应了我们自己手动插入数据的顺序

0x05 jdk8u20 漏洞分析

我们在jdk8u20漏洞原理部分提到逃过异常抛出是该漏洞的关键所在,又经过上面一个case的分析,现在再来看看如何逃过异常抛出呢?没错,就是在jdk源码中找到一个类似于该case中的BeanContextSupport类,让BeanContextSupport成为外层,去调用jdk源码中的AnnotationInvocationHandler类,这样一来没有异常抛出就能够使反序列化流程不被终止,成功组成新的gadget链,完成一次完美的反序列化漏洞攻击。

那么在jdk源码中到底有没有一个类似于该case中的BeanContextSupport类?答案是显而易见的,其实为了方便读者理解此处的内容,我在case中就把这个类的名称给了出来——是的,就是 java.beans.beancontext.BeanContextSuppor类,我们利用的是该类中的readChildren方法,来具体看看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public final void readChildren(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        int count = serializable;
        while (count-- > 0) {
            Object                      child = null;
            BeanContextSupport.BCSChild bscc  = null;
            try {
                child = ois.readObject();
                bscc  = (BeanContextSupport.BCSChild)ois.readObject();
            } catch (IOException ioe) {
                continue;
            } catch (ClassNotFoundException cnfe) {
                continue;
            }
            synchronized(child) {
                BeanContextChild bcc = null;
                try {
                    bcc = (BeanContextChild)child;
                } catch (ClassCastException cce) {
                    // do nothing;
                }
                if (bcc != null) {
                    try {
                        bcc.setBeanContext(getBeanContextPeer());

                       bcc.addPropertyChangeListener("beanContext", childPCL);
                       bcc.addVetoableChangeListener("beanContext", childVCL);

                    } catch (PropertyVetoException pve) {
                        continue;
                    }
                }
                childDeserializedHook(child, bscc);
            }
        }
    }

可以看到,在该方法的第7行,对传入进来的ObjectInputStream对象调用了readObject方法进行反序列化处理,并且当在反序列化过程中如果出现异常,采用的是continue处理。完美的符合我们的要求。

我们在上文中提到ObjectAnnotation这个概念,并且其实可以发现,如果存在ObjectAnnotation结构,那么一般是由TC_ENDBLOCKDATA - 0x78去标记结尾的,但是这里其实存在一个问题,我们知道在jdk7u21修复中是因为IllegalArgumentException异常被捕获后抛出了java.io.InvalidObjectException,虽然这里我们可以利用BeanContextSupport来强制序列化流程继续下去,但是抛出的异常会导致BeanContextSupportObjectAnnotationTC_ENDBLOCKDATA - 0x78结尾标志无法被正常处理,如果我们不手动删除这个TC_ENDBLOCKDATA - 0x78那么会导致后面的结构归在ObjectAnnotation结构中,从而读取错误,反序列化出来的数据不是我们预期数据。所以我们在生成BeanContextSupportObjectAnnotation中不能按照正规的序列化结构,需要将标记结尾的结构TC_ENDBLOCKDATA - 0x78删除

也正由于我们把TC_ENDBLOCKDATA - 0x78删除了,会导致我们在使用SerializationDumper工具查看jdk8u20的序列化数据结构出错,如下图所示:

这里还有一个tips点,就是我们在插入BeanContextSupport对象的时候并不是像case中那样直接插入,而是借用假属性的概念插入。在成员抛弃中我们提到

在反序列化中,如果当前这个对象中的某个字段并没有在字节流中出现,则这些字段会使用类中定义的默认值,如果这个值出现在字节流中,但是并不属于对象,则抛弃该值,但是如果这个值是一个对象的话,那么会为这个值分配一个 Handle。

所以我们插入一个任意类型为BeanContextSupport的字段就可以在不影响原有的序列化流程的情况下,形成一个gadget链

这里可能有点难以理解,多说一点

我们知道一般gadget链是一链接着一链紧紧相连,通过写各种类之间的调用,就能够满足整个gadget链的要求,实现整个gadget链的相连。但在jdk8u20中,并非如此,因为LinkedHashSet没法在满足绕过异常抛出的条件下直接调用BeanContextSupport方法,但是BeanContextSupport可以调用AnnotationInvocationHandler方法,这也就导致我们的gadget链在LinkedHashSet下一步断了,那怎么办?

只能通过修改序列化数据结构的方式,在LinkedHashSet中强行插入一个BeanContextSupport类型的字段值,由于在java反序列化的流程中,一般都是首先还原对象中字段的值,然后才会还原objectAnnotation结构中的值(即是按照序列化数据结构的顺序),所以它会首先反序列化LinkedHashSet,然后反序列LinkedHashSet字段的值,由于在这个字段值中有一个BeanContextSupport类型的字段,所以反序列化会去还原BeanContextSupport对象,也就是objectAnnotation中的数据

在反序列化BeanContextSupport的过程中,会首先反序列化BeanContextSupport的字段值,其中有个值为 Templates.classAnnotationInvocationHandler 类的对象的字段,然后反序列化会去还原AnnotationInvocationHandler对象,成功的关联了下一个链!

最后就是同 Jdk7u21 一样的流程,利用动态代理触发Proxy.equals(EvilTemplates.class),达到恶意类注入实现RCE的最终目的。

目前jdk8u20反序列化漏洞 payload 的写法有以下几种方式:

纵观以上的方法各有各的优劣,有的容易理解,但是构建起来却很麻烦,有的不容易理解,但是构建起来较为方便,其实还有如果读者全文看下来,还有一种更容易理解的方法,就是先把jdk7u21漏洞利用的payload的序列化数据结构生成出来,然后向该数据结构中用类似case中的方法,去手动插入对象,但是这个工作量比较大,故我也没有手动去实现,有兴趣、有时间的朋友可以去尝试利用该方法生成payload(包你酸爽~但是对你理解jdk8u21来说,这种方式是最直白的方式)

另外,jdk8u20特殊的一点在于,实际上BeanContextSupport不能算是真正意义上的一条链,链还是jdk7u21中的那条链,只不过BeanContextSupport是用来避免抛出异常用到的媒介。

0x06 总结

本文对jdk8u20原生反序列化漏洞进行了分析,但和其他分析文章不同的是,本文没有按照常规的分析方法进行分析,而是重点写了一个case,用一个最简单的case去了解jdk8u20最核心的问题点,然后从整体上阐述了jdk8u20反序列化漏洞是怎么一回事,流程上是什么样的

站在读者的角度上去考虑,让自己如何用更直白的方式让别人理解你发的内容,我觉得这样的方式可以让我更能理解我所分析的漏洞、记忆我所写的内容,毕竟,每一个分析文章其实对于我来说都是一次整体上的总结

0x07 参考

https://github.com/pwntester/JRE8u20_RCE_Gadget

http://wouter.coekaerts.be/2015/annotationinvocationhandler

https://paper.seebug.org/456/

https://github.com/potats0/javaSerializationTools

https://mp.weixin.qq.com/s/SMq6aE5-qV9cINv1-74RgA

https://xz.aliyun.com/t/8277

https://mp.weixin.qq.com/s/3bJ668GVb39nT0NDVD-3IA

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-06-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java 反序列化学习
讲的比较清楚的文章:https://www.cnblogs.com/ityouknow/p/5603287.html
wywwzjj
2023/05/09
1.4K0
Java 反序列化学习
Java安全-反序列化-1-URLDNS
Java在序列化对象时,将会调用这个对象的writeObject方法,参数类型是ObjectOutputStream,开发者可以将任何内容写入这个Stream中;反序列化时,也会调用这个对象的readObject方法,可以读取到前面写入的内容并进行处理
Naraku
2022/04/14
2530
Java安全-反序列化-1-URLDNS
java的反序列化(一)What’s java’s serialize&unserialize
AC ED 00 05之后可能跟上述的数据类型说明符,也可能跟77(TC_BLOCKDATA元素)或7A(TC_BLOCKDATALONG元素)其后跟的是块数据。
h0cksr
2023/05/17
8000
java的反序列化(一)What’s java’s serialize&unserialize
JDK7u21反序列化漏洞分析笔记
JDK7u21原生gadget链的构造十分经典,在对于其构造及思想学习后,写下本文作为笔记。
p4nda
2023/01/03
5670
JDK7u21反序列化漏洞分析笔记
Java反序列化漏洞
Lib之过?Java反序列化漏洞通用利用分析 转自:https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/
一滴水的眼泪
2020/09/21
1.1K0
Java反序列化漏洞
《Object Serialization Stream Protocol/对象序列化流协议》总结
本文主要是《Object Serialization Stream Protocol》一文的翻译,然后对序列化格式进行了一些总结
p4nda
2023/01/03
6800
《Object Serialization Stream Protocol/对象序列化流协议》总结
深入学习Java序列化
对于Java的序列化,一直只知道只需要实现Serializbale这个接口就可以了,具体内部实现一直不是很了解,正好这次在重复造RPC的轮子的时候涉及到序列化问题,就抽时间看了下 Java序列化的底层实现,这篇文章算是这次的学习小结吧。
本人秃顶程序员
2019/04/30
6650
深入学习Java序列化
深入学习 Java 序列化
内容来源:天凉好个秋,链接:beautyboss.farbox.com/post/study/shen-ru-xue-xi-javaxu-lie-hua 前言 对于Java的序列化,一直只知道只需要实现Serializbale这个接口就可以了,具体内部实现一直不是很了解,正好这次在重复造RPC的轮子的时候涉及到序列化问题,就抽时间看了下 Java序列化的底层实现,这篇文章算是这次的学习小结吧。 第一部分:What Java序列化是指把Java对象保存为二进制字节码的过程,Java反序列化是指把二进制码重新转
IT大咖说
2018/06/04
7050
【Java】序列化和反序列化
一个字(word)是两个byte,即 word = 2 byte = 16 bit, 则一个字最大为 FFFF。
人不走空
2024/05/11
2170
【Java】序列化和反序列化
Java反序列化源码深入追踪
实际上,Externalizable接口继承自Serializable接口,但他们的序列化机制是完全不同的:使用Serializable的方式,在反序列化时不会直接调用被序列化对象的构造器,而是先获取被序列化对象对应类的 【自下而上最顶层实现了Serializable的祖先类的超类】【即自上而下连续的最后一个未实现Serizable接口的类】的构造器,然后在此构造器的基础上重新创建一个新的构造器来完成实例化。这句话读起来有些拗口,我们后面分析Serializable反序列化机制时还会详细介绍。而使用Externalizable则是调用一个无参构造方法来实例化,原因如下: Externalizable序列化的过程:使用Externalizable序列化时,在进行反序列化的时候,会重新实例化一个对象,然后再将被反序列化的对象的状态全部复制到这个新的实例化对象当中去,这也就是为什么会调用构造方法啦,也因此必须有一个无参构造方法供其调用,并且权限是public。
saintyyu
2021/11/22
6020
序列化流程分析总结
本文写的比较细,推荐复制demo代码,然后一步一步跟随笔者的分析进行debug调试跟随,这样跟能够帮助读者理解此文。
p4nda
2023/01/03
4090
序列化流程分析总结
java序列化反序列化深入探究
When---什么时候需要序列化和反序列化: 简单的写一个hello world程序,用不到序列化和反序列化。写一个排序算法也用不到序列化和反序列化。但是当你想要将一个对象进行持久化写入文件,或者你想将一个对象从一个网络地址通过网络协议发送到另一个网络地址时,这时候就需要考虑序列化和反序列化了。另外如果你想对一个对象实例进行深度拷贝,也可以通过序列化和反序列化的方式进行。 What---什么是序列化和反序列化: Serialization-序列化:可以看做是将一个对象转化为二进制流的过程 Deseriali
SecondWorld
2018/03/14
7210
深入理解 Java 反序列化漏洞
序列化与反序列化是让Java对象脱离Java运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。主要应用在以下场景:
kirito-moe
2020/02/26
8.6K0
Java对象序列化底层原理源码解析WhatHowWhyOther
What Java序列化是指把Java对象保存为二进制字节码的过程,Java反序列化是指把二进制码重新转换成Java对象的过程。 那么为什么需要序列化呢? 第一种情况是:一般情况下Java对象的声明周期都比Java虚拟机的要短,实际应用中我们希望在JVM停止运行之后能够持久化指定的对象,这时候就需要把对象进行序列化之后保存。 第二种情况是:需要把Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之
JavaEdge
2018/05/16
3.9K2
java之序列化机制(1)
有关Java对象的序列化和反序列化也算是Java基础的一部分,下面对Java序列化的机制和原理进行一些介绍。
IT工作者
2022/05/07
2110
手把手教你写JAVA反序列化的POC
前面,我们了解了java的序列化机制,也知道在什么情况下会出现漏洞,为了对反序列化漏洞有个更直观的认识,这里就来说一说存在于apache commons-collections.jar中的一条pop链,要知道这个类库使用广泛,所以很多大型的应用也存在着这个漏洞,我这里就以weblogic cve-2015-4852来说说反序列化漏洞的具体利用方法。
tnt阿信
2020/08/05
1.7K0
手把手教你写JAVA反序列化的POC
Weblogic 反序列化漏洞(CVE-2018-2628)漫谈
2018年4月18日,Oracle官方发布了4月份的安全补丁更新CPU(Critical Patch Update),更新中修复了一个高危的 WebLogic 反序列化漏洞CVE-2018-2628。攻击者可以在未授权的情况下通过T3协议对存在漏洞的 WebLogic 组件进行远程攻击,并可获取目标系统所有权限。
Seebug漏洞平台
2018/05/03
1.7K0
Weblogic 反序列化漏洞(CVE-2018-2628)漫谈
Java安全-反序列化-2-CC
TransformedMap,⽤于对Map类型的对象做修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。 如下,传入变量innerMap,返回outerMap。outerMap在添加新元素时,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的Value的回调,处理后得到的返回值才会被添加进outerMap中
Naraku
2022/04/14
3490
Java安全-反序列化-2-CC
Weblogic反序列化历史漏洞全汇总
序列化是让Java对象脱离Java运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。
Jayway
2019/09/29
8.1K0
Weblogic反序列化历史漏洞全汇总
Java反序列化之CC1链
Commons Collections是Apache软件基金会的一个开源项目,它提供了一组可复用的数据结构和算法 的实现,旨在扩展和增强Java集合框架,以便更好地满足不同类型应用的需求。该项目包含了多种不同 类型的集合类、迭代器、队列、堆栈、映射、列表、集等数据结构实现,以及许多实用程序类和算法实 现。它的代码质量较高,被广泛应用于Java应用程序开发中。
红队蓝军
2024/05/27
2430
Java反序列化之CC1链
相关推荐
Java 反序列化学习
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验