Java安全之反序列化漏洞分析

简介: Java安全之反序列化漏洞分析

Java序列化与反序列化


序列化与反序列化对于Java程序员来说,应该不算陌生了,序列化与反序列化简单来说就是Java对象与数据之间的相互转化。那么对于完全面向对象的Java语言来说为什么要有序列化机制?实质上,序列化机制并不只局限于Java语言,序列化的本质是内存对象到数据流的一种转换,我们知道内存中的东西不具备持久性,但有些场景却需要将对象持久化保存或传输。例如缓存系统中存储了用户的Session,如果缓存系统直接下线,带系统重启后用户就需要重新登陆,为了使缓存系统内存中的Session对象一直有效,就需要有一种机制将对象从内存中保存入磁盘,并且待系统重启后还能将Session对象恢复到内存中,这个过程就是对象序列化与反序列化的过程,从而避免了用户会话的有效性受系统故障的影响。此外,在Java工程中,序列化还广泛应用于JMX,RMI,网络传输(协议包对象)等场景,可以说序列化机制赋予了内存对象持久化的机会,就像虚拟机镜像(VMware Take a snapshot),也可以将序列化机制看作是内存对象的一种镜像机制。


在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以通过ObjectInputStream与ObejctOutputStream序列化,如下我们模拟了Session对象持久化存储与从磁盘加载的过程:

image.png

结合注释,这段测试代码应该不难理解,我们可以看到Java对象序列化就依赖于ObejctOutputStream的writeObject方法,而反序列化是由ObjectInputStream的readObject方法实现的,下图是作者画的一个序列化示意图:

image.png


反序列化漏洞成因


2015年年底,由公共依赖库Apache Common Collections引起的Java任意命令执行漏洞的严重安全问题,使得Java反序列化漏洞逐渐进入了安全

研究人员的视野(在此之前存在,但并未被重视),而任意命令执行的成因正是前文反序列化操作的ObejctInputStream类的readObject方法触发的。


Java序列化机制虽然有默认序列化机制,但也支持用户自定义的序列化与反序列化策略。例如对象的一些成员变量没必要序列化保存或传输,就可以不序列化,或者也可以对一些敏感字段进行处理等自定义对象序列化的行为,而自定义序列化规则的方式就是重写writeObejct与readObject。当对象重写了writeObejct或readObject方法时,Java序列化与反序列化就会调用用户自定义的逻辑了,下图示例我们对Session对象重写了序列化处理函数:

image.png

OK,到目前为止一切都在程序员的掌控之中!何来的漏洞之说?呵呵,意外往往就发生在不经意之间,如果反序列化过程中提供了命令执行的机会,那么任意命令执行漏洞就产生了,如下我们在Session对象的readObject函数中增加了执行命令的代码:

image.png

此时,黑客只需要将Session对象的sessionId构造成想要执行的命令字符串,即可实现远程命令执行的功能,如下成功打开系统calc.exe进程:

image.png

好了,我们再来梳理一下上例中漏洞存在的条件与利用思路:

  • 条件:首先Session对象重写了反序列化函数readObject,并且readObject方法存在执行命令的机会。
  • 漏洞利用:正常的反序列化流程会重新生成一个正常Session对象,而恶意的序列化数据抓住了反序列化的漏洞执行命令的机会,精心构造了序列化对象,使得数据流反序列化的过程中恶意命令得以执行。

看到这里,作为程序员的你肯定哈哈大笑!对象的反序列化函数谁会这样写?当然本示例只是为了以最直观的方式演示反序列漏洞产生原因,就直接提供了一个HelloWorld级别的漏洞示例,实际上,近两年 Java Apache-CommonsCollections 造成的序列化漏洞与Spring框架的反序列化漏洞(spring-tx.jar)的成因与原理都与上例相似,只是漏洞利用的构成比较复杂而已。


Apache-CommonCollections REC 漏洞解析


该漏洞曝光于2015年年底,被誉为当年“最被低估了的漏洞”,利用思路一经爆出,各大Java web 厂商纷纷躺枪,受此影响的Web服务器有:WebLogic、WebSphere、JBoss、Jenkins、OpenNMS等。介于该漏洞曝光距今已经有两年之久,并且网上也有对此分析的很透彻的文章,本文就来讲一下这个可以通过精心构造触发命令执行的漏洞的要点。

该漏洞的主要问题就出现在org.apache.commons.collections.Transformer接口上,在Apache-CommonsCollections包中,有一个InvokerTransformer类实现了Transformer接口,并且InvokerTransformer这个类也很恰巧,InvokerTransformer 的transform方法提供了一个可以通过Java反射机制,调用任意Java方法的机会:

image.png

相信很多漏洞利用者对 invoke非常敏感,意味着提供了方法调用的机会,再结合方法名与参数都能通过InvokerTransformer构造函数来控制,因此该类一定是漏洞挖掘者反复徘徊的点。那该如何利用?在commons-collections包中,InvokerTransformer结合ChainedTransformer就能构造一个Java类加载函数调用链,POC的作者是如下构造的:

image.png

这样构造的原因在于ChainedTransformer的transform函数会依次调用数组中InvokerTransformer的transform函数,构成一个函数调用链,下图是调试状态下,ChainedTransformer的transform方法的变量状态:

image.png

