百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

JAVA序列化和反序列化及漏洞补救

liuian 2025-03-04 13:06 18 浏览

上周,网络安全人员再一次在黑产面前遭到重挫,Joomla曝高危0Day漏洞,无需用户登陆就能触发。Joomla漏洞在官方发布升级版和补丁之前,已经在各种地下黑色产业链中流传了一段时间,恐怕并且已经有不少网站被黑客拿下。 这个恶意代码的进入点是用户代理字符串,这是每个浏览器都在广而告之的内容:让浏览器知道用户的技术结构从而为站点提供最佳或最合适的版本。很显然这个字符串存储于Joomla数据库中,但并没有被清洁处理以检测恶意代码。攻击者能够通过能够播报虚假用户代理字符串的方式利用特殊app和脚本拉力轻易编制一个定制化字符串并将恶意代码附着,这个安全隐患是在php中session是通过序列化存储的。

想要了解关于PHP相关的远程代码执行漏洞分析,可参考:vBulletin5远程代码执行漏洞分析

JAVA序列化和反序列化是啥?

在现有很多的应用当中,需要对某些对象进行序列化,让它们离开内存空间,入驻物理硬盘,以便可以长期保存,其中最常见的是Web服务器中的Session对象。对象的序列化一般有两种用途:把对象的字节序列永久地保存到硬盘上,通常存放在一个指定文件中;或者在网络上传送对象的字节序列。

而把字节序列恢复为对象的过程称为对象的反序列化。当两个进程在进行远程通信时,彼此可以发送各种类型的数据,而且无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

其实,在不同的计算机语言中,数据结构、对象以及二进制串的表示方式并不相同。对于像Java这种完全面向对象的语言,程序员所操作的一切都是对象,来自于类的实例化。

JAVA序列化和反序列化实例

在Java语言中最接近数据结构的概念,就是 POJO(Plain Old Java Object)或者Javabean。小编更熟悉Java语言,还是以此为例说明一下序列化和反序列化的实现。

