介绍

本篇主要分析java序列化、反序列化的过程,原理,
并且通过简化版URLDNS做案例分析利用链原理。

本篇很重要,打好基础是关键。

什么是序列化

我的理解很简单,就是将对象转化为可以传输、储存的数据。
什么意思?
正常一个对象,只能存在于程序运行时,当程序运行结束了,对象就消失了,对象只在程序运行时存在

如有这样一个需求,我想把user对象(包含了名字,年龄,身高,简历信息)传给数据持久化的服务(保存数据的服务)。
需要怎么做?
创建一个json对象,然后把user对象信息填写到json中,
{“name”:“zhangsan”,“age”:12,“height”,12,“resume”:{“school”:“tsinghua”,“level”:1}}
然后把这个json传给持久化服务,
这样就要求每次传输数据时,都要将对象先转为json等格式数据,再进行传输,而且这只是简单的数据。
如果是更复杂的,对象A里有对象B、C,对象B又有D,则需要手动,将这些数据转成json,发送到远程服务器,再根据这些数据,还原对象。

而使用序列化,则是将user对象序列化为数据,传输到持久化服务器上时,再反序列化,就得到了user对象,让对象可以传输。
所以序列化,就是将对象转化为数据,相当与给对象拍了个快照,传输或者储存,使用时再反序列化,又变为了对象。

看下php序列化和反序列化过程:

执行结果:

再反序列化看下:

php的序列化将对象转化为了字符串,包含了对象的所有数据信息,
反序列化时再根据这些信息还原对象。

【一>所有资源获取<一】
1、200份很多已经买不到的绝版电子书
2、30G安全大厂内部的视频资料
3、100份src文档
4、常见安全面试题
5、ctf大赛经典题目解析
6、全套工具包
7、应急响应笔记
8、网络安全学习路线

JAVA序列化

一个java序列化对象例子:

