反序列化与序列化过程分析

news2024/10/6 8:38:19

前言

在学习反序列化的漏洞时,大致都是了解了一些知识,比如序列化就是写入对象,反序列化就是读取文件恢复对象,在这个过程中会自动调用一些方法,readObject,writeObject,静态代码块等,但是从来没有了解过这个过程是怎么样的,一直很模糊,所以在这篇文章里面会记录整个学习过程,参考的技术文章较少,可能会有错误,希望理解

这里用cc2来举一个例子,并不解释cc2的原理,主要看一下是怎么写入序列化的数据怎么读取反序列化的数据

PriorityQueue的变量组成

图片

因为在序列化的过程中,静态常量,由transient修饰的变量都不会被序列化,serialVersionUID这个变量也是,所以在序列化PriorityQueue的时候,只有以下两个变量是可以被序列化的,同时也是执行命令的关键

private int size = 0;
private final Comparator<? super E> comparator;

cc2代码:

public static void vulnToTemplatesImpl() throws IOException, CannotCompileException, NotFoundException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        PriorityQueue priorityQueue = new PriorityQueue();
        priorityQueue.add(1);
        priorityQueue.add(1);
        //获取恶意类的字节码
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.get("util.Evil");
        byte[] evilPayload = ctClass.toBytecode();
        //设置恶意字节码到TemplatesImpl中
        TemplatesImpl templates = new TemplatesImpl();
        SerializeUtil.setFieldValue(templates,"_name","v1f18");
//        SerializeUtil.setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        SerializeUtil.setFieldValue(templates,"_bytecodes",new byte[][]{evilPayload});
        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);
        TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);
        SerializeUtil.setFieldValue(priorityQueue,"comparator",transformingComparator);

        Field queue = priorityQueue.getClass().getDeclaredField("queue");
        queue.setAccessible(true);
        Object[] o = (Object[]) queue.get(priorityQueue);
        o[0] = templates;
        SerializeUtil.writeObjectToFile(priorityQueue);
        SerializeUtil.readObjectToFile();
    }

ObjectOutputStream过程分析

进入ObjectOutputStream的单参数构造方法中

图片

进入verifySubclass

图片

这个verifySubclass上面有注释,大致意思就是在不违反安全限制下序列化这个类

通过getClass返回当前类的class类,判断是否为ObjectOutputStream.class

一般来说代码就直接return回去了,只有其他类(不是ObjectOutputStream的子类)才会继续执行下面的代码(权限检查),本意就是保护ObjectOutputStream类的内部实现和数据完整性

接着往下走

图片

bout是一个BlockDataOutputStream类,将序列化的数据写入out中

handles,subs一个用于管理内存中的数据结构,另一个用于替代对象,

进入writeStreamHeader方法

图片

这里用于bout写入魔术头,在之前就了解过java的序列化数据开头为aced0005,这个魔术头就是在这里写入的,对应的两个魔术头参数:

图片

继续往下,bout.setBlockDataMode(true)表示开启以块模式写入,这个块模式简单理解就是方便序列化数据的传输

然后接着往下走,查看extendedDebugInfo是什么:

图片

这个表示是否开启调试信息

然后进入writeObject

图片

enableOverride在构造方法里面已经设置了false

进入writeObject0

图片

这个subs.lookup(obj),去寻找ReplaceTable中是否已经存在了obj的类,如果不存在就返回对象本身

handles.lookup(obj)也是一样的,如果没找到,则返回-1

接着往下跟

图片

先获取obj的class对象赋值给cl,进入死循环,进入ObjectStreamClass.lookup(cl, true)方法

图片

这个方法主要用来在缓存中查找是否存在要序列化的对象的class,如果没有就创建一个ObjectStreamClass,里面用于存放class类的一些信息

new WeakClassKey(cl, Caches.localDescsQueue)将cl与Caches.localDescsQueue分别对应Reference中的referent和queue

进入Caches.localDescs.putIfAbsent(key, newRef);

图片

这里是将key(里面包含了PriorityQueue.class)和newRef做一个关联,代表这个cl已经处理过了

图片

然后进入构造方法

图片