123456789101112131415161718192021222324252627282930313233343536373839404142434445publicstaticvoidmain(String[]args)throwsException{SerializeObject();//序列化Object对象Objecto=DeserializeObject();//反序列Object对象System.out.println(MessageFormat.format("name={0},age={1}, sex={2}",o.getName(),o.getSex(),o.getAge(),o.getHobby()));}/*** MethodName: SerializeObject* Description: 序列化Object对象* @author Haom* @throws FileNotFoundException* @throws IOException*/privatestaticvoidSerializeObject()throws FileNotFoundException,IOException{Objectobject=newObject();object.setName("haom");object.setSex("Female");object.setAge(18);object.setHobby("Taekwondo");// 对于ObjectOutputStream 对象输出流,将Object对象存储到M盘的object.txt文件中,完成对Object对象的序列化操作 ObjectOutputStream oo=newObjectOutputStream(newFileOutputStream(newFile("M:/object.txt")));oo.writeObject(object);System.out.println("Object Serialization success!");oo.close();}/*** MethodName: DeserializeObject* Description: 反序列Object对象* @author Haom* @throws Exception* @throws IOException*/privatestaticObjectDeserializeObject()throws Exception,IOException{ObjectInputStream ois=newObjectInputStream(newFileInputStream(newFile("M:/object.txt")));Objectobject=(Object)ois.readObject();System.out.println("Object deserialization success!");returnObject;}

以上代码说明:序列化Object成功后在M盘生成了一个object.txt文件,而反序列化Object是读取M盘的Object.txt后生成了一个Object对象。

当然,并不是一个实现了序列化接口的类的所有字段及属性,都是可以序列化的:

  • 如果该类有父类,则分两种情况来考虑:如果该父类已经实现了可序列化接口,则其父类的相应字段及属性的处理和该类相同;如果该类的父类没有实现可序列化接口,则该类的父类所有的字段属性将不会序列化,并且反序列化时会调用父类的默认构造函数来初始化父类的属性,而子类却不调用默认构造函数,而是直接从流中恢复属性的值。

  • 如果该类的某个属性标识为static类型的,则该属性不能序列化。

  • 如果该类的某个属性采用transient关键字标识,则该属性不能序列化。

那么,在什么情况下,需要自定义序列化的方式? 先举个简单的例子,如下:

1234567891011121314151617181920212223242526272829303132333435363738394041424344publicclassSeriDemo1implementsSerializable{privateStringname;transient privateStringpassword;// 瞬态,不可序列化状态,该字段的生命周期仅存于调用者的内存中publicSeriDemo1(){}publicSeriDemo1(Stringname,Stringpassword){this.name=name;this.password=password;}//模拟对密码进行加密privateStringchange(Stringpassword){returnpassword+"minna";}//写入privatevoidwriteObject(ObjectOutputStream outStream)throwsIOException{outStream.defaultWriteObject();outStream.writeObject(change(password));}//读取privatevoidreadObject(ObjectInputStream inStream)throws IOException,ClassNotFoundException{inStream.defaultReadObject();StringstrPassowrd=(String)inStream.readObject();//模拟对密码解密password=strPassowrd.substring(0,strPassowrd.length()-5);}//返回一个“以文本方式表示”此对象的字符串publicStringtoString(){return"SeriDemo1 [name="+name+", password="+password+"]";}//静态的mainpublicstaticvoidmain(String[]args)throwsException{SeriDemo1 demo=newSeriDemo1("haom","0123");ByteArrayOutputStream buf=newByteArrayOutputStream();ObjectOutputStream out=newObjectOutputStream(buf);out.writeObject(demo);ObjectInputStream in=newObjectInputStream(newByteArrayInputStream(buf.toByteArray()));demo=(SeriDemo1)in.readObject();System.out.println(demo);}}

如上代码说明,可以得知,以下情况需要自定义序列化的方式:

  • 为了确保序列化的安全性,可以对于一些敏感信息加密;

  • 确保对象的成员变量符合正确的约束条件;

  • 确保需要优化序列化的性能。

在序列化选型的过程中,安全性的考虑往往发生在跨局域网访问的场景。当通讯发生在公司之间或者跨机房的时候,出于安全的考虑,对于跨局域网的访问往往被限制为基于HTTP/HTTPS的80和443端口。如果使用的序列化协议没有兼容而成熟的HTTP传输层框架支持,可能会导致以下几种结果:

  • 因为访问限制而降低服务可用性。

  • 被迫重新实现安全协议而导致实施成本升高。

  • 开放更多的防火墙端口和协议访问,但是是以牺牲安全性为前提。

反序列化漏洞危害

当应用代码从用户接受序列化数据,并试图反序列化改数据进行下一步处理时,会产生反序列化漏洞,其中最有危害性的就是远程代码注入。

这种漏洞产生原因是,java类ObjectInputStream在执行反序列化时,并不会对自身的输入进行检查,这就说明恶意攻击者可能也可以构建特定的输入,在 ObjectInputStream类反序列化之后会产生非正常结果,利用这一方法就可以实现远程执行任意代码。

这个漏洞的严重风险在于,即使你的代码里没有使用到Apache Commons Collections里的类,只要Java应用的Classpath里有Apache Commons Collections的jar包,都可以远程代码执行。

漏洞的根本问题其实并不是Java序列化的问题,而是Apache Commons Collections允许链式的任意的类函数反射调用。攻击者通过允许Java序列化协议的端口,把攻击代码上传到服务器上,再由Apache Commons Collections里的TransformedMap来执行。

反序列化漏洞补救

现在,Apache Commons Collections在 3.2.2版本中做了一定的安全处理,对这些不安全的Java类的序列化支持增加了开关,默认为关闭状态。

涉及的类包括:CloneTransformer,ForClosure, InstantiateFactory, InstantiateTransformer, InvokerTransformer, PrototypeCloneFactory,
PrototypeSerializationFactory, WhileClosure。

RedHat发布JBoss相关产品的解决方案:
https://access.redhat.com/solutions/2045023。

严格意义说起来,Java相对来说安全性问题比较少,出现的一些问题大部分是利用反射,最终用Runtime.exec(String cmd)函数来执行外部命令的。如果可以禁止JVM执行外部命令,未知漏洞的危害性会大大降低,可以大大提高JVM的安全性。

比如:

123456789101112131415161718192021222324252627282930313233SecurityManager originalSecurityManager=System.getSecurityManager();if(originalSecurityManager==null){// 创建自己的SecurityManagerSecurityManager sm=newSecurityManager(){privatevoidcheck(Permission perm){// 禁止execif(perm instanceofjava.io.FilePermission){Stringactions=perm.getActions();if(actions!=null&&actions.contains("execute")){thrownewSecurityException("execute denied!");}}// 禁止设置新的SecurityManagerif(perm instanceofjava.lang.RuntimePermission){Stringname=perm.getName();if(name!=null&&name.contains("setSecurityManager")){thrownewSecurityException("System.setSecurityManager denied!");}}}@OverridepublicvoidcheckPermission(Permission perm){check(perm);}@OverridepublicvoidcheckPermission(Permission perm,Objectcontext){check(perm);}};System.setSecurityManager(sm);}

如上所示,只要在Java代码里简单加一段程序,就可以禁止执行外部程序了。

禁止JVM执行外部命令,是一个简单有效的提高JVM安全性的办法。可以考虑在代码安全扫描时,加强对Runtime.exec相关代码的检测。

小结

本文只是初步进行JAVA序列化和反序列化的科普,让大家对此问题及相关补救方式有个直观的印象和简单了解,下一步深入解还请继续关注绿盟技术博客。使用JAVA反序列化增多了数据的种类,但是还需要尽量避免使用反序列化的交互操作,减少风险的增加。目前,绿盟科技蜂巢社区启动应急机制,已经实现远程代码执行漏洞的在线检测。在社区中,大家可以进行网络安全扫描插件的开发及讨论。

相关推荐

教你把多个视频合并成一个视频的方法

一.情况介绍当你有一个m3u8文件和一个目录,目录中有连续的视频片段,这些片段可以连成一段完整的视频。m3u8文件打开后像这样:m3u8文件,可以理解为播放列表,里面是播放视频片段的顺序。视频片段像这...

零代码编程:用kimichat合并一个文件夹下的多个文件

一个文件夹里面有很多个srt字幕文件,如何借助kimichat来自动批量合并呢?在kimichat对话框中输入提示词:你是一个Python编程专家,完成如下的编程任务:这个文件夹:D:\downloa...

Java APT_java APT 生成代码

JavaAPT(AnnotationProcessingTool)是一种在Java编译阶段处理注解的工具。APT会在编译阶段扫描源代码中的注解,并根据这些注解生成代码、资源文件或其他输出,...

Unit Runtime:一键运行 AI 生成的代码,或许将成为你的复制 + 粘贴神器

在我们构建了UnitMesh架构之后,以及对应的demo之后,便着手于实现UnitMesh架构。于是,我们就继续开始UnitRuntime,以用于直接运行AI生成的代码。PS:...

挣脱臃肿的枷锁:为什么说Vert.x是Java开发者手中的一柄利剑?

如果你是一名Java开发者,那么你的职业生涯几乎无法避开Spring。它如同一位德高望重的老国王,统治着企业级应用开发的大片疆土。SpringBoot的约定大于配置、SpringCloud的微服务...

五年后,谷歌还在全力以赴发展 Kotlin

作者|FredericLardinois译者|Sambodhi策划|Tina自2017年谷歌I/O全球开发者大会上,谷歌首次宣布将Kotlin(JetBrains开发的Ja...

kotlin和java开发哪个好,优缺点对比

Kotlin和Java都是常见的编程语言,它们有各自的优缺点。Kotlin的优点:简洁:Kotlin程序相对于Java程序更简洁,可以减少代码量。安全:Kotlin在类型系统和空值安全...

移动端架构模式全景解析:从MVC到MVVM,如何选择最佳设计方案?

掌握不同架构模式的精髓,是构建可维护、可测试且高效移动应用的关键。在移动应用开发中,选择合适的软件架构模式对项目的可维护性、可测试性和团队协作效率至关重要。随着应用复杂度的增加,一个良好的架构能够帮助...

颜值非常高的XShell替代工具Termora,不一样的使用体验!

Termora是一款面向开发者和运维人员的跨平台SSH终端与文件管理工具,支持Windows、macOS及Linux系统,通过一体化界面简化远程服务器管理流程。其核心定位是解决多平台环境下远程连接、文...

预处理的底层原理和预处理编译运行异常的解决方案

若文章对您有帮助,欢迎关注程序员小迷。助您在编程路上越走越好![Mac-10.7.1LionIntel-based]Q:预处理到底干了什么事情?A:预处理,顾名思义,预先做的处理。源代码中...

为“架构”再建个模:如何用代码描述软件架构?

在架构治理平台ArchGuard中,为了实现对架构的治理,我们需要代码+模型描述所要处理的内容和数据。所以,在ArchGuard中,我们有了代码的模型、依赖的模型、变更的模型等,剩下的两个...

深度解析:Google Gemma 3n —— 移动优先的轻量多模态大模型

2025年6月,Google正式发布了Gemma3n,这是一款能够在2GB内存环境下运行的轻量级多模态大模型。它延续了Gemma家族的开源基因,同时在架构设计上大幅优化,目标是让...

比分网开发技术栈与功能详解_比分网有哪些

一、核心功能模块一个基本的比分网通常包含以下模块:首页/总览实时比分看板:滚动展示所有正在进行的比赛,包含比分、比赛时间、红黄牌等关键信息。热门赛事/焦点战:突出显示重要的、关注度高的比赛。赛事导航...

设计模式之-生成器_一键生成设计

一、【概念定义】——“分步构建复杂对象,隐藏创建细节”生成器模式(BuilderPattern):一种“分步构建型”创建型设计模式,它将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建...

构建第一个 Kotlin Android 应用_kotlin简介

第一步:安装AndroidStudio(推荐IDE)AndroidStudio是官方推荐的Android开发集成开发环境(IDE),内置对Kotlin的完整支持。1.下载And...