class Person implements Serializable{public String name;public int age;Person(String name,int age){this.name = name;this.age = age;}
}public class Main {public static void main(String []args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {FileOutputStream out =new FileOutputStream("person.txt");ObjectOutputStream obj_out = new ObjectOutputStream(out);obj_out.writeObject(new Person("z3",12));}
}

FileOutputStream和ObjectOutputStream是java的流操作,可以把OutputStream当做一个单向流出的水管,FileOutputStream打开了文件,就相当于给文件接了一个File类型水管,然后把FileOutputStream类型对象传给了ObjectOutputStream,相当于把File类型水管接到了Object类型水管。

由于Object类是所有类的父类,所以Object类型水管可以投放任何对象,

这里创建了Person对象并传给writeObject方法,
相当于把Person对象扔进了Object类型水管,即 Person对象->Object类型水管->File类型水管->文件
这样就把Person对象写入了文件,

java输入输出流的方式处理数据真聪明
如果我想把序列化对象写入byte数组,那就创建个byteArrayOutputStream类型水管,然后,把它接到Object类型水管上,后面步骤不变,则:Person对象->Object类型水管->byte类型水管->byte数组

回到正题,
打开文件查看是乱码。

因为每个语言都有自己的序列化规则,java的序列化方式不适用于用文本方式查看。
可以使用SerializationDumper工具查看。

红框内对应的是类名,类属性名,成员变量值。
如果在类里添加方法,会被序列化吗?
答案是不会,调试发现,writeObject时只会将FieldValues即 成员变量序列化,
所以,序列化的并不是整个对象,只是对象的属性值。

以上就是java序列化的方法了,
但仔细看代码,会发现Person类继承了Serializable接口,
但是没有实现接口中的方法,,那继承这接口有什么用?去掉试试。
报错了

这是什么原因?
看一下Serializable接口,接口是空的

public interface Serializable {
}

调试看一下,为什么非要继承个空接口才能序列化?这空接口起到了什么作用?
如图

原因在这里,如果类不继承Serializable,那对象不属于字符串、数组、枚举类型,那就会进入else,抛出NotSerializableException异常,
所以,Serializable,只是用来标志,这个对象可以被序列化的。

在查看Serializable接口时,在注释中看到,建议在类里声明serialVersionUID变量,不然自动生成的serialVersionUID很容易出问题。

因为默认的serialVersionUID是jdk生成的,不同版本jdk可能生成的值不同,
看名字应该能猜到serialVersionUID是序列化版本的标志,如果序列化数据的serialVersionUID 和对应类的serialVersionUID 不一致,就会导致反序列化失败。

java反序列化

首先看下反序列化代码

class Person implements Serializable{public String name;public int age;Person(String name,int age){this.name = name;this.age = age;}
}public class Main {public static void main(String []args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {FileInputStream in =new FileInputStream("person.txt");ObjectInput obj_in = new ObjectInputStream(in);Person p = (Person) obj_in.readObject();System.out.println(p.name);}
}

成功将上面序列化的对象读出,

和上面的代码区别只是把Output换为了Input,把writeObject换为了readObject。
这也很好理解,把单向流出的水管换为单向流入的(Output换为Input),然后把写入数据的writeObject换为readObject,即:序列化数据person.txt->File类型水管->Object类型水管->Object对象。
(Person)这个用法是强制类型转换,将Object转Person类型,

再调试下,看java如何从一个二进制的序列化数据,恢复为java对象的,

首先读取了二进制的第一个字节,如果不是0x73,即10进制的115,就抛异常。

根据变量名TC_OBJECT可以猜出,0x73标志着数据是Object类型。
继续调试

发现然后又读了一个字节x72,对应变量TC_CLASSDESC,是类的描述:

然后读出类名和suid(对应serialVersionUID)。

后面有继续读出了classDescFlags、Fields等信息,不一一调试了。
对应数据如图:

这一步读出了类的所有信息,储存到ObjectStreamClass类型对象desc中,

接下来desc又newInstance(),创建了一个对象obj。

这个newInstance()是不是很熟悉?
在学反射时知道,Class类、Constructor类都有newInstance()方法。
看一下ObjectStreamClass类的newInstance()方法,
发现,newInstance调用的是类成员变量cons的newInstance方法。

private Constructor<?> cons;

所以在这一步,通过调用Object类的无参构造函数,创建了obj对象,这个对象有Person类的所有属性

继续调试,调用了方法。

readSerialData(obj, desc);

看起来是将读出的序列化数据赋值给对象,
然后继续调试,跟了很久。

putObject又是个native方法,将键值赋值给obj对象,
这也就能解释的通,为什么在反序列化时,java不会发出警告。
因为如果通过反射修改private成员函数值,java会发出警告,
正常反射修改private变量会发出警告,如图:

可以利用下吗?
执行下面代码试下:

Unsafe unsafe = Unsafe.getUnsafe();
Person p = new Person("z3",12);
unsafe.putObject(p, 16, "z5"); // 至于第二个参数为什么是16,不知道,调试时发现java就这么调的,可能16是"name"的一个标志?
System.out.println(p.getName());

报错

好吧,先不管它了,估计是用不了。

忘了说,调试时发现readSerialData方法,它会递归的反序列化当前对象的所有成员变量。

所以,总结,java在反序列化时,是递归的,将对象反序列化,对象的成员变量反序列化,对象的成员变量的成员变量反序列化。
一层层递归调用,直到基本变量,然后一层层再出来,完成最外层对象的反序列化,整个过程是由内向外的。
例如对象A里包含对象B,对象B包含对象C,C包含一个数字1,
将A序列化后,再反序列化的过程是,先打开A,发现A里有B,则再打开B,发现B包含C,再打开C,发现C里有1,则读取出1,完成C的反序列化,再把这个C给B,完成B的反序列化,再把B给A,完成A的反序列化。
这个过程特别像把对象A一层层扒开,直到遇到基本变量,然后开始由内向外反序列化,

现在知道反序列化过程了,读出序列化的数据,创建一个空的对象,再调用native方法,为对象赋值。

java反序列化利用(URLDNS)

现在了解了序列化与反序列过程。
怎么利用呢?

序列化是将对象里的成员变量序列化,进行传输,另一端收到序列化数据后,反序列化得到对象,

在这个过程中,反序列化的数据有可能会被拦截,修改,

所以就是说,反序列化数据可控,也就是对象的的成员变量可控。
在之前的文章中提到过,对象的方法执行过程是有可能成员变量影响的,而成员变量可控,那就有希望通过构造成员变量,控制方法的执行。

怎么通过控制对象的成员变量影响方法的执行呢?
首先,我们可控的只有变量的值,所以,想执行代码或命令,只能靠java自带的类方法,则需要寻找java可以被利用的类。

看下面这个代码

先调用genURLDNS函数,生成序列化对象,保存到文件out.bin中,

之后,每调用一次getURLDNS函数,都会从文件读取序列化数据,然后反序列化,然后导致本机发出一条dns请求。

这个out.bin就是构造好的恶意序列化数据,也可以叫做payload,作用是可以控制机器向指定地址发出一条dns请求,

那就调试下反序列化的过程,看看什么原因造成的。
readObject之前已经调试过了,但是忽略了一个点,
在readSerialData方法中调用了invokeReadObject,而在invokeReadObject函数中,判断了当前类是否有readObject方法,如果有,则通过反射的方式调用该类的readObject方法(例如反序列化A类时,如果A类有readObject方法,那就按A类自带的readObject方法反序列化,如果没有,那就用默认的方式反序列化)。

而问题的产生,就在HashMap的readObject方法中。
这里调用了hash(key)

调用了key的hashCode方法

而key是URL对象,
看下URL对象的hashCode方法,首先判断hashCode,不为-1,则调用handler.hashCode。

继续向下跟

而getHostAddress过程中发现这一步骤

正是这句代码,导致了dns查询,

所以总结下,反序列化导致dns查询的过程,
ObjectInputStream的readObject方法 =》 HashMap的readObject方法 => 创建HashMap对象,并putVal(hash(key)) key是URL对象 =》 URL的hashCode方法 =》 URLStreamHandler的hashCode方法 =》URLStreamHandler的getHostAddress方法 =》 InetAddress.getByName方法。

这个就是造成dns查询的利用链,也叫做Gadget,

再回头看下代码,开始图片中标注的“这里先不看”。

看注释说明,是为了防止造成两次dns查询,
现在明白了URLDNS的利用链,分析下原因。
因为hashMap.put一个url对象时会调用,
putVal(hash(key))代码,而上面总结过hash(URL对象)会造成dns查询。

f是hashCode方法的对象,把url对象的hashCode的值改为非-1,然后put(url),再把hashCode的值改回-1
回想一下“URL的hashCode方法”中,hashCode不为-1时,直接return,就不会有后面的dns查询了,利用链到“URL的hashCode方法”就断掉了。

而hashCode默认值就是-1,
所以通过这种方式,就可以避免在构造恶意序列化对象时,触发dns请求。

总结

造成反序列化漏洞的根本原因分析

序列化的内容只有类名成员变量,所以可控点是,类名和成员变量的值。
通过控制类名、可以指定反序列化的类,通过控制变量的值,就可以影响代码执行流程。然后按照我们的期望,将这些“影响”连接在一起,就可以控制代码的执行。
例如本例中,
影响1:URL对象赋值了一个http://开头的url地址,导致它只要调用hashCode就会查询dns(如果不赋值一个http://开头的url地址,就不会查询)。
影响2:为HashMap赋值了一个URL对象,导致它在反序列化时,会计算URL对象的hash(如果不赋值,就不会计算hash)。
上面dns查询,计算hash,这两个动作,都是因为两个变量的值导致的,所以:控制变量的值,会影响代码的执行流程。

当一个影响产生了“动作”,就可以触发下一个影响,再触发下一个影响,最后达到预期效果:命令执行,而这些能搭配使用且能达到我们预期效果的“影响”们,就是利用链。
而这个“动作”来源,只能是类的readObject方法,因为默认的反序列化过程,只是简单的赋值,,变量的值不会对代码的执行造成任何影响,也就不会有利用链。
类自定义的readObject方法,初衷是为了反序列化出完整的对象(有些类很复杂,简单的赋值不能复原对象)。
但是这个readObject方法会受变量值得影响,产生“动作”,触发下一个“影响”。

这就像,每个类都是一个形状不确定的零件,当这个类被实例化后,对象的变量值确定了,这个零件的形状就固定了,不同的变量值,会导致零件形状不同。
而我们要做的,就是从java自带的这些不确定的零件中找出,固定形状后可以像多米诺骨牌一样,后一个可以推倒前一个的这种零件。
然后将这些零件按多米诺骨牌一样排列,最后一个零件倒下,就会按动致命令执行按钮,
只要第一个零件被推倒,就会引发后面一系列连锁反应,导致命令执行。
而推倒第一个零件的,就是readObject方法,因为它是唯一一个,在反序列化时,受变量值影响代码执行过程的函数,所以推倒第一个零件的动作,由它发出。

什么是反序列化?反序列化的过程,原理相关推荐

  1. java 反序列化 ysoserial exploit/JRMPListener 原理剖析

    目录 0 前言 1 payloads/JRMPClient 1.1 Externalizable 1.2 生成payload 1.3 gadget链分析 2 exploit/JRMPListener ...

  2. Web APi之过滤器执行过程原理解析【二】(十一)

    前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...

  3. Algorithm之PGM之BNet:贝叶斯网络BNet的相关论文、过程原理、关键步骤等相关配图

    Algorithm之PGM之BNet:贝叶斯网络BNet的相关论文.过程原理.关键步骤等相关配图 目录 BNet的相关论文 BNet的过程原理 BNet的关键步骤 BNet的相关论文 更新-- BNe ...

  4. MySQL 储存过程-原理、语法、函数详细说明

    2019独角兽企业重金招聘Python工程师标准>>> Mysql储存过程是一组为了完成特定功能的SQL语句集,经过编译之后存储在数据库中,当需要使用该组SQL语句时用户只需要通过指 ...

  5. C语言学习笔记---001C语言的介绍,编译过程原理,工具等

    C语言学习笔记---001C语言的介绍,编译过程原理,工具等 创梦技术交流平台:资源下载,技术交流,网络赚钱: 交流qq群:1群:248318056 2群:251572072 技术交流平台:cre.i ...

  6. C函数调用过程原理及函数栈帧分析

    在x86的计算机系统中,内存空间中的栈主要用于保存函数的参数,返回值,返回地址,本地变量等.一切的函数调用都要将不同的数据.地址压入或者弹出栈.因此,为了更好地理解函数的调用,我们需要先来看看栈是怎么 ...

  7. 强化学习之——马尔可夫决策过程原理

    强化学习之--马尔可夫决策过程原理 1.1 MDP:策略与环境模型 我们以蛇棋为模型引入--蛇棋的关键问题在于:哪些因素决定了蛇棋最终获得分数的多少? 两个因素 选择什么样的手法投掷(也就是投3以内的 ...

  8. Redis集群--Cluster--故障转移的过程(原理)

    原文网址:Redis集群--Cluster--故障转移的过程(原理)_IT利刃出鞘的博客-CSDN博客_redis集群故障转移 简介 本文介绍Redis集群(Cluster)的故障转移的流程. 故障发 ...

  9. php ucenter原理,深入挖掘Discuz Ucenter同步登录过程原理分析

    深入挖掘discuz ucenter同步登录过程原理分析,哈哈,其实纯属标题党,主要就是做了一个思路分析而已. Discuz是Ucenter的一个应用. Ucenter包含Server和Client, ...

  10. java 反序列化 ysoserial exploit/JRMPClient 原理剖析

    目录 0 前言 1 payloads/JRMPListener 1.1 payload生成 1.2 gadget链分析 2 使用RMIRegistryExploit攻击上述开启的监听 3 exploi ...

最新文章

  1. 技术分享连载(六十九)
  2. http://www.cnblogs.com/amboyna/archive/2008/03/08/1096024.html
  3. VTK:简单操作之DistanceBetweenPoints
  4. MySQL 的 bug 必须修复吗?
  5. linux下的安装命令行工具下载,linux系统程序安装(二)yum工具2-yum源及包下载
  6. 单片机定时器实验两位倒计时秒表_51单片机基础与应用8天速成(三)
  7. Eclipse中,查找文件后(使用ctrl shift R/T),如何关联到文件所在目录(查看文件所在的目录结构)。
  8. linux命令grep如何使用,Linux命令之grep命令简单使用
  9. UAC怎么关闭?电脑的UAC如何关闭
  10. 第五次作业 刘惠惠 自动生成的方法存根
  11. opencv-python——基于标志物的道路逆透视变换
  12. 安卓掌读小说v1.5.8破解版免费分享
  13. CRC32、murmur32、SDBM32碰撞实验数据对比
  14. 我的理想作文400字计算机,我的理想作文400字5篇
  15. iOS运行时Runtime浅析
  16. 王者荣耀战力查询小程序源码下载-支持安卓ios微信和QQ战力查询支持打包成APP
  17. java根据经纬度转地址或者根据地址转经纬度
  18. 笃行不怠勾勒人才图,望城区人才工作为高质量发展增添强劲动力
  19. 上计算机课玩游戏检讨400字,学生犯错万能检讨书400字7篇
  20. idea出现java___jb_old___

热门文章

  1. 计算机课制作海报教案,全国xx杯计算机类说课课件:制作宣传海报教学设计.docx...
  2. 吴恩达dalao教你如何读论文(翻译)
  3. 2022-2028全球与中国农业微电网市场现状及未来发展趋势
  4. 大规模图数据匹配技术综述
  5. Protues 基于51单片机的DS18B20温度采集:键盘输入温度上下限_超出设定温度范围报警_8数码管显示
  6. Ubuntu基本使用
  7. java常用集合集合讲解
  8. 佳能M50相机MP4视频损坏代码修复参考编码参数
  9. 北京环球度假区与腾讯互动娱乐达成合作,引入系列腾讯游戏IP
  10. 实习笔记 —— SSM三个框架的整合 III (对后台管理页面进行编写)