笔记源代码地址:B站 库米豪斯巴达锅铲祖师 空间
反序列化安全漏洞模块部分内容引用自:java反序列漏洞原理分析及防御修复方法,这篇文章的内容较其它文章而言比较清楚,我是根据这篇文章学习的,所以说法可能有部分相似
感谢 @Cohen 大佬补充的反序列化的安全问题,但是。。。好难学
Java反序列化的安全性问题
我没怎么看懂,就算看懂了我也不会完全说出来,因为涉及到Web方面的知识可能导致一些人直接开摆了
仅根据我昨晚两个小时的学习结果,反序列化的安全性问题大概可以概括为:若反序列化过程中黑客得到了输入命令的机会,或黑客能够通过某种方式影响序列化后的文件或数据库的内容,那么黑客就可以操控服务端,从而引发安全问题
黑客可以通过这些方式影响反序列化的结果,从而使读取者获得错误的信息。错误的信息也可能会影响另一部分的信息,进而形成一个影响链,达到黑客的目的
反序列化安全问题大致在Java中被最先利用,从此以后不断被发现。这是发展简史:
- 2011年开始,攻击者就开始利用反序列化问题发起攻击
- 2015年11月6日FoxGlove Security安全团队的@breenmachine发布了一篇长博客,阐述了利用java反序列化和Apache Commons Collections这一基础类库实现远程命令执行的真实案例,各大java web server纷纷中招,这个漏洞横扫WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的最新版。
- 2016年java中Spring与RMI集成反序列化漏洞,使成百上千台主机被远程访问
- 2017年末,WebLogic XML反序列化引起的挖矿风波,使得反序列化漏洞再一次引起热议。
- 从2018年至今,安全研究人员陆续爆出XML、Json、Yaml、PHP、Python、.NET中也存在反序列化漏洞,反序列化漏洞一直在路上。。。
那既然是安全问题,放着肯定是不行的,要解决。 但是反序列化安全问题从来没有被真正解决过。大家可以去了解一下历史上几次重要的反序列化安全漏洞(看不看得懂不重要,看懂了来教我qwq)
要解决问题,先要发现问题,也就是序列化行为。序列化文件的特征是:开头四个字节是AC ED 00 05
。在文件中,这类字节叫做魔数,用于确定文件的类型(详情参考动力节点的JVM教程)
或者从端口号为1099的Java RMI
模型来发现序列化行为。这是一种分布式对象应用,能够使一个JVM中的对象去调用另一个JVM中的对象方法,并获取结果,且它的数据传输完全基于反序列化
若在源码中,我们可以直接检查实现了Serializable
接口,从而定位可能存在的反序列化安全问题;如果你重写了readObject()
方法,那你需要检查这个重写方法里有没有能够利用的地方
然后怎么解决我不知道了,可以去看看那篇文章,链接放在最上面了qwq
Properties类及配置文件的认识
还记得我们的集合继承图嘛?(一定要背出噢!)
在Map
包一下,有个HashMap
,HashMap
存在一个子类HashTable
,它与HashMap的区别是:HashTable的键和值都不能是null
HashMap有一个子类叫做Properties
,它的条件更严格,键和值必须都是String
为什么这么严格呢?其实,Properties可以与IO流联用,读取特定格式的文件信息
在各大类文件中,有一类文件,能够记录配置信息,这些文件叫做配置文件。在配置文件中有一个分支,它的语法是这样的:
# 这是注释
data1 = asdf
data2 = ghjk
这种文件叫做:属性配置文件,专门用于属性类Properties。这种文件还有专门的后缀名:.properties
通过IO流可以读取属性配置文件的信息,然后用Properties类接收,就能通过集合来方便地管理数据,而数据需要更改时也只需要操作文件,不用到处翻源代码。这波IO流与集合的配合操作强度拉满
Properties与IO流联用步骤
由这个用法的特征,我们首先创建一个Propoties类:
Properties data = new Properties();
当然,你也可以选择传入一个已经存在的Properties类对象,这样你的新对象里就会有原对象的所有数据。这个构造器内部调用了另一个方法(顺便推断出默认容积是8)
实际的算法中,这个对象会被保存到这里:
而读取的时候,会使用这个对象,取出里面的数据。根据实际算法,返回值里判断的两个条件实际上是:获取到的字符串是null,defaults对象不是null,在这个与门中,若两个条件都满足,会试图从defaults对象里获取内容;否则返回原来获取的字符串,简单分析发现,这样就可以正确输入数据(剩下3种情况自己分析吧ovo)
言归正传,我们要从文件里获取信息,先创建一个文件,这里假设文件相对路径是"path.properties"
,然后用文件流读取(字节流和字符流都可以)
这是我们准备好的流:
try (var fis = new FileInputStream("path.properties")) {
} catch (IOException e) {
e.printStackTrace();
}
然后,你不觉得一个字符一个字符地读太烦了吗?那我们直接给它全部加载过来。请在try语句里加上这段代码,Properties对象里就会出现文件里的数据了
data.load(fis);
我是不是还没讲过Map接口啊qwq没事简单提一嘴,就像python的字典一样,Map可以用get方法,传入键,获取值,同理set方法可以用键给值重新赋值。键是不可以重复的,添加时用put方法添加,分别传入键和值两个参数
现在,我们可以读数据了。然后一看,Properties类没有泛型的,然后用get方法,要传入一个键,返回一个对象,对象还要强转为String,呕。。。
果断丢弃糟粕,既然是专门服务于属性配置文件的类,必定有相关的方法。实际上,getProperties()
方法可以获取值,获取的数据类型直接是String
比如我们获取data1
的数据:
String data1 = data.getProperty("data1");
而这个方法在你键不对或者对应的键没有值的时候,会给你返回默认字符串,这个字符串由你指定。比如这样写,没读到数据的时候它就会很诚实地告诉你没有数据:
String data1 = data.getProperty("data1", "嗯。。。妹油,不好意思");
然后我们去把data2字段的值改一下,用setProperties()
方法更改。更改后再获取输出就直接有效果了
data.setProperty("data2", "qwq");
那这样更改也没什么用啊,我们可以尝试一下,能不能发现一个方法,把更改后的数据写到文件里保存。通过短时间翻找,我们发现一个store
方法(这个真的好找,就它要填OutputStream)。我们指定一个输出流,指定字符串备注(通常为null,如果有值则会以注释形式写入文件)
然后给它输出到原来读取数据的那个文件(顺便演示一下备注信息,平常不给别人看的话,写null就好):
data.store(new FileOutputStream("path.properties"), "output!");
我直接在try语句里写了,所以没有报异常,就是可能关流的时候这个关不掉。运行后,我们检查文件,除了被更改的数据和备注信息,还有一个日期记录(Java好贴心qwq他真的,我哭死)
资源绑定器
我们甚至还可以找到看起来更专业的东西,叫做资源绑定器,用来操作Properties手感舒适
资源,resource;绑定器,bundle。我们要用的这个资源绑定器,类名很好记:ResourceBundle
它是一个抽象类,需要通过内部的getBundle()
静态方法来获取对象;它专业操作属性资源文件,给它开VIP了,我们可以不写后缀名,通过文件名直接读取属性配置文件。创建过程不抛出任何异常
就注意这么多了,还不懂的同学看代码演示,秒懂
ResourceBundle rb = ResourceBundle.getBundle("path");
它是只能用在属性配置文件上,其它的不能用了;而且这个路径是相对类路径的,不是相对当前目录的
类路径,指的是src文件夹,不管你用哪个编辑器,你怎么改设置,雷打不动就是src文件夹。我这里的项目结构是Java标准项目结构,有src的,而且path.properties
就在src里面,我就敢这么写,你们要去确认路径
一般认为,纯Java项目应该这样布置文件目录:
- 项目根目录(命名为你的项目名)
- src
- 包
- Java文件(一般只允许出现一个public类,接口,枚举,记录,注解)
- Java文件
- Java文件
- 包
- 包
- 文件包(专门用来放非Java文件的其他文件)
- 文件包
- 文件包
- 包
- 其它文件夹(视编辑器而定,如IDEA会创建
.idea
,Eclipse会创建.classpath
和.project
文件夹)
- src
不是我说的,我从入门就被教应该这样排文件qwq
然后我们有了资源绑定器,我们可以很膨胀啊,我们连Properties
都不要了,我们直接读数据:
String data1 = rb.getString("data1");
但是不好意思,好像功能没那么多qwq不能输出默认值,不能改,就读。但是你就说用起来爽不爽吧
这种方式的优点不仅是代码简单,而且我们避免了绝对路径的麻烦。现在你这个项目文件夹到处挪,不影响它读文件。要不,要是你不用IDEA,你可以需要学习这么一段代码来通过类路径以下的相对路径获取绝对路径:
String path = Thread.currentThread().getContextClassLoader().getResource("path").getPath();
吓人吧,知道资源绑定器多好用了吧(虽然这段代码你迟早要学的,因为资源绑定器只能用于属性配置文件,但是偷懒能偷一点是一点)