可以看到,通过ChainedTransformer.transform 的构造相当于反射调用了Runtime.getRuntime().exec(),从而触发了命令的执行。OK,那么问题来了,谁去触发调用ChainedTransformer.transform函数呢?漏洞利用者找到了包中TransformedMap.checkSetValue()方法:

image.png

这样一来,POC就可以通过构造一个TransformedMap对象,然后再想办法触发checkSetValue函数即可,而TransformedMap在Apache-CommonsCollections 这个集合库中可以通过TransformedMap.decorate修饰器方法来修饰一个Map对象,而Map对象在反序列化是只需调用MapEntiry.setValue就能触发checkSetValue函数,进而进一步触发第一步构造的ChainedTransformer.transform方法,从而实现漏洞利用的目的。


以上就是Apache-CommonsCollections RCE漏洞的利用思路,该漏洞的实质是Apache-CommonsCollections为利用者提供了一系列可以构造行命令机会。但是聪明的你一定会问,上面的漏洞触发条件并不是在反序列化函数readObject中实现的,怎么能在反序列化中触发POC的执行?好问题!思路是这样的,可以找到一个这样的对象:


  1. 该类自定义重写了 readObejct 反序列化函数。
  2. readObecjt 方法中调用了Map的checkSetValue函数,并且该Map对象还可以通过构造函数构造。


呵呵,我知道,你又开始笑了!的确利用条件比较苛刻,也难怪该漏洞在2015年初没有被重视起来,但是,无巧不成书,老外还就给你在JDK中找到了一个满足条件的的对象:sun.reflect.annotation.AnnotationInvocationHandler。好了,废话不多说,来看一下的AnnotationInvocationHandler的反序列化函数满不满足触发要求:

image.png

因为setValue函数会调用checkSetValue:

image.png

从而在sun.reflect.annotation.AnnotationInvocationHandler对象反序列化时,就满足了ChainedTransformer、TransformerMap构造的POC触发的条件,最后我们给出完整的构造Apache CommonCollections 反序列化漏洞利用POC的代码(POC来自网络):

image.png

小伙伴们看到这里可能一头雾水了,作者这里借用斗象科技在分析该漏洞时画的一幅漏洞POC构造与触发流程图,小伙伴们可以借此再来梳理一下该漏洞利用的思路:

image.png

因此,POC的触发流程为:TransformedMap->AnnotationInvocationHandler.readObject()->setValue()->checkSetValue() 最后由反序列化readObject时触发执行。的确,该漏洞的触发方法与构造思路非常精妙,就不得不佩服这位构造出POC的老外。


漏洞如何防范?


首先,开发者要有安全意识,应该清楚了解项目使用到的组件,以及这些组件是否存在漏洞,虽然说Apache-CommonsCollections RCE漏洞曝光将近两年时间,但使用3.2.2版本之前的Apache-CommonsCollections的web服务框架依然存在,近期曝光的Apache James 3.0.0 版本的CVE-2017-12628漏洞就是由于还在使用commons-collections-3.2.1.jar造成的。不过,安全意识对于很多开发者不是没有,而是没有接触过,作者曾在一个使用struts框架的团队待过,他们写出的代码真的是Web漏洞一箩筐啊。

此外,也可以禁用JVM执行外部命令(Runtime.exec),因为Runtime.exec对于大多数Java正常应用来说是不会用到的,但是确是黑客控制Web服务后运行命令的重要方法,因此该手段是Java Web防护常用的且有效的手段,如果从攻击者角度看这种防护效果,那就是攻击工具webshell只能文件相关操作,无法执行命令。可以通过扩展SecurityManager来禁用Runtime.exec,当触发运行时还可加入报警逻辑,启动应急响应:

image.png


反序列化漏洞利用的其他思考


作者认为反序列化漏洞的利用应该更为广泛,思路不应该仅仅局限于远程命令执行漏洞的利用,也存在着系统数据篡改污染的危险,造成系统业务安全问题。例如序列化对象在系统中承担了账单、金额、认证、鉴权等职责,如果可以被反序列化恶意利用,后果也非常严重。这样的威胁不亚于命令执行的威胁,并且事后难于排查,因为是内存攻击,载荷不落地。所以,反序列化对于业务安全的威胁也是我们一个值得深思的问题。


对了,有兴趣的从代码层面研究一把的小伙伴可以去公众号配套的代码仓库去下载。


相关文章
|
8天前
|
SQL 安全 Java
安全问题已经成为软件开发中不可忽视的重要议题。对于使用Java语言开发的应用程序来说,安全性更是至关重要
在当今网络环境下,Java应用的安全性至关重要。本文深入探讨了Java安全编程的最佳实践,包括代码审查、输入验证、输出编码、访问控制和加密技术等,帮助开发者构建安全可靠的应用。通过掌握相关技术和工具,开发者可以有效防范安全威胁,确保应用的安全性。
21 4
|
16天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
53 5
|
16天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
33 2
|
17天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
22 3
|
20天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
17天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
12 2
|
20天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
18 0
|
21天前
|
存储 Java 编译器
[Java]基本数据类型与引用类型赋值的底层分析
本文详细分析了Java中不同类型引用的存储方式,包括int、Integer、int[]、Integer[]等,并探讨了byte与其他类型间的转换及String的相关特性。文章通过多个示例解释了引用和对象的存储位置,以及字符串常量池的使用。此外,还对比了String和StringBuilder的性能差异,帮助读者深入理解Java内存管理机制。
18 0
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
10天前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。