这个构造方法里面就包含了关于class的类描述内容

有class对象,class的名称,serialVersionUID,是否重写了writeObject和readObject,以及一些没有被transient修饰的属性…

图片

这里验证前面说的只有这两个变量是可以被序列化的,结束构造方法

图片

结束ObjectStreamClass.lookup(cl, true)

返回writeObject0

obj.getClass()) == cl这个是恒成立的,直接break死循环出去

下面的enableReplace为false,这个enableReplace只有在ObjectOutputStream的子类中才有可能为true

下面的obj != orig也为false,在上面obj赋值给orig

然后运行到这:

图片

这个就肯定得进入了,不然就要爆一个序列化的类没实现Serializable的错了

跟入writeOrdinaryObject

图片

extendedDebugInfo调试信息为false,之前分析过了

检查是否能序列化

bout.writeByte(TC_OBJECT)写入0x73,进入:writeClassDesc

图片

进入writeNonProxyDesc

图片

写入0x72,进入writeClassDescriptor

图片

跟进

图片

写入class的name,反序列化suid,flags(对应class的类别),变量的个数

在下面写入最主要的关于变量的描述:

for (int i = 0; i < fields.length; i++) {
            ObjectStreamField f = fields[i];
            out.writeByte(f.getTypeCode());
            out.writeUTF(f.getName());
            if (!f.isPrimitive()) {
                out.writeTypeString(f.getTypeString());
            }
        }

结束返回到writeNonProxyDesc

这个时候可以用编辑器打开序列化的数据:

图片

和分析的一样

图片

这个annotateClass应该留给子类扩展的

进入writeClassDesc

图片

注意参数desc.getSuperDesc(),PriorityQueue的AbstractQueue父类没有实现Serializable所以为空

返回null,执行到writeOrdinaryObject

图片

进入writeSerialData

图片

这里判断了是否重写WriteObject方法,PriorityQueue是有重写的,如果没有重写的话,就是另外的逻辑去写入数据了

进入到PriorityQueue的WriteObject

图片

defaultWriteObject用来将具体的变量值写入序列化数据中

图片

将PriorityQueue对象和PriorityQueue类描述符传入,进入defaultWriteFields

图片

主要看这个for循环

desc.getFields(false)获取类的字段,就是那两个

desc.getNumObjFields()返回被修改的字段数,我们只修改了一个,所以返回1

进入getObjFieldValues看一下

图片

继续

图片

这个循环没看懂,大致意思应该就是把修改了的数据赋值吧

返回defaultWriteFields方法

图片

循环这个objVals,其实这里就已经很明白了,就是通过递归的方式去写入数据

进入writeObject0

图片

看这个obj和之前的PriorityQueue一样啊,这里就不再分析了,都是一样的流程

所以重写的writeObject的逻辑最主要的地方就是在defaultWriteObject里面

回到PriorityQueue的writeObject方法,最后就是看属性有没有重写writeObjet方法了

图片

序列化的过程分析到这里结束

ObjectInputStream过程分析

进入ObjectInputStream的构造方法

图片

这个verifySubclass和上面序列化时相同

bin:用于处理输入流的对象,将读取的数据进行解析

handles:与对象对应的表

vlist:反序列化完成之后需要执行的方法列表

进入readStreamHeader:

图片

这里与序列化对应,判断前两个字节是否为0xaced0005

下面的setBlockDataMode就是开启以块模式读取

然后进入readObject方法

图片

进入readObject0

图片

获取当前的读取模式,这个在之前的构造方法里面设置了为true

bin.currentBlockRemaining()用于返回还没有读取的字节数

然后再由bin.setBlockDataMode(false)关闭块模式

bin.peekByte()用于查看下一个字节是否为最后一个字节

这里的bin.peekByte()读取的就是在序列化的时候写入的0x73,这里都是和序列化过程对应的

图片

进入readOrdinaryObject

图片

在这个方法里面有判断了一次是否为0x73

进入readClassDesc

图片

这个方法主要返回类的描述信息,读取的tc为114对应的0x72

进入readNonProxyDesc

图片

