Panda | 热爱安全的理想少年 - java 序列化
type: channel
link: https://www.cnpanda.net/tag/java-序列化/
items: 用一个 case 去理解 jdk8u20 原生反序列化漏洞
items: 《Object Serialization Stream Protocol/对象序列化流协议》总结

用一个 case 去理解 jdk8u20 原生反序列化漏洞
type: item
date: 2021-06-30T04:42:00+00:00
description: 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.javacompare一下：// 改之前 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()方法：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) { …

《Object Serialization Stream Protocol/对象序列化流协议》总结
type: item
date: 2021-06-10T15:53:00+00:00
description: 目录目录0、写在前面1、概述2、stream元素3、Stream 协议版本4、Stream 格式的语法a. 语法规则b.终端符号和常数5、参考0、写在前面本文主要是《Object Serialization Stream Protocol》一文的翻译，然后对序列化格式进行了一些总结1、概述​stream 格式满足以下设计目标：结构紧凑，便于高效阅读；允许仅使用流的结构和格式而不需要深入了解 stream，这种情况不需要调用调用任何类的代码；只允许 stream 对数据进行访问和操作；2、stream元素​表示 stream 中的对象需要的基本结构。对象的每个属性都需要表示：它的类和类中的字段，这些数据会被写入而且之后会被类中特定的方法读取。stream 中对象的表示可以用语法来描述。null objects, new objects, classes, arrays, strings以及对stream 中已有的任何对象的反向引用，都有特殊的表示。写入 stream 的每个对象都分配了一个Handle，用于引用回该对象。从0x7E0000开始按顺序分配Handle。当 stream 被重置时，句柄在0x7E0000处重新启动。类对象由以下对象表示：它的ObjectStreamClass对象​ 非动态代理类的ObjectStreamClass对象由以下表达式表示：可兼容类的SUID一组指示类的各种属性的标志，例如该类是否定义了 writeObject 方法，以及该类是否可序列化、是否是可外部或者是否是枚举类型可序列化字段的数量默认情况下，对于类的字段数组和对象字段来说，字段的类型要作为字符串被包含，并且必须按照 Java 虚拟机规范中的规定，采用“字段描述符”格式（例如，Ljava/lang/Object;）由 annotateClass 方法写入的可选块数据(Data-Block)记录或对象其超类型的 ObjectStreamClass（如果超类不可序列化，则为 null）动态代理类的ObjectStreamClass对象由以下表达式表示：动态代理类实现的接口数动态代理类实现的所有接口的名称，这些接口通过调用Class的getInterfaces方法的返回结果进行排序列出；可选的块数据(Data-Block)记录或由AnnotationProxyClass方法编写的对象超类对应的java.lang.reflect.Proxy的ObjectStreamClass​ 字符串对象的表示由长度信息和MUTF-8编码的字符串内容组成。MUTF-8编码与Java虚拟机和Java.io.DataInput以及DataOutput接口中使用的编码相同；它在表示补充字符和空字符方面与标准UTF-8不同。长度信息的形式取决于MUTF-8编码中字符串的长度。如果给定字符串的MUTF-8编码长度小于65536字节，则该长度将写入表示无符号16位整数的2字节。从Java2平台标准版v1.3开始，如果MUTF-8编码中的字符串长度为65536字节或更多，那么该长度将以表示有符号64位整数的8字节表示。序列化 stream 中字符串前面的类型码用于表明写入字符串的格式。数组由以下内容表示：他们的ObjectStreamClass对象元素的数量。值的顺序。值的类型在数组的类型中是隐式的。例如，字节数组的值是byte类型的。枚举常量由以下表达式表示：常量的基本枚举类型的 ObjectStreamClass 对象常量的名称字符串stream 中的新对象（New objects）由以下表示：所有对象类的派生类信息；对象的每一个可序列化类的数据，从它的顶级父类开始写入。针对 Stream 中的每一个类的信息包含以下内容： -- 一个类中的可序列化字段信息；-- 如果这个类包含writeObject/readObject方法，有可能出现通过writeObject方法写入的可选对象或者基础类型的数据块(Data-Block)记录，跟着使用endDataBlock的代码；​ 类写入的所有原始数据都被缓冲并包装在数据块记录中，无论数据是在 writeObject 方法内写入 stream 还是从 writeObject 方法外部直接写入 Stream。 此数据只能通过相应的 readObject 方法读取或直接从 stream 中读取。writeObject方法写入的对象终止任何以前的数据块记录，并视情况作为常规对象或空引用或反向引用写入。数据块记录允许错误恢复丢弃任何可选数据。当从类内调用时，stream 可以丢弃任何数据或对象，直到endBlockData。3、Stream 协议版本​ 有必要对JDK1.2中的序列化 Stream 格式进行更改，该格式与JDK1.1的所有…