这个assign方法就是将desc这个对象和一个句柄绑定,这样就就可以通过句柄在句柄表中快速找到对应的对象

进入readClassDescriptor

图片

这个就是读取类描述的主要方法了,看一下readNonProxy读取了哪些信息

图片

类名称name,serialVersionUID,是否为代理对象,是否重写了WriteObject,以及成员变量等

这里的numFields也是和上面的序列化过程对应的两个可序列化的变量,然后创建长度为2的ObjectStreamField数组

看一下for循环

利用readByte读取下一个字节,readUTF读取类名信息

判断这个变量是什么类型的,比如我这里的第一个Isize,在创建ObjectStreamField的时候就会选择对应的类型:

图片

其实在这里也写了[,L为Object类型,后面那个comparator变量就不分析了一样的

然后回到readNonProxyDesc方法,readDesc就是类描述的对象

图片

进入resolveClass方法

图片

这里其实就是类加载里面的一个过程,链接指定的类,说白了就是返回对应的类class对象

返回对应的PriorityQueue.class,注意这里的Class.forName的第二个参数为false即不初始化,所以不会去执行静态代码块

退出resolveClass

skipCustomData()方法用于跳过所有块数据和对象,直到遇到 TC_ENDBLOCKDATA。

然后进入initNonProxy()方法

图片

先获取了serialVersionUID,进入lookup方法

图片

和序列化的时候一样,返回对应的class描述对象osc,但是这里有一个在反序列化中常用的操作,就是去执行静态代码块,简单看一下就好了分两种情况

  1. 类存在serialVersionUID变量,在new ObjectStreamClass(cl)的构造方法里面,会去调用getDeclaredSUID,getDeclaredSUID里面f.getLong(null),这里的f其实就是在获取对应的serialVersionUID参数

    图片

在第一次获取的时候,会导致类的加载和初始化,所以会去执行静态代码块,这个没法分析,底层都是c来写的,看不到源码

  1. 不存在serialVersionUID变量,在initNonProxy的osc.getSerialVersionUID()[因为不存在serialVersionUID,所以需要去计算这个类的serialVersionUID]中存在computeDefaultSUID方法,来计算serialVersionUID,这里面就存在了如果有静态代码块的话就去执行

图片

继续往下调试

图片

在这个位置,对比了SerialVersionUID,如果不相同的话就会抛异常

图片

然后这里就初始化了一些非代理类的描述参数

然后回到readNonProxyDesc方法

handles.finish(descHandle)这个方法应该表示对应的句柄已经使用完成,把这个descHandle标记成已经完成

然后回到readOrdinaryObject了

图片

desc.checkDeserialize();检查是否允许对类的反序列化

获取对应的class赋值给cl

obj = desc.isInstantiable() ? desc.newInstance() : null;

检查这个类是否可以外部化并且(或父类)存在无参构造方法,则返回true,即创建对应的对象赋值给obj

然后下面还有一个需要关注的地方:

图片

这个readSerialData中包含了我们是否需要执行用户自定义的readObject方法

图片

如果有的话就会进入slotDesc.invokeReadObject(obj, this)来反射调用readObject,进入PriorityQueue的readObject方法

图片

进入defaultReadObject,这个方法也是和序列化的defaultWriteObject一样的,去获取对应变量的值,也是递归的方式,具体往下看

图片

进入defaultReadFields,将PriorityQueue对象和PriorityQueue描述传入

图片

这里主要看这个for循环,这个代码看着非常熟悉,先获取全部的序列化变量,然后获取修改的变量个数

进入readObject0(f.isUnshared())

图片

进入readOrdinaryObject

图片

从这里开始就又开始递归了,这里简化一些

图片

从readClassDesc方法读取下一个字节判断下一个字段的类型进入对应的方法readNonProxyDesc

从readClassDescriptor里读取类描述,然后从resolveClass获取对应类描述的类class,然后initNonProxy来对类描述初始化,而后在进入readSerialData进行读取变量数据,执行readObject,最后又开始递归直到任务完成即读完所有内容

最后进入到checkResolve

图片

这个方法主要检查obj是否有效

然后回到readObject了

图片

下面就是准备结束整个反序列化过程了,并且调用回调函数,到这里也就结束了。

总结

我这里没有去看cc2是如何执行命令的,而是侧重怎么把数据写入和读取,如果跟完整个过程,会发现其实本质上就是在自动的调用各处的代码,在这些自动调用的代码中寻找突破口已到达执行命令的目的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/882862.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

⛳ Docker - Centos 安装配置

目录 ⛳ Docker - Centos 安装配置&#x1f3ed; Docker 安装&#xff1a;&#x1f4e2; 一、安装依赖包&#x1f4ac; 二、添加 Docker 下载源地址&#x1f43e; 三、更新yum缓存&#x1f463; 四、安装Docker&#x1f4bb; 五、启动Docker&#x1f381; 六、查看Docker状态和…

网络编程(8.15)io模型,IO多路复用(select,poll)

1.使用select函数实现IO多路复用 使用select函数实现IO多路复用的服务器&#xff1a; #include<stdio.h> #include<head.h> #include<netinet/in.h> #include<sys/select.h> #include<arpa/inet.h> #define PROT 1112 #define IP "192.16…

缓存淘汰算法(LFU LRU FIFO)及进程的状态和转换

目录 一、缓存淘汰算法 1.LFU&#xff08;Least Frequently Used&#xff09;最近最不常用算法 2.LRU&#xff08;Least Recently User&#xff09;最近最少使用算法 3.FIFO&#xff08;First in first out&#xff09;先进先出算法 二、进程的状态和转换 1.最基本的三种状…

解决Mac系统android monitor启动时卡住,显示白屏的问题

一.启动环境 清安装1.8版本的jdk&#xff0c;java1.8版本以上不支持android monitor&#xff1b;如果你电脑上安装有java 11等高级别的版本&#xff0c;请自行搜索&#xff0c;如果在mac上安装多jdk&#xff0c;以及如何切换到1.8版本上 二.解决方案 请更新SWT插件&#xff…

x86架构芯片启动过程分析

1、上电启动顺序 上电自检 读取ROM里的bios程序 bios程序会进行硬件检测&#xff0c;比如&#xff1a;内存、硬盘、显卡等 bios完成自检后&#xff0c;需要选择引导设备。比如设备上有U盘、SSD、eMMC、机械硬盘&#xff0c;bios需要知道从哪个启动介质去启动计算机 bios操作界面…

资料分析(四)—— 倍数、比重、平均数

倍数 现期倍数 &#xff08;A是B的几倍&#xff09;&#xff1a; 多几倍 1 增长率 1 增长倍数&#xff08;A比B多几倍&#xff09;&#xff1a; - 1 是几倍 - 1 增长率&#xff08;增长几倍&#xff09; 超过倍数&#xff08;A超过B的 n 倍&#xff09;&#xff1a;A …

数据库--MySQL三大范式、多表查询、函数sql

数据库相关链接&#xff1a; 数据库基础操作--增删改查&#xff1a;http://t.csdn.cn/189CF 数据库--数据类型&#xff1a;http://t.csdn.cn/NnBsY​​​​​​​ 数据库--SQL关键字的执行顺序&#xff1a; http://t.csdn.cn/MoJ4i 一、什么是范式&#xff1f; 范式是数据库…

Android Settings 无障碍设置显示大小页面重复加载问题

基于Android 11&#xff0c;跟踪源码 显示大小页面 packages/apps/Settings/src/com/android/settings/display/PreviewSeekBarPreferenceFragment.java 通过commit() 提交更新页面显示大小。该方法是是在其父类PreviewSeekBarPreferenceFragment 实现调用。 基类预览滑动进度…

Vue-6.创建Vue项目

使用预设默认配置创建Vue项目 创建一个简单的 Vue 项目需要使用 Vue CLI&#xff08;命令行界面&#xff09;。Vue CLI 是一个用于快速构建 Vue.js 项目的工具&#xff0c;它可以帮助你设置项目的基本结构、配置以及开发环境。 以下是创建一个简单的 Vue 项目的步骤&#xff…

机器学习基础之《分类算法(1)—sklearn转换器和估计器》

一、转换器 1、什么是转换器 之前做特征工程的步骤&#xff1a; &#xff08;1&#xff09;第一步就是实例化了一个转换器类&#xff08;Transformer&#xff09; &#xff08;2&#xff09;第二步就是调用fit_transform&#xff0c;进行数据的转换 2、我们把特征工程的接口称…

idea中Maven报错Unable to import maven project: See logs for details问题的解决方法

idea中Maven报错Unable to import maven project: See logs for details问题的解决方法。 在查看maven的环境配置和idea的maven配置后&#xff0c;发现是idea 2020版本和maven 3.9.3版本的兼容性问题。在更改为Idea自带的maven 3.6.1版本后问题解决&#xff0c;能成功下载jar包…

个人对哈希数据结构学习总结 -- 实践篇 -- 上

个人对哈希数据结构学习总结 -- 实践篇 -- 上 引言最佳实践Java篇HashMapgetput扩容 ConcurrentHashMapgetput扩容协作扩容读为什么可以不加锁&#xff1f; ThreadLocalMapgetput扩容delete为什么遍历到null桶就可以判断key不存在&#xff1f;ThreadLocalMap为什么不需要锁&…

【uniapp】picker mode=“region“ 最简单的省市区 三级联动

省市区 picker template <picker mode"region" :value"date" class"u-w-440" change"bindTimeChange"><u--inputborder"bottom"class"u-fb u-f-s-28"placeholder"请选择省市区"type"te…

从零实战SLAM-第八课(非特征点的视觉里程计)

在七月算法报的班&#xff0c;老师讲的蛮好。好记性不如烂笔头&#xff0c;关键内容还是记录一下吧&#xff0c;课程入口&#xff0c;感兴趣的同学可以学习一下。 --------------------------------------------------------------------------------------------------------…

ARM04cortex-A7核LED灯实验

文章目录 一、核心板二、扩展板二、硬件术语2.1 原理图2.2 PCB板2.3 丝印2.4 网络编号 三、分析电路图3.1 思路3.2 总结3.3 工作原理 实验目的&#xff1a;实现LED1/LED2/LED3三盏灯工作 一、核心板 二、扩展板 二、硬件术语 2.1 原理图 原理图是用来描述PCB板子上各个硬件连接…

深入理解epoll

文章目录 概述1. epoll_create - 创建一个epoll实例2. epoll_ctl - 控制epoll实例的事件结构体介绍events取值&#xff1a;data&#xff1a; 联合体&#xff08;共用体&#xff09;&#xff1a; 3. epoll_wait - 等待事件发生伪代码总结 概述 在网络编程中&#xff0c;高效地处…

2023 年牛客多校第九场题解

B Semi-Puzzle: Brain Storm 题意&#xff1a;给定 a , m a,m a,m&#xff0c;构造一个非负整数 u u u&#xff0c;使得 a u ≡ u ( m o d m ) a^u \equiv u \pmod m au≡u(modm)。 1 ≤ a < m ≤ 1 0 9 1 \le a<m \le 10^9 1≤a<m≤109&#xff0c; 0 ≤ u ≤ 1 …

Elasticsearch:如何在 Ubuntu 上安装多个节点的 Elasticsearch 集群 - 8.x

Elasticsearch 是一个强大且可扩展的搜索和分析引擎&#xff0c;可用于索引和搜索大量数据。 Elasticsearch 通常用于集群环境中&#xff0c;以提高性能、提供高可用性并实现数据冗余。 在本文中&#xff0c;我们将讨论如何在 Ubuntu 20.04 上安装和配置具有多节点集群的 Elast…

centos7安装protobuf|序列化和反序列化工具

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总https://blog.csdn.net/yu_cblog/categ…

如何利用视频监控与AI智能识别技术实现铁塔基站机房无人值守?

一、项目背景 很多通信铁塔和机房类项目&#xff0c;都呈现高密度、网格化分布的特点&#xff0c;铁塔基站大多都分布在公路边、高山、野外等区域&#xff0c;巡检难度大&#xff0c;维护效率低&#xff1b;基站设备众多且监控方式单一&#xff0c;而且时刻面临着非法闯入、被…